美文网首页美文共赏
《Android编程权威指南》之HTTP与后台任务

《Android编程权威指南》之HTTP与后台任务

作者: 夜远曦白 | 来源:发表于2021-11-29 19:39 被阅读0次

《Android编程权威指南》第 24 章啦,本章又有个新应用啦,叫 PhotoGallery,用来获取 Flickr 网站的最新公共图片「不限版权的图片」。本章将学习 Retrofit 网络请求库,Json 数据,Gson 解析 Json 等等。

一、创建 PhotoGallery 应用

按照惯例,创建应用,先写下 xml 文件,这里又是用 activity 嵌 fragment 的方式。
main_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/flayout_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

fragment 中放入列表:
fragment_photo_gallery.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerview_photo"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

MainActivity.kt:

class MainActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)

        val isFragmentContainerEmpty = savedInstanceState == null
        if (isFragmentContainerEmpty){
            supportFragmentManager
                .beginTransaction()
                .add(R.id.flayout_container, PhotoGalleryFragment.newInstance())
                .commit()
        }
    }
}
上面采用检查 savedInstanceState 的方式判断当前 Activity 是不是重建或者第一次创建,再添加 fragment。

PhotoGalleryFragment.kt:

class PhotoGalleryFragment : Fragment() {

    private lateinit var photoRecyclerView: RecyclerView

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_photo_gallery, container, false)
        photoRecyclerView = view.findViewById(R.id.recyclerview_photo)
        photoRecyclerView.layoutManager = GridLayoutManager(context, 3)
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    companion object {
        fun newInstance() = PhotoGalleryFragment()
    }

}

目前运行起来还是个空页面,因为没有给 RecyclerView 绑定数据。

二、Retrofit 网络连接基本

Retrofit 「https://square.github.io/retrofit/」是 Square 公司创建和维护的一个开源库。但本质上,它的 HTTP 客户端封装使用的是 OkHttp 「https://square.github.io/okhttp/」 库。

Retrofit 可创建 HTTP 网关类。给 Retrofit 一个带注解方法的接口,它会做接口实现。Retrofit 的接口实现能发起 HTTP 请求,收到 HTTP 响应数据后会解析为一个 OkHttp.ResponseBody。然而,OkHttp.ResponseBody 无法直接使用:你要将其转换为自己应用需要的数据类型。为解决这个问题,可以注册一个响应数据转换器。随后,在准备网络请求需要的数据以及从网络响应解析数据时,Retrofit 就可以用这个转换器进行各种数据类型的相互转换了。

先在 build.gradle 文件添加 Retrofit 依赖:

 implementation 'com.squareup.retrofit2:retrofit:2.9.0'
  • 定义 Retrofit API 接口

新建个包放接口 api,新建一个接口文件,FlickrApi.kt:

import retrofit2.Call
import retrofit2.http.GET

interface FlickrApi {
    @GET("/")
    fun fetchContents(): Call<String>
}

这里接口中的每一个函数都对应着一个特定的 HTTP 请求,必须使用 HTTP 请求方法注解。

常见的 HTTP 请求类型有 @GET、@POST、@PUT、@DELETE 和 @HEAD。

@GET("/") 注解的作用是把 fetchContents() 函数返回的 Call 配置成一个 GET 请求。字符串"/"表示一个相对路径 URL —— 针对 Flickr API 端点基 URL 来说的相对路径。大多数 HTTP 请求方法注解包括相对路径。这里,"/" 相对路径是指请求会发往我们稍后提供的基 URL。

所有 Retrofit 网络请求默认都会返回一个 retrofit2.Call 对象(一个可执行的网络请求)。执行 Call 网络请求就会返回一个相应的 HTTP 网络响应。(也可以配置 Retrofit 返回 RxJava Observable「目前主流方式」)

Call 的泛型参数是什么类型,Retrofit 在反序列化 HTTP 响应数据后就会生成同样的数据类型。Retrofit 默认会把 HTTP 响应数据反序列化为一个 OkHttp.ResponseBody 对象。指定 Call<String> 就是告诉 Retrofit ,我们需要的是 String 对象,而不是 OkHttp.ResponseBody 对象。

  • 构建 Retrofit 对象并创建 API 实例

Retrofit 实例负责实现和创建 API 接口实例。为基于定义的 API 接口生成网络请求。现在开始构建 Retrofit 实例。

        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.flickr.com/")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build()
        
        val flickrApi = retrofit.create(FlickrApi::class.java)

Retrofit.Builder() 是一个流接口,用来配置并构建 Retrofit 实例。
baseUrl(...) 提供要访问的基 URL 端点。
Retrofit.Builder() 进行参数设定后调用 build() 函数会返回一个配置好的 Retrofit实例。

再添加个依赖包,做数据类型转换。

implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'

利用 addConverterFactory(...) 函数添加特定的数据类型转换器实例。在返回 Call 结果之前,Retrofit对象就会使用这个字符串数据转换器把 ResponseBody 对象转换为 String 对象。当然,Square 还为 Retrofit 提供了其他一些开源数据类型转换器。

  • 执行网络请求

创建 Call 请求:

 val flickrHomePageRequest : Call<String> = flickrApi.fetchContents()

注意,调用 FlickrApi 的 fetchContents() 并不是执行网络请求,而是返回一个代表网络请求的 Call<String> 对象。

然后,在 onCreate(savedInstanceState: Bundle?) 里调用 enqueue(...) 去执行代表网络请求的 Call 对象。

        flickrHomePageRequest.enqueue(object : Callback<String> {
            override fun onResponse(call: Call<String>, response: Response<String>) {
                Log.d(TAG, "Response received : ${response.body()}")
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
                Log.e(TAG, "Failed to fetch photos", t)
            }
        })

Retrofit 天生就遵循两个最重要的Android多线程规则。

(1) 仅在后台线程上执行耗时任务。

(2) 仅在主线程上做 UI 更新操作。

Call.enqueue(...) 函数执行代表网络请求的 Call 对象。最关键的是,它是在后台线程上执行网络请求的。这一切都由 Retrofit 管理和调度的。

传递给 onResponse() 和 onFailure() 函数的 Call 对象就是最初发起网络请求的 Call 对象。

  • 获取网络使用权限

在 AndroidManifest.xml 中添加网络权限:

 <uses-permission android:name="android.permission.INTERNET" />

运行可以看到打印日志「注意此 api 需要翻墙访问,so,可以自行找个其他国内公开的 api 进行访问」

Log
  • 使用仓库模式联网

这里把 Retrofit 配置代码和 API 联网代码抽出来,移到一个新类中。

private const val TAG = "FlickrFetchr"

class FlickrFetchr {

    private val flickrApi :FlickrApi

    init {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.flickr.com/")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build()
        flickrApi = retrofit.create(FlickrApi::class.java)
    }

    fun fetchContents():LiveData<String>{

        val responseLiveData : MutableLiveData<String> = MutableLiveData()

        val flickrHomePageRequest: Call<String> = flickrApi.fetchContents()

        flickrHomePageRequest.enqueue(object : Callback<String> {
            override fun onResponse(call: Call<String>, response: Response<String>) {
                Log.d(TAG, "Response received : ${response.body()}")
                responseLiveData.value = response.body()
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
                Log.e(TAG, "Failed to fetch photos", t)
            }
        })
        return responseLiveData
    }
}

注意,fetchContents() 函数返回的是个无法修改的 LiveData<String>。可修改的 LiveData 对象尽量不要对外暴露,以防被其他外部代码篡改。LiveData 里的数据流动应保持一个方向。

然后修改 PhotoGalleryFragment 中的 onCreate() 方法。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val flickrLiveData: LiveData<String> = FlickrFetchr().fetchContents()
        flickrLiveData.observe(this,
            Observer { responseString ->
                Log.d(TAG, "Response received:$responseString")
            })
    }

这里借鉴了 Google 应用架构指导推崇的仓库模式。FlickrFetchr 充当基本仓库的角色。这种仓库类封装了从一个或多个数据源获取数据的逻辑。不管是本地数据库,还是远程服务器,它都知道该如何获取或保存各种数据。UI 代码不关心数据的获取和保存(仓库类自己的内部实现),需要数据时,找仓库类就行了。

运行程序,可以看到日志打印跟上述一样,是 Flickr 主页内容。

三、从 Flickr 获取 JSON 数据

四、应对设备配置改变

五、在 RecyclerView 里显示结果

其他

PhotoGallery 项目 Demo 地址:

https://github.com/visiongem/AndroidGuideApp/tree/master/PhotoGallery

相关文章

网友评论

    本文标题:《Android编程权威指南》之HTTP与后台任务

    本文链接:https://www.haomeiwen.com/subject/wshkxrtx.html