美文网首页KotlinAndroid开发经验谈Android知识
Android搭建应用框架系列之Retrofit封装

Android搭建应用框架系列之Retrofit封装

作者: Goach | 来源:发表于2017-11-16 11:47 被阅读358次

    前言

    当我们要从零去搭建一个自己的应用框架时 。做为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封装进一步讲解。

    相关文章

      网友评论

      • 姑娘请别为难小僧:总结的很好,不过要是用java写的Demo,我们更容易接受。有没java版本的哈
        Goach:@姑娘请别为难小僧 暂时没写Java版本,不过可以用AS转换吧

      本文标题:Android搭建应用框架系列之Retrofit封装

      本文链接:https://www.haomeiwen.com/subject/iogrpxtx.html