Retrofit+LiveData+Java 進行網(wǎng)絡(luò)請求

之前一直在使用 Kotlin 寫 Retrofit讨便,一系列 api 封裝太好了,我甚至不知道這堆語法糖和 api 展開后是什么啦撮。搜索資料和看了點 demo 后苟呐,寫了一篇 Java 版本的 Retrofit+LiveData,現(xiàn)在才感覺自己略懂坝撑【哺眩看來 Kotlin First 還是得建立在完全理解原有的Java框架的基礎(chǔ)上啊。

概覽

Retrofit 和 LiveData 結(jié)合的一種實現(xiàn)方法

  1. 構(gòu)建 Retrofit 對象時巡李,添加一個轉(zhuǎn)換適配器工廠 CallAdapterFactory

  2. 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 實例

  3. 我們自定義的 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&param2=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}")
            })
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末综液,一起剝皮案震驚了整個濱河市款慨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谬莹,老刑警劉巖檩奠,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桩了,死亡現(xiàn)場離奇詭異,居然都是意外死亡埠戳,警方通過查閱死者的電腦和手機井誉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來整胃,“玉大人颗圣,你說我怎么就攤上這事∑ㄊ梗” “怎么了在岂?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛮寂。 經(jīng)常有香客問我蔽午,道長,這世上最難降的妖魔是什么酬蹋? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任及老,我火速辦了婚禮,結(jié)果婚禮上除嘹,老公的妹妹穿的比我還像新娘写半。我一直安慰自己,他們只是感情好尉咕,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布叠蝇。 她就那樣靜靜地躺著,像睡著了一般年缎。 火紅的嫁衣襯著肌膚如雪悔捶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天单芜,我揣著相機與錄音蜕该,去河邊找鬼。 笑死洲鸠,一個胖子當(dāng)著我的面吹牛堂淡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扒腕,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼绢淀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘾腰?” 一聲冷哼從身側(cè)響起皆的,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹋盆,沒想到半個月后费薄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝全,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年楞抡,在試婚紗的時候發(fā)現(xiàn)自己被綠了伟众。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡拌倍,死狀恐怖赂鲤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柱恤,我是刑警寧澤数初,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站梗顺,受9級特大地震影響泡孩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寺谤,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一仑鸥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧变屁,春花似錦眼俊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闷板,卻和暖如春澎灸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遮晚。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工性昭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人县遣。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓糜颠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親萧求。 傳聞我的和親對象是個殘疾皇子括蝠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容