之前一直在使用 Kotlin 寫 Retrofit讨便,一系列 api 封裝太好了,我甚至不知道這堆語法糖和 api 展開后是什么啦撮。搜索資料和看了點 demo 后苟呐,寫了一篇 Java 版本的 Retrofit+LiveData,現(xiàn)在才感覺自己略懂坝撑【哺眩看來 Kotlin First 還是得建立在完全理解原有的Java框架的基礎(chǔ)上啊。
概覽
Retrofit 和 LiveData 結(jié)合的一種實現(xiàn)方法
構(gòu)建 Retrofit 對象時巡李,添加一個轉(zhuǎn)換適配器工廠 CallAdapterFactory
-
CallAdapterFactory 生產(chǎn)的適配器 CallAdapter 需要重寫
responseType()
抚笔,adapt()
兩個方法-
responseType()
用于返回從GSON數(shù)據(jù)到JAVA對象的類型
-
adapt()
該方法用于將 Retrofit 請求返回時的 Call 對象轉(zhuǎn)換為需要的類,這里為我們自定義的 LiveData 對象侨拦,為了后續(xù)監(jiān)聽網(wǎng)絡(luò)回調(diào)殊橙,這里 CallAdapterFactory 構(gòu)建 CallAdapter 時需要傳遞當(dāng)前 Call 實例
-
-
我們自定義的 LiveData 對象需要重寫
onActive()
-
onActive()
該方法在 LiveData 實例的 observer 由 0變1 時會調(diào)用,我們傳遞進來的 Call 在這里使用狱从。
由于網(wǎng)絡(luò)線程在后臺運行膨蛮,此時應(yīng)該對 Call 實例 enqueue 一個 Callback,Callback 重寫的
onResponse()
方法中對 LiveData<T> 進行postValue\<T>()
-
這樣我們的 LiveData 在有 observer 后就能及時收到請求的回調(diào)并進行更新了
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轉(zhuǎn)換適配器
public class LiveDataCallAdapter<T> implements CallAdapter<T, LiveData<T>> {
private final Type mResponseType;
public LiveDataCallAdapter(Type mResponseType) {
this.mResponseType = mResponseType;
}
@NotNull
@Override
// 用于返回從GSON數(shù)據(jù)到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 時會調(diào)用
@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就能正常響應(yīng)以
LiveData<T>
為返回值的方法了
簡易實例
一般而言鸽疾,Retrofit 對象需要一個靜態(tài)類進行全局管理,這里為了減少static
的書寫训貌,使用 Kotlin 的object
關(guān)鍵字聲明靜態(tài)類
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>
}
這里為了便于測試,直接使用了網(wǎng)站的 url api冒窍,所以Post 方法的參數(shù)直接在 url 中進行了傳遞递沪。如果要放到body中可以這樣寫
// 以url的格式進行編碼,參數(shù)需要使用 @Field() 標(biāo)注
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 對應(yīng)的 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}")
})
}
}