Kotlin - Retrofit2和Rxjava2封裝的網(wǎng)絡(luò)請求類(含圖片上傳)

文/ZYRzyr
原文鏈接:http://www.itdecent.cn/p/c66d50cd14ee

閱讀建議:本文適合熟悉RetrofitRxjava2的同學(xué)閱讀,其中也包含一丟丟的RxLifecycle,文中不包含這兩個庫的使用說明。不熟悉RxJava的同學(xué),建議去這里了解,里面包含3篇文章,均通俗易懂。

提示:文中使用的RxJava2的類均是不支持背壓的,即Observable(被觀察者)Observer(觀察者)。需要背壓策略,請自行替換為對應(yīng)的Flowable(被觀察者)Subscriber(觀察者)即可。如果想使用Kotlin的一些便利的語法,可以將RxJava依賴換成RxKotlin即可。

開始

1.按慣例先添加依賴:

//Retrofit相關(guān)
compile(['com.squareup.okhttp3:logging-interceptor:3.9.0',//用于查看http請求時的log
         'com.squareup.retrofit2:retrofit:2.3.0',
         'com.squareup.retrofit2:adapter-rxjava2:2.3.0',
         'com.squareup.retrofit2:converter-gson:2.3.0'])

//RxJava相關(guān)
compile(['io.reactivex.rxjava2:rxandroid:2.0.1',
         'io.reactivex.rxjava2:rxjava:2.1.3'])       //此處可換成'io.reactivex.rxjava2:rxkotlin:2.1.0'

//RxLifecycle相關(guān)
compile(['com.trello.rxlifecycle2:rxlifecycle-kotlin:2.2.0',
         'com.trello.rxlifecycle2:rxlifecycle-components:2.2.0'])

提示:可以去Retrofit、Rxjava2(RxAndroid)、okhttp、RxLifecycle,查詢最新版本號。

2.封裝請求類

為了秉承RxJava的鏈式調(diào)用風(fēng)格,也為了方便每一個API的調(diào)用操作,創(chuàng)建了一個單例類ApiClient,具體如下:

class ApiClient private constructor() {
    lateinit var service: GitHubService

    private object Holder {
        val INSTANCE = ApiClient()
    }

    companion object {
        val instance by lazy { Holder.INSTANCE }
    }

    fun init() {  //在Application的onCreate中調(diào)用一次即可
        val okHttpClient = OkHttpClient().newBuilder()
                 //輸入http連接時的log,也可添加更多的Interceptor
                .addInterceptor(HttpLoggingInterceptor().setLevel( 
                        if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
                        else HttpLoggingInterceptor.Level.NONE
                ))
                .build()

        val retrofit = Retrofit.Builder()
                .baseUrl("https://api.github.com/")   //本文以GitHub API為例
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build()

        service = retrofit.create(GitHubService::class.java)
    }
}

其中使用GitHub的的API作為測試,GitHubService如下:

interface GitHubService {
//請?zhí)砑酉鄳?yīng)的`API`調(diào)用方法
    @GET("users/{user}/repos")
    fun listRepos(@Path("user") user: String): Observable<List<Repo>> //每個方法的返回值即一個Observable
}

上面的Repo即一個簡單的Kotlin數(shù)據(jù)類,由于字較多,就不貼出來了,具體可去文末Demo地址查找。

3.RESTful API請求響應(yīng)的處理

API的響應(yīng)返回形式有很多種,此處介紹最常見的兩種形式的處理:標準RESTful API任性的后端寫的API。GitHub提供的API即標準RESTful API
RESTful API的請求響應(yīng)主要處理狀態(tài)碼與數(shù)據(jù)體,具體封裝如下:

abstract class ApiResponse<T>(private val context: Context) : Observer<T> {
    abstract fun success(data: T)
    abstract fun failure(statusCode: Int, apiErrorModel: ApiErrorModel)

    override fun onSubscribe(d: Disposable) {
        LoadingDialog.show(context)
    }

    override fun onNext(t: T) {
        success(t)
    }

    override fun onComplete() {
        LoadingDialog.cancel()
    }

    override fun onError(e: Throwable) {
        LoadingDialog.cancel()
        if (e is HttpException) { //連接服務(wù)器成功但服務(wù)器返回錯誤狀態(tài)碼
            val apiErrorModel: ApiErrorModel = when (e.code()) {
                ApiErrorType.INTERNAL_SERVER_ERROR.code ->
                    ApiErrorType.INTERNAL_SERVER_ERROR.getApiErrorModel(context)
                ApiErrorType.BAD_GATEWAY.code ->
                    ApiErrorType.BAD_GATEWAY.getApiErrorModel(context)
                ApiErrorType.NOT_FOUND.code ->
                    ApiErrorType.NOT_FOUND.getApiErrorModel(context)
                else -> otherError(e)

            }
            failure(e.code(), apiErrorModel)
            return
        }

        val apiErrorType: ApiErrorType = when (e) {  //發(fā)送網(wǎng)絡(luò)問題或其它未知問題,請根據(jù)實際情況進行修改
            is UnknownHostException -> ApiErrorType.NETWORK_NOT_CONNECT
            is ConnectException -> ApiErrorType.NETWORK_NOT_CONNECT
            is SocketTimeoutException -> ApiErrorType.CONNECTION_TIMEOUT
            else -> ApiErrorType.UNEXPECTED_ERROR
        }
        failure(apiErrorType.code, apiErrorType.getApiErrorModel(context))
    }

    private fun otherError(e: HttpException) =
            Gson().fromJson(e.response().errorBody()?.charStream(), ApiErrorModel::class.java)
}

說明:
1.每個響應(yīng)繼承Observer,其中的泛型以適配返回的不同的數(shù)據(jù)體;
2.定義兩個抽象方法successfailure,在使用的時候只需關(guān)注成功和失敗這兩種情況;
3.在onSubscribe即開始請求的時候顯示Loading,在請求完成或出錯時隱藏;
4.在onNextObserver成功接收數(shù)據(jù)后直接調(diào)用success,在調(diào)用處可直接使用返回的數(shù)據(jù);
5.在onError即請求出錯時處理,此處包含兩種情況:連接服務(wù)器成功但服務(wù)器返回錯誤狀態(tài)碼、網(wǎng)絡(luò)或其它問題。

在錯誤處理中,定義了一個枚舉類ApiErrorType,用于列舉出服務(wù)器定義的錯誤狀態(tài)碼情況:

enum class ApiErrorType(val code: Int, @param: StringRes private val messageId: Int) {
//根據(jù)實際情況進行增刪
    INTERNAL_SERVER_ERROR(500, R.string.service_error), 
    BAD_GATEWAY(502, R.string.service_error),
    NOT_FOUND(404, R.string.not_found),
    CONNECTION_TIMEOUT(408, R.string.timeout),
    NETWORK_NOT_CONNECT(499, R.string.network_wrong),
    UNEXPECTED_ERROR(700, R.string.unexpected_error);

    private val DEFAULT_CODE = 1

    fun getApiErrorModel(context: Context): ApiErrorModel {
        return ApiErrorModel(DEFAULT_CODE, context.getString(messageId))
    }
}

還定義了一個錯誤消息的的實體類ApiErrorModel(在Kotlin中即為一個數(shù)據(jù)類),用于包含錯誤信息提示用戶或服務(wù)器返回的錯誤信息以提示開發(fā)人員:

data class ApiErrorModel(var status: Int, var message: String)

4.線程與生命周期

RxJava的一大特色即方便的線程切換操作,在請求API中需要進行線程的切換,通常是以下形式(偽代碼):

observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

但每個請求都寫一段這個,顯得特別麻煩,所以進行以下簡單封裝:

object NetworkScheduler {
    fun <T> compose(): ObservableTransformer<T, T> {
        return ObservableTransformer { observable ->
            observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }
}

使用的時候簡單搞定,偽代碼如下:

observable.compose(NetworkScheduler.compose())

Android中,當一個Activity在調(diào)APIonDestroy了,需要取消請求,所以此處引入了RxLifecycle進行管理:
Activity繼承RxAppCompatActivity后,在observable的調(diào)用鏈中加入.bindUntilEvent(this, ActivityEvent.DESTROY)即可,偽代碼如下:

observable.compose(NetworkScheduler.compose())
          .bindUntilEvent(this, ActivityEvent.DESTROY)  //加入這句
          .subscribe(...)

5.使用

以上準備工作完成后,即可開始使用:
首先在Application中初始化ApiClient

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        ApiClient.instance.init() //這里
    }
}

在需要的地方使用ApiClient,如本文Demo,點擊按鈕時,請求數(shù)據(jù),成功后用TextView顯示出來:

class MainActivity : RxAppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        submit.setOnClickListener { fetchRepo() } //按鈕點擊事件
    }

    private fun fetchRepo() {
        //鏈式調(diào)用
        ApiClient.instance.service.listRepos(inputUser.text.toString())   //GitHubService中的方法
                .compose(NetworkScheduler.compose())                      //線程切換處理
                .bindUntilEvent(this, ActivityEvent.DESTROY)              //生命周期管理
                .subscribe(object : ApiResponse<List<Repo>>(this) {       //對象表達式約等于Java中的匿名內(nèi)部類 
                    override fun success(data: List<Repo>) {              //請求成功,此處顯示一些返回的數(shù)據(jù)
                        userName.text = data[0].owner.login
                        repoName.text = data[0].name
                        description.text = data[0].description
                        url.text = data[0].html_url
                    }

                    override fun failure(statusCode: Int, apiErrorModel: ApiErrorModel) { //請求失敗,此處直接顯示Toast
                        Toast.makeText(this@MainActivity, apiErrorModel.message, Toast.LENGTH_SHORT).show()
                    }
                })
    }
}

效果如下:


效果.gif

6.任性的后端寫的API請求響應(yīng)的處理

這種情況只需要對數(shù)據(jù)類和響應(yīng)處理進行修改即可。有些后端開發(fā)者們,可能將返回體寫成如下形式:

{
    "code": "200",
    "data": [
        {
            "name": "Tom",
            "age": 12,
            "money": 100.5
        },
        {
            "name": "Bob",
            "age": 13,
            "money": 200.5
        }
    ],
    "message": "客戶端請求成功"
}

所有返回的數(shù)據(jù)中,最外層都包裹了一層信息,以表示請求成功或失敗,中間data才是具體數(shù)據(jù),所以定義數(shù)據(jù)類(實體類)時,需要定義成如下形式:

data class ResponseWrapper<T>(var code: Int, var data: T, var message: String)

其中data為泛型,以適配不同的數(shù)據(jù)體。
然后將上文第3點中的ApiResponse修改如下:

abstract class RequestCallback<T>(private val context: Context) : Observer<ResponseWrapper<T>> {
    abstract fun success(data: T)
    abstract fun failure(statusCode: Int, apiErrorModel: ApiErrorModel)

    private object Status {
        val SUCCESS = 200
    }

    override fun onSubscribe(d: Disposable) {
        LoadingDialog.show(context)
    }

    override fun onNext(t: ResponseWrapper<T>) {
        if (t.code == Status.SUCCESS) {
            success(t.data)
            return
        }

        val apiErrorModel: ApiErrorModel = when (t.code) {
            ApiErrorType.INTERNAL_SERVER_ERROR.code ->
                ApiErrorType.INTERNAL_SERVER_ERROR.getApiErrorModel(context)
            ApiErrorType.BAD_GATEWAY.code ->
                ApiErrorType.BAD_GATEWAY.getApiErrorModel(context)
            ApiErrorType.NOT_FOUND.code ->
                ApiErrorType.NOT_FOUND.getApiErrorModel(context)
            else -> ApiErrorModel(t.code, t.message)
        }
        failure(t.code, apiErrorModel)
    }

    override fun onComplete() {
        LoadingDialog.cancel()
    }

    override fun onError(e: Throwable) {
        LoadingDialog.cancel()
        val apiErrorType: ApiErrorType = when (e) {
            is UnknownHostException -> ApiErrorType.NETWORK_NOT_CONNECT
            is ConnectException -> ApiErrorType.NETWORK_NOT_CONNECT
            is SocketTimeoutException -> ApiErrorType.CONNECTION_TIMEOUT
            else -> ApiErrorType.UNEXPECTED_ERROR
        }
        failure(apiErrorType.code, apiErrorType.getApiErrorModel(context))
    }
}

使用方式:
1.先在GitHubService.kt中新增如下方法:

@GET("xxx/xxx")
fun repos(@Path("user") user: String): Observable<ResponseWrapper<List<Repo>>>

2.之后與上文第5點相同。

2017年10月13日更新—增加上傳圖片的方法
新增OkHttpUtil.kt,用于上傳圖片,代碼如下:

object OkHttpUtil {
    fun createTextRequestBody(source: String): RequestBody
            = RequestBody.create(MediaType.parse("text/plain"), source)

    fun createPartWithAllImageFormats(requestKey: String, file: File): MultipartBody.Part
            = MultipartBody.Part
            .createFormData(requestKey, file.name, RequestBody.create(MediaType.parse("image/*"), file))
}

使用方式:
1.先在GitHubService.kt中新增如下方法:

@Multipart
@POST("xxxx/xxxx") //This is imaginary URL
fun updateImage(@Part("name") name: RequestBody,
                @Part image: MultipartBody.Part): Observable<UserInfo>

2.在需要的地方使用:

ApiClient.instance.service.updateImage(OkHttpUtil.createTextRequestBody("Bob"),
                 OkHttpUtil.createPartWithAllImageFormats("avatar",file))   //此處調(diào)用OkHttpUtil中的方法
                .compose(NetworkScheduler.compose())
                .bindUntilEvent(this,ActivityEvent.DESTROY)
                .subscribe(object : ApiResponse<UserInfo>(this) {
                    override fun success(data: UserInfo) {
                        //Do something
                    }

                    override fun failure(statusCode: Int, apiErrorModel: ApiErrorModel) {
                        //Do something
                    }
                })



最后
希望本文對您有所幫助。如果文中有什么表述不當?shù)牡胤剑堅谙路皆u論,以幫助我改正。

Demo地址:https://github.com/ZYRzyr/ApiClient (歡迎Star和Follow)

原文作者/ZYRzyr
原文鏈接:http://www.itdecent.cn/p/c66d50cd14ee

請進入這里獲取授權(quán):https://101709080007647.bqy.mobi

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • 成功者之所以說堅持等于勝利,這是因為他已經(jīng)是過來人,他們是有發(fā)言權(quán)的。但對大多數(shù)普通人來說,前途既是未知,可能咬著...
    未文啊閱讀 165評論 0 0
  • 親子日記第四十二天,今天是農(nóng)歷五月二十八,是兒子的生日,上午去蛋糕店做了一個蛋糕送到兒子幼兒園,老師小朋友...
    AA穩(wěn)穩(wěn)閱讀 317評論 0 4
  • 在和學(xué)生討論民國四大才女的時候,學(xué)生問我為什么張愛玲留下的作品最多、最出名。我戲言因為張愛玲活得年紀最大啊...
    邰楓的臺閱讀 700評論 0 2

友情鏈接更多精彩內(nèi)容