Retrofit 2.9.0

Retrofit

Retrofit最新版引入

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

除了Retrofit之外不引入其他依赖。
网络请求:

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

    fun netRequest(){
        // 网络请求地址格式要写对,否则crash
        // 需要网络权限否则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只上的一个网络请求封装库,内部依靠OkHttp来完成网络请求。API通过interface来声明,配置API路径、请求方式、请求参数、返回值类型等配置。getUserData()请求结果是一个json格式的字符串,返回类型定义为Call<ResponseBody>,okhttp3.ResponseBody,retrofit2.Call是Retrofit对okhttp3.Call包装。

converter-gson

上面返回的是json格式,我们希望返回的是Bean对象;使用库进行反序列化;
库引入:

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

请求逻辑:

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(){
        // 网络请求地址格式要写对,否则crash
        // 需要网络权限否则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方式进行网络请求,使用此库Observable。
依赖库:

    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'

代码逻辑:注意需要在自线程执行,否则线程异常:android.os.NetworkOnMainThreadException

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

    fun netRequest() {
        Thread(object :Runnable{
            override fun run() {
                // 网络请求地址格式要写对,否则crash
                // 需要网络权限否则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()
                // 需要自线程执行否则报: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对象类型,也是添加不同的ConverterFactory即可,API返回XML格式也是可以解析的。

Retrofit.create()

Proxy.newProxyInstance实现动态代理模式。通过动态代理,将ApiService调用操作转发给InvocationHandler来完成。Retrofit后续会通过反射拿到我们声明的getUserData()时标注各个配置项,如:API路径、请求方式、请求参数、返回值类型等信息,然后拼接为OkHttp的一个原始网络请求。当我们调用了call.enqueue时,这个操作会触发InvocationHandler去发起OkHttp网络请求。
Retrofit会根据method是否是默认方法来决定如何调用,loadServiceMethod(method)方法:
将每个代表接口方法的method转化为ServiceMethod对象,包含了API的具体信息
因为API可能会被多次调用,将构造出来的ServiceMethod对象缓存到se rviceMethodCache中实现复用。

ServiceMethod

loadServiceMethod(method)返回的是一个ServiceMethod对象,每个ServiceMethod对象对应一个API接口方法,内部包含对API解析结果。loadServiceMethod(method).invoke(args)调用API方法并传递参数的过程,对应:val call = service.getUserData()这个过程。
ServiceMethod是一个抽象类,包含抽象的invoke(Object[] args)方法。
ServiceMethod使用了工厂模式,由于API的最终请求方式可能是多样化的。可能通过线程池来执行,也可以通过kotlin协程来执行,使用工厂模式的意义可以将这种差异都隐藏在不同的ServiceMethod实现类中,外部统一通过parseAnnotations方法来获取ServiceMethod实现类。
parseAnnotations方法返回的ServiceMethod实际上是HttpServiceMethod,通过HttpServiceMethod.parseAnnotations返回的HttpServiceMethod实现。

HttpServiceMethod

是ServiceMethod直接唯一字类。HttpServiceMethod也是一个抽象类,包含两个泛型声明,ResponseT表示API方法返回值的外层包装类型,ReturnT是实际需要的数据类型。例如fun getUserData():Call<UserBean>方法,ResponseT对应的是Call,ReturnT对应的是UserBean。此外,HttpServiceMethod也实现了父类invoke方法,并转交给另一个抽象方法adapt来完成,API网络请求具体看adapt实现。
Retrofit目前已经支持Kotlin协程方式进行调用了。
parseAnnotations主要逻辑:
1、先通过createCallAdapter(retrofit, method, adapterType, annotations)方法拿到CallAdapter对象,实现API方法的不同返回值包装类处理逻辑。例如:getUserData()方法返回的值包装类类型Call,那返回CallAdapter对象对应DefaultCallAdapterFactory包含的Adapter;如果是Observable,那么返回的就是RxJava2CallAdapterFactory包含的Adapter。
2、通过createResponseConverter(retrofit, method, responseType)方法拿到Converter对象,Converter就用于实现API方法的不同返回值处理逻辑。例如:getUserData()返回类型ResponseBody,那么Converter对象就对应BuiltInConverters;如果是UserBean,那么对应GsonConverterFactory
根据上面两步,构造出一个CallAdapted对象并返回。
CallAdapter是HttpServiceMethod的子类,在InvocationHandler中通过loadServiceMethod(method).invoke(args)发起调用链,会先创建出一个OkHttpCall对象,并最后调用callAdapter.adapt(call)方法

OkHttpCall

是实际发起OkHttp请求的地方。当调用getUserData():Call<ResponseBody>方法时,返回的Call对象实际上是OkHttpCall类型,而当我们调用call.enqueue(callback)方法时,enqueue方法会发起一个OkHttp请求,传入的Callback对象会由okhttp3.callback本身回调进行中转调用。

总结

通过retrofit.create(ApiService::class.java)得到ApiService动态实现类,通过Java原生提供的Proxy.newProxyInstance代表的动态代理功能来实现的。拿到ApiService实现类,可以直接调用ApiService中声明的方法。
当我们调用了service.getUserData()方法,Retrofit会将每个API方法都抽象封装为一个ServiceMethod并缓存起来,操作会转交给ServiceMethod来完成,由ServiceMethod来负责返回我们的目标类型,对应的是ServiceMethod.invoke(Object[] args)方法,args代表的是我们调用的API方法时需要传递参数,对应本例是空数组。
ServiceMethod只具有唯一的子类HttpServiceMethod, 而HttpServiceMethod会invoke方法构建出一个OkHttpCall对象,然后调用其抽象方法adapt
对于不同的请求方式,ServiceMethod.parseAnnotations方法最终会返回不同的HttpServiceMethod子类。本例,最终会返回CallAdapter对象

Kotlin 协程方式来调用

依赖库:

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来启动一个协程,避免请求还未结束main线程就停止的情况。实际开发中要避免runBlocking这样使用协程。

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()不需要任何包装类了,直接声明目标数据类型就可以,使用更简洁。
每个方法用suspend关键字进行修饰,标明其只能在协程中调用。
Retrofit是以Java语言实现的,但suspend关键字只能用于Kotlin,两者存在“沟通障碍”,但只要调用方也属于JVM语言,那Retrofit就是可以使用的,通过IDEA将ApiService反编译为Java类,看下suspend函数在Retrofit的角度来看是怎样实现的

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

方法返回值类型变为Object,方法参数列表中添加了一个kotlin.coroutines.Continuation参数。
在RequestFactory类中包含了一个isKotlinSuspendFunction的boolen类型的变量,当前解析的Method是否是suspend函数。RequestFactory的build()方法中,对API方法每个参数进行解析,包含了检测当前解析参数是否属于最后一个参数的逻辑。
如果检测到最后一个参数类型是Continuation.class,那isKotlinSuspendFunction就会变为true。
API最后一个参数强转为Continuation<ResponseT>类型,调用KotlinExtensions.await(call,continuation)这个Kotlin的扩展函数
await()以suspendCancellableCoroutine这个支持cancel的CoroutineScope作为作用域,以Call.enqueue的方式发起OkHttp请求,拿到responseBody后就透传出来,完成整个调用流程。

Retrofit对Android平台

Retrofit并不需要依赖于Android平台,可以用于任意的Java客户端,Retrofit只是对Android平台进行了特殊实现。
在构建Retrofit对象时候,可以选择传递一个Platform对象用于标记调用方所处的平台
1、判断是否支持Java8,是否支持调用interface的默认方法,判断是否支持Optional和CompletableFuture要用到。因为Android应用如果支持Java8,需要Gradle文件进行主动配置,Java8在Android平台目前也支持不彻底。
2、实现main线程回调的Executor。Android平台不允许在main线程上执行耗时任务,UI操作都需要切换到main线程来完成。对于Android平台,Retroft回调网络请求结果,通过main线程执行的Executor来进行线程切换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342