Retrofit 2.9.0

Retrofit

Retrofit最新版引入

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}

除了Retrofit之外不引入其他依賴。
網(wǎng)絡(luò)請(qǐng)求:

interface ApiService {
    @GET("getUserData")
    fun getUserData():Call<ResponseBody>
}

    fun netRequest(){
        // 網(wǎng)絡(luò)請(qǐng)求地址格式要寫對(duì)欣簇,否則crash
        // 需要網(wǎng)絡(luò)權(quán)限否則crash
        val retrofit = Retrofit.Builder()
            .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
            .build()
        val service = retrofit.create(ApiService::class.java)
        val call = service.getUserData()
        call.enqueue(object :Callback<ResponseBody>{
            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                Log.d(TAG, "onFailure: $t")
            }

            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                val userBean = response.body()?.string()
                Log.d(TAG, "onResponse: $userBean")
            }

        })
    }

Retrofit是建立在OkHttp只上的一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝庫(kù)巾钉,內(nèi)部依靠OkHttp來完成網(wǎng)絡(luò)請(qǐng)求翘狱。API通過interface來聲明,配置API路徑砰苍、請(qǐng)求方式潦匈、請(qǐng)求參數(shù)、返回值類型等配置赚导。getUserData()請(qǐng)求結(jié)果是一個(gè)json格式的字符串历等,返回類型定義為Call<ResponseBody>,okhttp3.ResponseBody辟癌,retrofit2.Call是Retrofit對(duì)okhttp3.Call包裝寒屯。

converter-gson

上面返回的是json格式,我們希望返回的是Bean對(duì)象黍少;使用庫(kù)進(jìn)行反序列化寡夹;
庫(kù)引入:

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

請(qǐng)求邏輯:

interface ApiService {
    @GET("getUserData")
    fun getUserData():Call<UserBean>
}

data class UserBean(val status:String,val msg:String,val data:Data)
data class Data(val userName:String,val userAge:String)

fun netRequest(){
        // 網(wǎng)絡(luò)請(qǐng)求地址格式要寫對(duì),否則crash
        // 需要網(wǎng)絡(luò)權(quán)限否則crash
        val retrofit = Retrofit.Builder()
            .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val service = retrofit.create(ApiService::class.java)
        val call = service.getUserData()
        call.enqueue(object :Callback<UserBean>{
            override fun onFailure(call: Call<UserBean>, t: Throwable) {
                Log.d(TAG, "onFailure: $t")
            }

            override fun onResponse(call: Call<UserBean>, response: Response<UserBean>) {
                val userBean = response.body()
                Log.d(TAG, "onResponse: $userBean")
            }
        })
    }

adapter-rxjava2

如果不想看到Call<UserBean>,通過RxJava方式進(jìn)行網(wǎng)絡(luò)請(qǐng)求厂置,使用此庫(kù)Observable菩掏。
依賴庫(kù):

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'

代碼邏輯:注意需要在自線程執(zhí)行,否則線程異常:android.os.NetworkOnMainThreadException

interface ApiService {
    @GET("getUserData")
    fun getUserData():Observable<UserBean>
}

    fun netRequest() {
        Thread(object :Runnable{
            override fun run() {
                // 網(wǎng)絡(luò)請(qǐng)求地址格式要寫對(duì)昵济,否則crash
                // 需要網(wǎng)絡(luò)權(quán)限否則crash
                val retrofit = Retrofit.Builder()
                    .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build()
                val service = retrofit.create(ApiService::class.java)
                val call = service.getUserData()
                // 需要自線程執(zhí)行否則報(bào):accept(t: Throwable?)--》android.os.NetworkOnMainThreadException
                call.subscribe(object : Consumer<UserBean> {
                    override fun accept(userBean: UserBean?) {
                        Log.d(TAG, "accept-userBean: $userBean")
                    }
                }, object : Consumer<Throwable> {
                    override fun accept(t: Throwable?) {
                        Log.d(TAG, "accept-error: $t")
                    }
                })
            }
        }).start()
    }

思路

不論是Call還是Observable類型智绸,只需要添加不同的CallAdapterFactory即可野揪,LiveData類型也是可以的。不管需要ResponseBody還是Bean對(duì)象類型瞧栗,也是添加不同的ConverterFactory即可斯稳,API返回XML格式也是可以解析的。

Retrofit.create()

Proxy.newProxyInstance實(shí)現(xiàn)動(dòng)態(tài)代理模式迹恐。通過動(dòng)態(tài)代理挣惰,將ApiService調(diào)用操作轉(zhuǎn)發(fā)給InvocationHandler來完成。Retrofit后續(xù)會(huì)通過反射拿到我們聲明的getUserData()時(shí)標(biāo)注各個(gè)配置項(xiàng)殴边,如:API路徑憎茂、請(qǐng)求方式、請(qǐng)求參數(shù)锤岸、返回值類型等信息竖幔,然后拼接為OkHttp的一個(gè)原始網(wǎng)絡(luò)請(qǐng)求。當(dāng)我們調(diào)用了call.enqueue時(shí)是偷,這個(gè)操作會(huì)觸發(fā)InvocationHandler去發(fā)起OkHttp網(wǎng)絡(luò)請(qǐng)求赏枚。
Retrofit會(huì)根據(jù)method是否是默認(rèn)方法來決定如何調(diào)用,loadServiceMethod(method)方法:
將每個(gè)代表接口方法的method轉(zhuǎn)化為ServiceMethod對(duì)象晓猛,包含了API的具體信息
因?yàn)锳PI可能會(huì)被多次調(diào)用饿幅,將構(gòu)造出來的ServiceMethod對(duì)象緩存到se rviceMethodCache中實(shí)現(xiàn)復(fù)用。

ServiceMethod

loadServiceMethod(method)返回的是一個(gè)ServiceMethod對(duì)象戒职,每個(gè)ServiceMethod對(duì)象對(duì)應(yīng)一個(gè)API接口方法栗恩,內(nèi)部包含對(duì)API解析結(jié)果。loadServiceMethod(method).invoke(args)調(diào)用API方法并傳遞參數(shù)的過程洪燥,對(duì)應(yīng):val call = service.getUserData()這個(gè)過程磕秤。
ServiceMethod是一個(gè)抽象類,包含抽象的invoke(Object[] args)方法捧韵。
ServiceMethod使用了工廠模式市咆,由于API的最終請(qǐng)求方式可能是多樣化的≡倮矗可能通過線程池來執(zhí)行蒙兰,也可以通過kotlin協(xié)程來執(zhí)行,使用工廠模式的意義可以將這種差異都隱藏在不同的ServiceMethod實(shí)現(xiàn)類中芒篷,外部統(tǒng)一通過parseAnnotations方法來獲取ServiceMethod實(shí)現(xiàn)類搜变。
parseAnnotations方法返回的ServiceMethod實(shí)際上是HttpServiceMethod,通過HttpServiceMethod.parseAnnotations返回的HttpServiceMethod實(shí)現(xiàn)针炉。

HttpServiceMethod

是ServiceMethod直接唯一字類挠他。HttpServiceMethod也是一個(gè)抽象類,包含兩個(gè)泛型聲明篡帕,ResponseT表示API方法返回值的外層包裝類型殖侵,ReturnT是實(shí)際需要的數(shù)據(jù)類型贸呢。例如fun getUserData():Call<UserBean>方法,ResponseT對(duì)應(yīng)的是Call拢军,ReturnT對(duì)應(yīng)的是UserBean楞陷。此外,HttpServiceMethod也實(shí)現(xiàn)了父類invoke方法朴沿,并轉(zhuǎn)交給另一個(gè)抽象方法adapt來完成猜谚,API網(wǎng)絡(luò)請(qǐng)求具體看adapt實(shí)現(xiàn)败砂。
Retrofit目前已經(jīng)支持Kotlin協(xié)程方式進(jìn)行調(diào)用了赌渣。
parseAnnotations主要邏輯:
1、先通過createCallAdapter(retrofit, method, adapterType, annotations)方法拿到CallAdapter對(duì)象昌犹,實(shí)現(xiàn)API方法的不同返回值包裝類處理邏輯坚芜。例如:getUserData()方法返回的值包裝類類型Call,那返回CallAdapter對(duì)象對(duì)應(yīng)DefaultCallAdapterFactory包含的Adapter斜姥;如果是Observable,那么返回的就是RxJava2CallAdapterFactory包含的Adapter鸿竖。
2、通過createResponseConverter(retrofit, method, responseType)方法拿到Converter對(duì)象铸敏,Converter就用于實(shí)現(xiàn)API方法的不同返回值處理邏輯缚忧。例如:getUserData()返回類型ResponseBody,那么Converter對(duì)象就對(duì)應(yīng)BuiltInConverters杈笔;如果是UserBean,那么對(duì)應(yīng)GsonConverterFactory
根據(jù)上面兩步闪水,構(gòu)造出一個(gè)CallAdapted對(duì)象并返回。
CallAdapter是HttpServiceMethod的子類蒙具,在InvocationHandler中通過loadServiceMethod(method).invoke(args)發(fā)起調(diào)用鏈球榆,會(huì)先創(chuàng)建出一個(gè)OkHttpCall對(duì)象,并最后調(diào)用callAdapter.adapt(call)方法

OkHttpCall

是實(shí)際發(fā)起OkHttp請(qǐng)求的地方禁筏。當(dāng)調(diào)用getUserData():Call<ResponseBody>方法時(shí)持钉,返回的Call對(duì)象實(shí)際上是OkHttpCall類型,而當(dāng)我們調(diào)用call.enqueue(callback)方法時(shí)篱昔,enqueue方法會(huì)發(fā)起一個(gè)OkHttp請(qǐng)求每强,傳入的Callback對(duì)象會(huì)由okhttp3.callback本身回調(diào)進(jìn)行中轉(zhuǎn)調(diào)用。

總結(jié)

通過retrofit.create(ApiService::class.java)得到ApiService動(dòng)態(tài)實(shí)現(xiàn)類州刽,通過Java原生提供的Proxy.newProxyInstance代表的動(dòng)態(tài)代理功能來實(shí)現(xiàn)的舀射。拿到ApiService實(shí)現(xiàn)類,可以直接調(diào)用ApiService中聲明的方法怀伦。
當(dāng)我們調(diào)用了service.getUserData()方法脆烟,Retrofit會(huì)將每個(gè)API方法都抽象封裝為一個(gè)ServiceMethod并緩存起來,操作會(huì)轉(zhuǎn)交給ServiceMethod來完成房待,由ServiceMethod來負(fù)責(zé)返回我們的目標(biāo)類型邢羔,對(duì)應(yīng)的是ServiceMethod.invoke(Object[] args)方法驼抹,args代表的是我們調(diào)用的API方法時(shí)需要傳遞參數(shù),對(duì)應(yīng)本例是空數(shù)組拜鹤。
ServiceMethod只具有唯一的子類HttpServiceMethod, 而HttpServiceMethod會(huì)invoke方法構(gòu)建出一個(gè)OkHttpCall對(duì)象框冀,然后調(diào)用其抽象方法adapt
對(duì)于不同的請(qǐng)求方式,ServiceMethod.parseAnnotations方法最終會(huì)返回不同的HttpServiceMethod子類敏簿。本例明也,最終會(huì)返回CallAdapter對(duì)象

Kotlin 協(xié)程方式來調(diào)用

依賴庫(kù):

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
}

通過runBlocking來啟動(dòng)一個(gè)協(xié)程,避免請(qǐng)求還未結(jié)束main線程就停止的情況惯裕。實(shí)際開發(fā)中要避免runBlocking這樣使用協(xié)程温数。

interface ApiService {
    @GET("getUserData")
    suspend fun getUserData():UserBean
}

    fun test2() {
        val retrofit = Retrofit.Builder().baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val service = retrofit.create(ApiService::class.java)
        runBlocking {
            Log.d(TAG, "test2: isMain-1-:${Looper.myLooper() == Looper.getMainLooper()}")
            val job: Job = launch {
                Log.d(TAG, "test2: isMain-2-:${Looper.myLooper() == Looper.getMainLooper()}")
                try {
                    val userBean = service.getUserData()
                    Log.d(TAG, "test2: userBean:$userBean")
                } catch (e: Throwable) {
                    Log.d(TAG, "test2: onFailure:$e")
                }
            }
        }
    }

getUserData()不需要任何包裝類了,直接聲明目標(biāo)數(shù)據(jù)類型就可以蜻势,使用更簡(jiǎn)潔撑刺。
每個(gè)方法用suspend關(guān)鍵字進(jìn)行修飾,標(biāo)明其只能在協(xié)程中調(diào)用握玛。
Retrofit是以Java語言實(shí)現(xiàn)的够傍,但suspend關(guān)鍵字只能用于Kotlin,兩者存在“溝通障礙”,但只要調(diào)用方也屬于JVM語言挠铲,那Retrofit就是可以使用的冕屯,通過IDEA將ApiService反編譯為Java類,看下suspend函數(shù)在Retrofit的角度來看是怎樣實(shí)現(xiàn)的

public interface ApiService {
   @GET("getUserData")
   @Nullable
   Object getUserData1(@NotNull Continuation var1);

方法返回值類型變?yōu)镺bject拂苹,方法參數(shù)列表中添加了一個(gè)kotlin.coroutines.Continuation參數(shù)安聘。
在RequestFactory類中包含了一個(gè)isKotlinSuspendFunction的boolen類型的變量,當(dāng)前解析的Method是否是suspend函數(shù)醋寝。RequestFactory的build()方法中搞挣,對(duì)API方法每個(gè)參數(shù)進(jìn)行解析,包含了檢測(cè)當(dāng)前解析參數(shù)是否屬于最后一個(gè)參數(shù)的邏輯音羞。
如果檢測(cè)到最后一個(gè)參數(shù)類型是Continuation.class囱桨,那isKotlinSuspendFunction就會(huì)變?yōu)閠rue。
API最后一個(gè)參數(shù)強(qiáng)轉(zhuǎn)為Continuation<ResponseT>類型嗅绰,調(diào)用KotlinExtensions.await(call,continuation)這個(gè)Kotlin的擴(kuò)展函數(shù)
await()以suspendCancellableCoroutine這個(gè)支持cancel的CoroutineScope作為作用域舍肠,以Call.enqueue的方式發(fā)起OkHttp請(qǐng)求,拿到responseBody后就透?jìng)鞒鰜砭矫妫瓿烧麄€(gè)調(diào)用流程翠语。

Retrofit對(duì)Android平臺(tái)

Retrofit并不需要依賴于Android平臺(tái),可以用于任意的Java客戶端财边,Retrofit只是對(duì)Android平臺(tái)進(jìn)行了特殊實(shí)現(xiàn)肌括。
在構(gòu)建Retrofit對(duì)象時(shí)候,可以選擇傳遞一個(gè)Platform對(duì)象用于標(biāo)記調(diào)用方所處的平臺(tái)
1酣难、判斷是否支持Java8谍夭,是否支持調(diào)用interface的默認(rèn)方法黑滴,判斷是否支持Optional和CompletableFuture要用到。因?yàn)锳ndroid應(yīng)用如果支持Java8,需要Gradle文件進(jìn)行主動(dòng)配置紧索,Java8在Android平臺(tái)目前也支持不徹底袁辈。
2、實(shí)現(xiàn)main線程回調(diào)的Executor珠漂。Android平臺(tái)不允許在main線程上執(zhí)行耗時(shí)任務(wù)晚缩,UI操作都需要切換到main線程來完成。對(duì)于Android平臺(tái)媳危,Retroft回調(diào)網(wǎng)絡(luò)請(qǐng)求結(jié)果荞彼,通過main線程執(zhí)行的Executor來進(jìn)行線程切換。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末济舆,一起剝皮案震驚了整個(gè)濱河市卿泽,隨后出現(xiàn)的幾起案子莺债,更是在濱河造成了極大的恐慌滋觉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐邦,死亡現(xiàn)場(chǎng)離奇詭異椎侠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)措拇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門我纪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丐吓,你說我怎么就攤上這事浅悉。” “怎么了券犁?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵术健,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我粘衬,道長(zhǎng)荞估,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任稚新,我火速辦了婚禮勘伺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褂删。我一直安慰自己飞醉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布屯阀。 她就那樣靜靜地躺著缅帘,像睡著了一般噪裕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上股毫,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天膳音,我揣著相機(jī)與錄音,去河邊找鬼铃诬。 笑死祭陷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趣席。 我是一名探鬼主播兵志,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宣肚!你這毒婦竟也來了想罕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤霉涨,失蹤者是張志新(化名)和其女友劉穎按价,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笙瑟,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楼镐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了往枷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片框产。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖错洁,靈堂內(nèi)的尸體忽然破棺而出秉宿,到底是詐尸還是另有隱情,我是刑警寧澤屯碴,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布描睦,位于F島的核電站,受9級(jí)特大地震影響窿锉,放射性物質(zhì)發(fā)生泄漏酌摇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一嗡载、第九天 我趴在偏房一處隱蔽的房頂上張望窑多。 院中可真熱鬧,春花似錦洼滚、人聲如沸埂息。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽千康。三九已至享幽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拾弃,已是汗流浹背值桩。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豪椿,地道東北人奔坟。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搭盾,于是被迫代替她去往敵國(guó)和親咳秉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354