Retrofit+LiveData+Java 进行网络请求

之前一直在使用 Kotlin 写 Retrofit,一系列 api 封装太好了,我甚至不知道这堆语法糖和 api 展开后是什么。搜索资料和看了点 demo 后,写了一篇 Java 版本的 Retrofit+LiveData,现在才感觉自己略懂。看来 Kotlin First 还是得建立在完全理解原有的Java框架的基础上啊。

概览

Retrofit 和 LiveData 结合的一种实现方法

  1. 构建 Retrofit 对象时,添加一个转换适配器工厂 CallAdapterFactory

  2. CallAdapterFactory 生产的适配器 CallAdapter 需要重写 responseType()adapt() 两个方法

    • responseType()

      用于返回从GSON数据到JAVA对象的类型

    • adapt()

      该方法用于将 Retrofit 请求返回时的 Call 对象转换为需要的类,这里为我们自定义的 LiveData 对象,为了后续监听网络回调,这里 CallAdapterFactory 构建 CallAdapter 时需要传递当前 Call 实例

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

推荐阅读更多精彩内容