Android 网络功能搭建 (协成 + Retrofit + OkHttp)

概述

网络层功能搭建,使用技术: 协成 + Retrofit + Okhttp + Moshi。

一、支持功能

  1. 实现优雅调用网络请求:支持多Domain。

  2. 网络请求公共参数:GET 参数、POST url 参数、POST Formbody 参数。

  3. 网络请求唯一ID:请求时带入,完成后带回。方便测试、bug统计。

  4. 统一接口返回回调:统一处理错误码,log打点统计。

  5. 去信封功能:接口返回如下的json格式, 直接解析成data对应的model。

{
  "errorCode": 0,
  "errorMsg": "",
  "data": {
    "title": "this is title"
  }
}

二、使用方式

网络请求事例

@JsonClass(generateAdapter = true)
@CropEnvelope //CropEnvelope注解即可实现去信封功能 (前提是Response返回类型)
data class WanAndroidCropBanner(
    @Json(name = "title")
    val title: String
)

interface WanAndroidService {
    @GET("/banner/json")
    @ID(ReqId.ID_1) //唯一ID, ID 是自定义注解
    suspend fun banner(): Response<WanAndroidBanner> //Response 是自定义类型NetworkResponseCall的别名 
}

class WanAndroidRepository : NetworkRepository<WanAndroidService>(
    WanAndroidService::class.java,
    baseUrl = "https://wanandroid.com/"
) {
    suspend fun cropBanner() = service.cropBanner()
}

viewModelScope.launch {//协成启动supend方法
    WanAndroidRepository()
        .cropBanner()
        .onFailure { error ->
            _uiState.update { it.copy(name = "${error.status} ${error.id}") }
        }.onSuccess { banners ->
            _uiState.update { it.copy(name = banners.firstOrNull()?.title ?: "empty") }
        }
}

网络请求公共参数

Network.networkParameterAdapter = object : NetworkParameterAdapter {
    //GET 参数
    override fun getGetParameter(request: Request): Map<String, String> = mutableMapOf()
    //POST url 参数 (url?key=value 项目中总是有奇奇怪怪的需求)
    override fun getPostQueryParameter(request: Request): Map<String, String> = mutableMapOf()
    //POST Formbody 参数
    override fun getPostFieldParameter(request: Request): Map<String, String> = mutableMapOf()
}

统一接口返回回调

Network.responseListener.add {
    val error = when (it) {
        is NetworkResponse.Success -> "Success"
        is NetworkResponse.BizError -> "BizError ${it.msg}"
        is NetworkResponse.ApiError -> "ApiError"
        is NetworkResponse.NetworkError -> "NetworkError  ${it.error.message}"
        is NetworkResponse.UnknownError -> "UnknownError  ${it.error?.message}"
    }
    println("response listener $error")
}

三、实现过程

Retrofit Call Adapter

通过扩展 Retrofit Call Adapter 实现返回类型的包装,统一接口返回回调和接口唯一ID功能。

class NetworkResponseAdapterFactory(
    private val responseListener: ((response: NetworkResponse<Any, Any>) -> Unit)? = null
) : CallAdapter.Factory() {

    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        //检查是否是要处理的类型,不是返回null, 是返回 NetworkResponseAdapter
        if (getRawType(responseType) != NetworkResponse::class.java) return null
        return NetworkResponseAdapter<Any, Any>(responseType, errorBodyConverter, responseListener, id)
    }
}
class NetworkResponseAdapter<S : Any, E : Any>(
    private val successType: Type,
    private val errorBodyConverter: Converter<ResponseBody, E>,
    private val responseListener: ((response: NetworkResponse<Any, Any>) -> Unit)? = null,
    private val id: String = ""
) : CallAdapter<NetworkResponse<S, E>, Call<NetworkResponse<S, E>>> {

    //Converter 序列化的类型
    override fun responseType(): Type = successType

    override fun adapt(call: Call<NetworkResponse<S, E>>): Call<NetworkResponse<S, E>> {
        //返回自定义的call
        return NetworkResponseCall(call, errorBodyConverter, responseListener, id)
    }
}
//包装数据返回给接口调用处
internal class NetworkResponseCall<S : Any, E : Any>(
    private val delegate: Call<NetworkResponse<S, E>>,
    private val errorConverter: Converter<ResponseBody, E>,
    private val responseListener: ((response: NetworkResponse<Any, Any>) -> Unit)? = null,
    private val id: String = ""
) : Call<NetworkResponse<S, E>> {
    
    override fun enqueue(callback: Callback<NetworkResponse<S, E>>) {
        return delegate.enqueue(object : Callback<NetworkResponse<S, E>> {
            override fun onResponse(
                call: Call<NetworkResponse<S, E>>,
                response: Response<NetworkResponse<S, E>>
            ) {
               //省略处理代码
               //Moshi 解析后再转成包装类型 NetworkResponse 返回
               callback.onResponse(
                        this@NetworkResponseCall,
                        Response.success(it.apply {
                            responseListener?.invoke(this)
                        })
            }
        })
    }
}
//网络请求返回的包装类,含有结果&异常信息
sealed class NetworkResponse<out T : Any, out U : Any>(open val id: String = "") {
    data class Success<T : Any>(val data: T, override val id: String = "") : NetworkResponse<T, Nothing>()

    data class BizError(val code: Int, val msg: String, override val id: String = "") : NetworkResponse<Nothing, Nothing>()NetworkResponse<Nothing, Nothing>()
    
    //省略其他类型
}

网络请求公共参数

使用Okhttp Interceptor 实现公共参数的功能。

class NetworkParameterInterceptor(private val networkParameterAdapter: NetworkParameterAdapter) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        val urlBuilder = request.url.newBuilder()
        when (request.method) {
            "GET" -> {
                val parameter = networkParameterAdapter.getGetParameter(request)
                parameter.forEach {
                    urlBuilder.addQueryParameter(it.key, it.value)
                }
                request.newBuilder().url(urlBuilder.build()).build()
            }
            "POST" -> {
                val queryParameter = networkParameterAdapter.getPostQueryParameter(request)
                queryParameter.forEach {
                    urlBuilder.addQueryParameter(it.key, it.value)
                }
                request = request.newBuilder().url(urlBuilder.build()).build()
                if (request.body is FormBody) {
                    val builder = FormBody.Builder()
                    val body = request.body as FormBody
                    for (i in 0 until body.size) {
                        builder.add(body.encodedName(i), body.encodedValue(i))
                    }
                    val fieldParameter = networkParameterAdapter.getPostFieldParameter(request)
                    fieldParameter.forEach {
                        builder.add(it.key, it.value)
                    }
                    request = request.newBuilder().post(builder.build()).build()
                }
            }
        }
        return chain.proceed(request)
    }
}

去信封功能

自定义Moshi json 解析器,Moshi 解析完成后返回给 NetworkResponseCall 处理。

//Moshi JsonAdapter.Factory
class NetworkMoshiAdapterFactory : JsonAdapter.Factory {
    override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
        if (Utils.getRawType(type) != NetworkResponse::class.java) return null
        val responseType = getParameterUpperBound(0, type as ParameterizedType)
        val dataTypeAdapter = moshi.nextAdapter<Any>(
            this, responseType, annotations
        )
        return NetworkCropResponseTypeAdapter(responseType, dataTypeAdapter)
    }
}

//Moshi 去信封的 JsonAdapter
class NetworkCropResponseTypeAdapter<T>(
    private val outerType: Type,
    private val dataTypeAdapter: JsonAdapter<T>
) : JsonAdapter<T>() {
    override fun fromJson(reader: JsonReader): T? {
        reader.beginObject()
        var data: Any? = null
        var errorCode = 0
        var errorMsg = ""
        var dataException: Exception? = null
        while (reader.hasNext()) {
            when (reader.nextName()) {
                "data" -> data = dataTypeAdapter.fromJson(reader)
                "errorCode" -> errorCode = reader.nextInt()
                "errorMsg" -> errorMsg = reader.nextString()
                else -> reader.skipValue()
            }
        }
        reader.endObject()
        
        //省略处理代码
        return NetworkResponse.Success(data) as? T
    }
}

样例代码

# AF Github

参考资料

# Kotlin 协程与 Retrofit

# Create Retrofit CallAdapter for Coroutines to handle response as states

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

推荐阅读更多精彩内容