之前一直在使用 Kotlin 写 Retrofit,一系列 api 封装太好了,我甚至不知道这堆语法糖和 api 展开后是什么。搜索资料和看了点 demo 后,写了一篇 Java 版本的 Retrofit+LiveData,现在才感觉自己略懂。看来 Kotlin First 还是得建立在完全理解原有的Java框架的基础上啊。
概览
Retrofit 和 LiveData 结合的一种实现方法
构建 Retrofit 对象时,添加一个转换适配器工厂 CallAdapterFactory
-
CallAdapterFactory 生产的适配器 CallAdapter 需要重写
responseType()
,adapt()
两个方法-
responseType()
用于返回从GSON数据到JAVA对象的类型
-
adapt()
该方法用于将 Retrofit 请求返回时的 Call 对象转换为需要的类,这里为我们自定义的 LiveData 对象,为了后续监听网络回调,这里 CallAdapterFactory 构建 CallAdapter 时需要传递当前 Call 实例
-
-
我们自定义的 LiveData 对象需要重写
onActive()
-
onActive()
该方法在 LiveData 实例的 observer 由 0变1 时会调用,我们传递进来的 Call 在这里使用。
由于网络线程在后台运行,此时应该对 Call 实例 enqueue 一个 Callback,Callback 重写的
onResponse()
方法中对 LiveData<T> 进行postValue\<T>()
-
这样我们的 LiveData 在有 observer 后就能及时收到请求的回调并进行更新了
1. 添加适配器工厂
...
Retrofit.Builder()
.baseUrl(baseUrl)
.client(new MyOkHttpClient())
// add an adapterFactory here
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build();
...
2. 定义工厂类
public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
private static final String TAG = "LiveDataCallAdapterFact";
@Nullable
@Override
public CallAdapter<?, ?> get(@NotNull Type returnType, @NotNull Annotation[] annotations, @NotNull Retrofit retrofit) {
if (getRawType(returnType) != LiveData.class){
return null;
}
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawType = getRawType(observableType);
Log.d(TAG, "get: rawType="+ rawType.getSimpleName());
return new LiveDataCallAdapter<>(observableType);
}
}
3. 定义Call转换适配器
public class LiveDataCallAdapter<T> implements CallAdapter<T, LiveData<T>> {
private final Type mResponseType;
public LiveDataCallAdapter(Type mResponseType) {
this.mResponseType = mResponseType;
}
@NotNull
@Override
// 用于返回从GSON数据到JAVA对象的的类型
public Type responseType() {
return mResponseType;
}
@NotNull
@Override
public LiveData<T> adapt(@NotNull Call<T> call) {
return new MyLiveData<>(call);
}
}
4. 自定义LiveData
public class MyLiveData<T> extends LiveData<T> {
private AtomicBoolean started = new AtomicBoolean(false);
private final Call<T> call;
public MyLiveData(Call<T> call) {
this.call = call;
}
// 在 observer 由 0变1 时会调用
@Override
protected void onActive() {
super.onActive();
if (started.compareAndSet(false, true)){
call.enqueue(new Callback<T>() {
@Override
public void onResponse(@NotNull Call<T> call, @NotNull Response<T> response) {
MyLiveData.super.postValue(response.body());
}
@Override
public void onFailure(@NotNull Call<T> call, @NotNull Throwable t) {
MyLiveData.super.postValue(null);
}
});
}
}
}
完成上述四部分后,Retrofit就能正常响应以
LiveData<T>
为返回值的方法了
简易实例
一般而言,Retrofit 对象需要一个静态类进行全局管理,这里为了减少static
的书写,使用 Kotlin 的object
关键字声明静态类
Retrofit 管理类 Retrofit Manager
private const val TAG = "RetrofitManager"
object RetrofitManager {
private var baseUrl:String = "https://www.wanandroid.com/user/"
private var timeoutDuration = 10L
private val retrofitMap = ConcurrentHashMap<String, Retrofit>()
private var okHttpBuilder = OkHttpClient.Builder()
fun init(baseUrl:String){
this.baseUrl = baseUrl
}
fun getBaseUrl():String{
return baseUrl
}
fun setTimeoutDuration(timeoutDuration:Long){
this.timeoutDuration = timeoutDuration
}
fun get(baseUrl: String=this.baseUrl):Retrofit{
var retrofit = retrofitMap[baseUrl]
if (retrofit == null){
retrofit = createRetrofit(baseUrl)
retrofitMap[baseUrl] = retrofit
}
return retrofit
}
private fun createRetrofit(baseUrl: String):Retrofit{
val myClient= okHttpBuilder
.connectTimeout(timeoutDuration, TimeUnit.SECONDS )
.addInterceptor(MyInterceptor())
.build()
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(myClient)
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// 测试用拦截器
private class MyInterceptor:Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val timeStart = System.nanoTime()
Log.i(TAG, "intercept: sending request ${request.url()} on ${chain.connection()},header=${request.headers()}")
val response = chain.proceed(request)
val timeEnd = System.nanoTime()
Log.i(TAG, "intercept: received response for ${response.request().url()} ${(timeEnd-timeStart)/0x1e6d} header=${response.headers()}")
return response
}
}
}
测试用 Service
interface LoginService{
@POST("login/")
fun getLoginResponse(@Query("username")userName:String,
@Query("password")userPwd:String)
:LiveData<LoginResponse>
}
这里为了便于测试,直接使用了网站的 url api,所以Post 方法的参数直接在 url 中进行了传递。如果要放到body中可以这样写
// 以url的格式进行编码,参数需要使用 @Field() 标注
interface PostInBodyWithUrlFormat{
@FormUrlEncoded
@Post("targetUrl/")
// post 的 body 会被写为 param1=xxx¶m2=xxx
fun postTest(@Field("param1") String param1, @Field("param2") String param2):Call<Any?>
}
// 以JSON的格式进行编码
interface PostInBodyWithJsonFormat{
@Post("targetUrl/")
fun postTest(@Body JSONObject params)
}
// 以自定义的Body进行编码
interface PostInBodyWithRequestBody{
@Post("targetUrl/")
fun postTest(@Body JSONObject params)
}
fun getRequestBody():RequestBody{
// 要查询所有注册的 MediaType 请访问 https://www.iana.org/assignments/media-types/media-types.xhtml
// 要查询常见的MIME Type 请访问 https://zhuanlan.zhihu.com/p/166136721
return RequestBody.create(MediaType.parse("text/plain; charset=utf-8","I can do anything here"))
}
api 的返回 Json
{"data":{"admin":false,"chapterTops":[],"coinCount":0,"collectIds":[],"email":"","icon":"","id":104201,"nickname":"2642981383","password":"","publicName":"2642981383","token":"","type":0,"username":"2642981383"},"errorCode":0,"errorMsg":""}
Json 对应的 JavaBean 类
data class LoginResponse(val data:Data, val errorCode:Int, val errorMsg:String){
data class Data(val admin:Boolean, val chapterTops:List<String>, val coinCount:Int,
val collectIds:List<Int>, val email:String, val icon:String,
val id:Int, val nickname:String, val password:String,
val publicName:String, val token:String, val type:Int,
val userName:String
)
}
LiveData测试
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate: ")
}
override fun onStart() {
super.onStart()
RetrofitManager.get().create(LoginService::class.java)
.getLoginLiveData("userName","userPwd") //填入wanandroid用户名和密码
.observe(this, { loginResponse->
Log.d(TAG, "onPause: loginResponse=${loginResponse}")
})
}
}