《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
网友评论