Android 網(wǎng)絡(luò)功能搭建 (協(xié)成 + Retrofit + OkHttp)

概述

網(wǎng)絡(luò)層功能搭建孤紧,使用技術(shù): 協(xié)成 + Retrofit + Okhttp + Moshi筛圆。

一煮落、支持功能

  1. 實現(xiàn)優(yōu)雅調(diào)用網(wǎng)絡(luò)請求:支持多Domain。

  2. 網(wǎng)絡(luò)請求公共參數(shù):GET 參數(shù)怯邪、POST url 參數(shù)骂远、POST Formbody 參數(shù)趣避。

  3. 網(wǎng)絡(luò)請求唯一ID:請求時帶入尸执,完成后帶回。方便測試浸卦、bug統(tǒng)計署鸡。

  4. 統(tǒng)一接口返回回調(diào):統(tǒng)一處理錯誤碼,log打點統(tǒng)計。

  5. 去信封功能:接口返回如下的json格式靴庆, 直接解析成data對應(yīng)的model时捌。

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

二、使用方式

網(wǎng)絡(luò)請求事例

@JsonClass(generateAdapter = true)
@CropEnvelope //CropEnvelope注解即可實現(xiàn)去信封功能 (前提是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 {//協(xié)成啟動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") }
        }
}

網(wǎng)絡(luò)請求公共參數(shù)

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

統(tǒng)一接口返回回調(diào)

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")
}

三匣椰、實現(xiàn)過程

Retrofit Call Adapter

通過擴展 Retrofit Call Adapter 實現(xiàn)返回類型的包裝,統(tǒng)一接口返回回調(diào)和接口唯一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)
    }
}
//包裝數(shù)據(jù)返回給接口調(diào)用處
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 解析后再轉(zhuǎn)成包裝類型 NetworkResponse 返回
               callback.onResponse(
                        this@NetworkResponseCall,
                        Response.success(it.apply {
                            responseListener?.invoke(this)
                        })
            }
        })
    }
}
//網(wǎng)絡(luò)請求返回的包裝類,含有結(jié)果&異常信息
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>()
    
    //省略其他類型
}

網(wǎng)絡(luò)請求公共參數(shù)

使用Okhttp Interceptor 實現(xiàn)公共參數(shù)的功能蛤奥。

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 協(xié)程與 Retrofit

# Create Retrofit CallAdapter for Coroutines to handle response as states

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凡桥,一起剝皮案震驚了整個濱河市蟀伸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缅刽,老刑警劉巖啊掏,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異衰猛,居然都是意外死亡迟蜜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門啡省,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娜睛,“玉大人,你說我怎么就攤上這事卦睹∑杞洌” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵结序,是天一觀的道長障斋。 經(jīng)常有香客問我,道長徐鹤,這世上最難降的妖魔是什么配喳? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮凳干,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘被济。我一直安慰自己救赐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著经磅,像睡著了一般泌绣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上预厌,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天阿迈,我揣著相機與錄音,去河邊找鬼轧叽。 笑死苗沧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炭晒。 我是一名探鬼主播待逞,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼网严!你這毒婦竟也來了识樱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤震束,失蹤者是張志新(化名)和其女友劉穎怜庸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垢村,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡割疾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肝断。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杈曲。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胸懈,靈堂內(nèi)的尸體忽然破棺而出担扑,到底是詐尸還是另有隱情,我是刑警寧澤趣钱,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布涌献,位于F島的核電站,受9級特大地震影響首有,放射性物質(zhì)發(fā)生泄漏燕垃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一井联、第九天 我趴在偏房一處隱蔽的房頂上張望卜壕。 院中可真熱鬧,春花似錦烙常、人聲如沸轴捎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侦副。三九已至侦锯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秦驯,已是汗流浹背尺碰。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留译隘,地道東北人亲桥。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像细燎,于是被迫代替她去往敵國和親两曼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容