美文网首页美文共赏
《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