安卓网络框架Retrofit封装(利用协程和flow)

398 阅读2分钟

原始的retrofit使用方式

//创建网络请求接口对象实例
    Api api = mRetrofit.create(Api.class);
    //对发送请求进行封装
    Call<Data<Info>> dataCall = api.getJsonData("xxx", "xx");

    //异步请求
    dataCall.enqueue(new Callback<Data<Info>>() {
        //请求成功回调
         @Override
         public void onResponse(Call<Data<Info>> call,  Response<Data<Info>> response) {
           }
        //请求失败回调
         @Override
         public void onFailure(Call<Data<Info>> call,   Throwable t) {         
           }
      });

    //同步请求
    Response<Data<Info>> data= dataCall.execute();

封装 RetrofitFlow.kt

  • injectApi 封装获取retrofit实例的方法
inline fun <reified T : Any> injectApi(mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED): Lazy<T> {
    return lazy(mode) {
        RetrofitFlow.getRetrofit().create(T::class.java)
    }
}

private val bapiLoginApi by injectApi<PrivateLoginApi>()
@JvmStatic
fun getRetrofit(): Retrofit {
    
    if (retrofitInstance == null) {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        val mGlobalRetrofit = Retrofit.Builder()
            .client(OkHttpClient.Builder().addInterceptor(loggingInterceptor).build())
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("todo ")
            .build()
        retrofitInstance = mGlobalRetrofit.newBuilder().client(
            OkHttpClient.Builder().addInterceptor(loggingInterceptor).build().newBuilder()
                .addNetworkInterceptor(loggingInterceptor).build()
        ).build()
    }
    return retrofitInstance!!
}
  • requestFlow 发起请求并emit结果
inline fun <reified T : Any, reified R : BaseApi> R.requestFlow(
    crossinline request: suspend R.() -> Response<BapiResponse<T>>,
): Flow<T> {
    return flow {
        var urlPath: String? = null
        try {
            val response = request()
            urlPath = try {
                response.raw().request().url().encodedPath()
            } catch (e: java.lang.Exception) {
                Log.e("retro-flow", "get request url failed", e)
                null
            }
            val bapiRes = response.body()
            if (response.code() == 200 && bapiRes != null && bapiRes.errorCode == 10000 && bapiRes.data != null) {
                emit(bapiRes.data)
            } else {

                if (response.code() == 200) {
                    throw ApiException(
                        bapiRes?.errorCode ?: response.code(),
                        bapiRes?.errorMsg ?: response.message() ?: "response error"
                    )
                } else {
                    throw HttpException()
                }
            }
        } catch (e: Exception) {
            if (e is ApiException) {
                throw e
            } else {
                throw HttpException()
            }
        }
    }.flowOn(Dispatchers.IO).onCompletion { cause ->
        run {
            cause?.let {
                Log.e("retro-flow", "request error", it)
            }
        }
    }
}
  • next collect请求结果
suspend inline fun <T> Flow<T>.next(crossinline bloc: suspend T.() -> Unit): Unit =
    catch { }.collect { bloc(it) }
  • 具体使用

image.png 确保回调在当前组件的生命周期内,防止内存泄漏

  • 源码
package com.example.stateflow

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RetrofitFlow {
    companion object {
        private var retrofitInstance: Retrofit? = null

        @JvmStatic
        fun getRetrofit(): Retrofit {
            if (retrofitInstance == null) {
                val loggingInterceptor = HttpLoggingInterceptor()
                loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
                val mGlobalRetrofit = Retrofit.Builder()
                    .client(OkHttpClient.Builder().addInterceptor(loggingInterceptor).build())
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl("todo ")
                    .build()
                retrofitInstance = mGlobalRetrofit.newBuilder().client(
                    OkHttpClient.Builder().addInterceptor(loggingInterceptor).build().newBuilder()
                        .addNetworkInterceptor(loggingInterceptor).build()
                ).build()
            }
            return retrofitInstance!!
        }

        @JvmStatic
        fun updateBaseUrl(baseUrl: String?) {
            baseUrl ?: return
            val old = retrofitInstance
            if (old != null) {
                retrofitInstance = old.newBuilder().baseUrl(baseUrl).build()
            }
        }
    }
}

inline fun <reified T : Any> injectApi(mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED): Lazy<T> {
    return lazy(mode) {
        RetrofitFlow.getRetrofit().create(T::class.java)
    }
}

suspend inline fun <T> Flow<T>.next(crossinline bloc: suspend T.() -> Unit): Unit =
    catch { }.collect { bloc(it) }

fun <T> Flow<T>.catchError(bloc: Throwable.() -> Unit) = catch { cause -> bloc(cause) }

inline fun <reified T : Any, reified R : BaseApi> R.requestFlow(
    crossinline request: suspend R.() -> Response<BapiResponse<T>>,
): Flow<T> {
    return flow {
        var urlPath: String? = null
        try {
            val response = request()
            urlPath = try {
                response.raw().request().url().encodedPath()
            } catch (e: java.lang.Exception) {
                Log.e("retro-flow", "get request url failed", e)
                null
            }
            val bapiRes = response.body()
            if (response.code() == 200 && bapiRes != null && bapiRes.errorCode == 10000 && bapiRes.data != null) {
                emit(bapiRes.data)
            } else {

                if (response.code() == 200) {
                    throw ApiException(
                        bapiRes?.errorCode ?: response.code(),
                        bapiRes?.errorMsg ?: response.message() ?: "response error"
                    )
                } else {
                    throw HttpException()
                }
            }
        } catch (e: Exception) {
            if (e is ApiException) {
                throw e
            } else {
                throw HttpException()
            }
        }
    }.flowOn(Dispatchers.IO).onCompletion { cause ->
        run {
            cause?.let {
                Log.e("retro-flow", "request error", it)
            }
        }
    }
}

inline fun <reified T : Any, reified R : BaseApi> R.requestFlowNotBapi(
    crossinline request: suspend R.() -> Response<T>,
): Flow<T> {
    return flow {
        var urlPath: String? = null
        try {
            val response = request()
            urlPath = try {
                response.raw().request().url().encodedPath()
            } catch (e: java.lang.Exception) {
                Log.e("retro-flow", "get request url failed", e)
                null
            }
            val baseRes = response.body()
            if (response.code() == 200 && baseRes != null) {
                emit(baseRes)
            } else {
                throw ApiException(response.code(), response.message() ?: "response error")
            }
        } catch (e: Exception) {
        }
    }.flowOn(Dispatchers.IO).onCompletion { cause ->
        run {
            cause?.let {
                Log.e("retro-flow", "request error", it)
            }
        }
    }
}

class ApiException(val code: Int, val msg: String) : Exception(msg)

class HttpException() : Exception()

interface BaseApi {}


data class BapiResponse<T>(
    val errno: Int? = null,
    val errmsg: String? = null,
    val data: T? = null
) {
    val errorCode: Int?
        get() {
            return errno
        }
    val errorMsg: String?
        get() {
            return errmsg
        }
}