Android搭建應用框架系列之Retrofit封裝

前言

當我們要從零去搭建一個自己的應用框架時 。做爲2017Android程序員的我,就會把Kotlin+Retrofit+MVP+RX系列拿的去實戰。整體框架模式構思好後,那就得想想大概實現的步驟。說到這裏,就得整理下應用大概有哪些東西了。

應用模塊總結.png

目前個人能想到的也就這些,這樣就有個引導的步驟和思路了。所以寫了下面幾篇文章

Android搭建應用框架系列之Retrofit封裝
Android搭建應用框架系列之MVP封裝
Android搭建應用框架系列之RxBus
Android搭建應用框架系列之ORM數據庫
Android搭建應用框架系列之Glide
Android搭建應用框架系列之BaseActivity
Android搭建應用框架系列之StatusView

也算自己給自己的的一些總結,具體代碼參考GoachFrame-Github

接下來,就先從網絡層Retrofit+OkHttp說起,記得以前自己寫過一篇Retrofit的博客,學會Retrofit+OkHttp+RxAndroid三劍客的使用,讓自己緊跟Android潮流的步伐,但返回的數據沒有結合RxJava來使用,所以這裏重新來寫下。

思路

我們都知道,實現一個Retrofit大概需要下面的幾個步驟
1. 配置一個OkHttp對象
2. 配置BaseUrl
3. 需要返回Obserable對象就配置RxJava2CallAdapterFactory
4. 需要Gson解析就配置GsonConverterFactory

同時一個Retrofit對象最好對應一個BaseUrl。本着封裝變化的原則,仔細相想,這裏也就OkHttp是變化的,BaseUrl可以通過參數傳入,然後RxJavaCallAdapterFactoryGsonConverterFactory直接配置,另外GsonConverterFactory可以傳入一個Gson對象,來統一處理返回JSON數據。其中Retrofit創建通過一個單例來實現,自定義的OkHttp對象可在Application裏面注入,也可以直接傳默認實現的OkHttp對象

OkHttp

  • 先在build.gradle添加依賴庫
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.3-2'
implementation 'com.android.support:appcompat-v7:26.+'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2::2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'com.jakewharton.timber:timber:4.5.1'

其中okhttp3:logging庫是下面做http請求攔截器使用,TimberLog封裝庫

  • 創建OkHttp對象的接口IClient
interface IClient {
    fun getClient():OkHttpClient
}

抽象一個getClient方法供外部自己配置。

  • 接下來設置一個默認配置的OkHttpDefaultOkHttpClient
class DefaultOkHttpClient:IClient {
    private var mConnectionTimeOut = Consts.CONNECTION_TIMEOUT
    private var mWriteTimeOut = Consts.CONNECTION_TIMEOUT
    private var mReadTimeOut = Consts.CONNECTION_TIMEOUT
    private var isRetryOnConnectionFailure = Consts.IS_RETRY_ON_CONNECTION_FAILURE
    private var mCookieJar = getCookieJar()
    private var mInterceptors : Array<Interceptor> = emptyArray()
    override fun getClient(): OkHttpClient {
        return OkHttpClient()
                .newBuilder()
                .connectTimeout(mConnectionTimeOut,TimeUnit.SECONDS)
                .writeTimeout(mWriteTimeOut,TimeUnit.SECONDS)
                .readTimeout(mReadTimeOut,TimeUnit.SECONDS)
                .retryOnConnectionFailure(isRetryOnConnectionFailure)
                .cookieJar(mCookieJar)
                .addInterceptors(mInterceptors)
                .build()
    }

    fun getCookieJar():CookieJar{
        return object:CookieJar{
            var mCookieStore:MutableMap<String,MutableList<Cookie>> = mutableMapOf()
            override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
                mCookieStore.put(url.host(),cookies)
            }

            override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
                val cookies = mCookieStore[url.host()]
                return cookies?: mutableListOf()
            }
        }
    }
    fun OkHttpClient.Builder.addInterceptors(mInterceptors : Array<Interceptor>):OkHttpClient.Builder{
        if(mInterceptors.isNotEmpty()){
            mInterceptors.forEach {
                this.addInterceptor(it)
            }
        }
        return this
    }

    fun setConnectionTimeOut(time:Long):DefaultOkHttpClient{
        this.mConnectionTimeOut = time
        return this
    }
    fun setWriteTimeOut(time:Long):DefaultOkHttpClient{
        this.mWriteTimeOut = time
        return this
    }

    fun setReaderTimeOut(time:Long):DefaultOkHttpClient{
        this.mReadTimeOut = time
        return this
    }
    fun isRetryOnConnectionFailure(isRetry:Boolean):DefaultOkHttpClient{
        this.isRetryOnConnectionFailure = isRetry
        return this
    }
    fun setCookieJar(cookieJar:CookieJar):DefaultOkHttpClient{
        this.mCookieJar = cookieJar
        return this
    }
    fun setInterceptors(interceptors:Array<Interceptor>):DefaultOkHttpClient{
        this.mInterceptors = interceptors
        return this
    }
}

上面的getCookieJar方法可以實現在發送請求時候,CookieJar方法會回調loadForRequestcookie加入request header裏面,在請求響應的時候,Cookjar會回調saveFromResponse方法,從而讀取response header裏面的cookie。這是隻是簡單的配置下,這裏暫時沒用到cookie的使用,在注入的時候還可以進一步cookie持久化和保存在本地,比如實現用戶的自動登錄功能。

上面還提供了請求時間的配置和攔截器的配置,這樣就可以在Application注入的時候進一步配置

GsonConverterFactory

創建Retrofit的時候,我們還需要傳入GsonConverterFactory,做爲請求返回json使用Gson來使用,其中GsonConverterFactory可以傳入一個Gson對象,統一做一些數據的序列化和反序列化數據處理。其中TypeAdapter是同時對數據進行序列化處理和反序列化處理,或者單獨的通過JsonSerializer進行序列化,以及JsonDeserializer反序列化,如下

class GsonConverter {
    fun <T> createGson():Gson{
        return GsonBuilder()
                .registerTypeAdapter(String::class.java, NullStringAdapter())
                .registerTypeAdapter(Long::class.java, LongDeserializer())
                .registerTypeAdapter(Double::class.java, DoubleDeserializer())
                .registerTypeAdapter(Date::class.java, DateSerializer())
                .registerTypeAdapter(Date::class.java, DateDeserializer())
                .registerTypeAdapter(ResponseWrapper::class.java,ResponseWrapperDeserializer<T>())
                .create()
    }
}

創建GsonBuilder然後通過registerTypeAdapter注入需要處理的一些 TypeAdapter或者JsonSerializerJsonDeserializer等等

private class NullStringAdapter : TypeAdapter<String>() {

    override fun read(reader: JsonReader): String {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull()
            return ""
        }

        return reader.nextString()
    }

    override fun write(writer: JsonWriter, value: String?) {
        if (value == null) {
            writer.nullValue()
            return
        }

        writer.value(value)
    }
}

NullStringAdapterString類型的NULL轉換爲”“,

private class DateSerializer : JsonSerializer<Date> {
    override fun serialize(src: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
        return if (src == null) null else JsonPrimitive(src.time / 1000)
    }
}

Date數據類型序列化的時間戳轉換到精確到秒

private class DateDeserializer : JsonDeserializer<Date> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Date? {
        return if (json == null || json.asLong == 0L) null else Date(json.asLong * 1000)
    }
}

Date數據類型反序列化的時間戳轉換到精確到毫秒

private class DoubleDeserializer : JsonDeserializer<Double> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Double {
        return if (json == null || TextUtils.isEmpty(json.asString)) 0.0 else json.asDouble
    }
}

Double數據類型反序列化JSON返回NULL或者爲空的時候返回默認值

private class LongDeserializer : JsonDeserializer<Long> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Long {
        return if (json == null || TextUtils.isEmpty(json.asString)) 0 else json.asLong
    }
}

Long數據類型反序列化JSON返回NULL或者爲空的時候返回默認值

private class ResponseWrapperDeserializer<T>:JsonDeserializer<ResponseWrapper<T>>{
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ResponseWrapper<T> {
        val jsonObj = json.asJsonObject
        val code = jsonObj.get("code").asInt
        val msg = jsonObj.get("msg").asString
        val version = jsonObj.get("version").asString
        val timestamp = jsonObj.get("timestamp").asLong
        val data = context.deserialize<T>(jsonObj.get("data"), (typeOfT as ParameterizedType).actualTypeArguments[0])
        return ResponseWrapper(code,msg,version,timestamp,data)
    }
}

這個主要是處理Java在編譯的時候會擦除泛型,如果不處理,在Obserable的時候就無法傳入泛型了。其中ResponseWrapper處理JSON的第一層統一樣式,比如這裏的

{
"code":0,
"msg":"",
"version":"",
"timestamp",12324334,
"data":T
}

其中上面的data可以是對象,也可以是數組,所以這裏我們返回的時候就可以用泛型傳入,ResponseWrapper如下

data class ResponseWrapper<out T>(val code:Int = -1,
                                  val msg:String = "",
                                  val version:String = "",
                                  val timestamp:Long = 0,
                                  @Transient val data: T): Serializable

Retrofit

OkHttpGson準備好後,接下來就可以創建Retrofit對象了。通過單例實現

object ApiService {
    private var mIClient:IClient = DefaultOkHttpClient()
    private val mRetrofitMap:MutableMap<String,Retrofit> = mutableMapOf()
    fun <T> get(baseUrl: String, service: Class<T>): T {
        return this.getRetrofit<T>(baseUrl).create(service)
    }

    private fun <T> getRetrofit(baseUrl: String):Retrofit{
        if(baseUrl.isEmpty()){
            throw IllegalArgumentException("baseUrl can not be empty")
        }
        if(mRetrofitMap[baseUrl] != null){
            return mRetrofitMap[baseUrl]!!
        }
        val mRetrofit = Retrofit.Builder()
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(GsonConverter().createGson<T>()))
                .client(mIClient.getClient()).build()
        mRetrofitMap.put(baseUrl,mRetrofit)
        return mRetrofit
    }
    fun registerClient(client:IClient){
        this.mIClient = client
    }

}

IClient是傳入的OkHttp對象,默認實例化DefaultOkHttpClient;
mRetrofitMap保存傳入的BaseUrl對應的Retrofit對象,實現一一對應的關係;
get方法供外界調用傳入BaseUrl和接口Service,以及Response轉換的data對應的bean;
getRetrofit方法,當通過在mRetrofitMap找不到Retrofit對象的時候,就創建Retrofit對象。同時保存;
registerClient供在Application裏面注入自定義的OkHttp對象

注入

準備好後接下來就是在ApplicationonCreate方法裏面注入OkHttp實現一些攔截器

ApiService
        .registerClient(DefaultOkHttpClient()
              .setInterceptors(arrayOf(
               OkHttpLogInterceptor().getInterceptor(),
               BasicParamsInterceptor().getInterceptor(),
               ResponseInterceptor().getInterceptor())))

其中OkHttpLogInterceptor是通過上面說的 okhttp3.logging庫創建的,主要是攔截http請求日誌

class OkHttpLogInterceptor:IInterceptor {
    override fun getInterceptor(): Interceptor {
        val mHttpLogInter = HttpLoggingInterceptor{
            message ->
            Timber.d("HttpLogging=====$message")
        }
        mHttpLogInter.level = HttpLoggingInterceptor.Level.BODY
        return mHttpLogInter
    }
}

BasicParamsInterceptor是傳入一些公共的參數,可以自己在注入的時候傳入,也可使用默認的一套公共參數

class BasicParamsInterceptor(val mQueryParameters : MutableMap<String,String>? = null):IInterceptor {
    override fun getInterceptor(): Interceptor {
        return Interceptor { chain ->
            val originalRequest = chain.request()
            val originalHttpUrl = originalRequest.url()
            val newUrl = originalHttpUrl
                    .newBuilder()
                    .addQueryParameters(mQueryParameters?:defaultBaseParameters(originalHttpUrl))
                    .build()
            val newRequest = originalRequest
                    .newBuilder()
                    .url(newUrl)
                    .method(originalRequest.method(),originalRequest.body())
                    .build()
            chain.proceed(newRequest)
        }
    }
    fun HttpUrl.Builder.addQueryParameters(mQueryParameters : MutableMap<String,String>):HttpUrl.Builder{
        if(mQueryParameters.isNotEmpty()){
            mQueryParameters.forEach {
                this.addQueryParameter(it.key,it.value)
            }
        }
        return this
    }
    fun defaultBaseParameters(originalHttpUrl:HttpUrl):MutableMap<String,String>{
        return mutableMapOf("version" to Consts.API_VERSION,
                            "platform" to Consts.API_PLATFORM,
                            "methodName" to originalHttpUrl.encodedPath().split("/").last(),
                            "token" to "")
    }
}

ResponseInterceptor是一些異常code處理,

class ResponseInterceptor(val handlerResponseException:((response:Response)->Unit)?=null):IInterceptor {
    override fun getInterceptor(): Interceptor {
        return Interceptor {
            chain ->
            val response = chain.proceed(chain.request())
            handlerResponseException?.invoke(response)
            when(response.code()){
                200 -> response
                10001 -> throw TokenExpiredException(response.code(), response.message())
                else -> throw RequestException(response.code(), response.message())
            }
        }
    }
}

其他的code處理都可以在這裏處理,或者結合RxBus進行進一步的操作。

接口

定義一個接口

interface CommService {
    @FormUrlEncoded
    @POST("ArticleList")
    fun articleList(@Field("page") page:Int = 0,@Field("size") size:Int = 15,@Field("id") id:Long): Observable<ResponseWrapper<ArticleListResponse>>
}

注意,返回的bean裏面不能用泛型,否則會報錯,這裏ArticleListResponsejson返回的數據,這裏只是隨便定義一些數據。

open class BaseResponse:Serializable

open class PageResponse : BaseResponse() {
    var page = 0
    var size = 0
    var total = 0
}

class ArticleListResponse : PageResponse() {
    val data: List<Item> = emptyList()

    class Item(
            val id: Long,
            val title: String,
            val image: String,
            val desp: String,
    ) : Serializable
}

接下來提供一個AppModel提交請求

object AppModel {
    fun articleList(pageInfo: PageInfo,id:Long = 0):Observable<ArticleListResponse>{
        return ApiClient(CommService::class.java).articleList(pageInfo.page,pageInfo.size,id).responseWrapperLogic()
    }
    fun <T> ApiClient(service: Class<T>):T{
        return ApiService.get(BuildConfig.BASE_URL,service)
    }
    private fun <T> Observable<ResponseWrapper<T>>.responseWrapperLogic() =
            map { it.data}.compose{it.subscribeOn(Schedulers.io())}.observeOn(AndroidSchedulers.mainThread())
}

這裏只是隨便定義接口方法而已,可以根據自己相應的接口添加。

使用

最簡單調用

AppModel.articleList(page,1).subscribeWith ({},{}).bindTo(mvpView.sub)

接下來可以進一步結合compose來寫請求的加載框或者其他加載動畫。其中subscribeWith是定義的一個DisposableObserver<T>bindTo是結合 CompositeDisposable一起綁定多個Disposable,後面可以更好的管理綁定和解綁

fun <T> Observable<T>.subscribeWith(onNext:((res: T) ->Unit)?=null,
                                    onError:((e: Throwable) ->Unit)?=null,
                                    onComplete:(() ->Unit)?=null):DisposableObserver<T>{
    return this.subscribeWith(object : DisposableObserver<T>() {
        override fun onError(e: Throwable) {
            if(onError!=null) onError(e)
        }
        override fun onComplete() {
            if(onComplete!=null) onComplete()
        }

        override fun onNext(res: T) {
            if(onNext!=null) onNext(res)
        }
    })
}
fun  <T> DisposableObserver<T>.bindTo(sub: CompositeDisposable) {
    sub.add(this)
}

mvpView.sub是在BasePresenter裏面定義的CompositeDisposable對象,在下一篇Android搭建應用框架系列之MVP封裝進一步講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章