前言

当我们要从零去搭建一个自己的应用框架时 。做为2017年Android程序员的我,就会把Kotlin+Retrofit+MVP+RX系列拿的去实战。整体框架模式构思好后,那就得想想大概实现的步骤。说到这里,就得整理下应用大概有哪些东西了。

应用模块总结.png

目前个人能想到的也就这些,这样就有个引导的步骤和思路了。所以写了下面几篇文章

也算自己给自己的的一些总结,具体代码参考GoachFrame-Github

接下来,就先从网络层Retrofit+OkHttp说起,记得以前自己写过一篇Retrofit的博客,学会Retrofit+OkHttp+RxAndroid三剑客的使用,让自己紧跟Android潮流的步伐,但返回的数据没有结合RxJava来使用,所以这里重新来写下。

思路

我们都知道,实现一个Retrofit大概需要下面的几个步骤

配置一个OkHttp对象

配置BaseUrl

需要返回Obserable对象就配置RxJava2CallAdapterFactory

需要Gson解析就配置GsonConverterFactory

同时一个Retrofit对象最好对应一个BaseUrl。本着封装变化的原则,仔细相想,这里也就OkHttp是变化的,BaseUrl可以通过参数传入,然后RxJavaCallAdapterFactory和GsonConverterFactory直接配置,另外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请求拦截器使用,Timber是Log封装库

创建OkHttp对象的接口IClient

interface IClient {
fun getClient():OkHttpClient
}

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

接下来设置一个默认配置的OkHttp的DefaultOkHttpClient

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 = 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> = mutableMapOf()
override fun saveFromResponse(url: HttpUrl, cookies: MutableList) {
mCookieStore.put(url.host(),cookies)
}
override fun loadForRequest(url: HttpUrl): MutableList {
val cookies = mCookieStore[url.host()]
return cookies?: mutableListOf()
}
}
}
fun OkHttpClient.Builder.addInterceptors(mInterceptors : Array):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):DefaultOkHttpClient{
this.mInterceptors = interceptors
return this
}
}

上面的getCookieJar方法可以实现在发送请求时候,CookieJar方法会回调loadForRequest把cookie加入request header里面,在请求响应的时候,Cookjar会回调saveFromResponse方法,从而读取response header里面的cookie。这是只是简单的配置下,这里暂时没用到cookie的使用,在注入的时候还可以进一步cookie持久化和保存在本地,比如实现用户的自动登录功能。

上面还提供了请求时间的配置和拦截器的配置,这样就可以在Application注入的时候进一步配置

GsonConverterFactory

创建Retrofit的时候,我们还需要传入GsonConverterFactory,做为请求返回json使用Gson来使用,其中GsonConverterFactory可以传入一个Gson对象,统一做一些数据的序列化和反序列化数据处理。其中TypeAdapter是同时对数据进行序列化处理和反序列化处理,或者单独的通过JsonSerializer进行序列化,以及JsonDeserializer反序列化,如下

class GsonConverter {
fun 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())
.create()
}
}

创建GsonBuilder然后通过registerTypeAdapter注入需要处理的一些 TypeAdapter或者JsonSerializer和JsonDeserializer等等

private class NullStringAdapter : TypeAdapter() {
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)
}
}
NullStringAdapter对String类型的NULL转换为"",
private class DateSerializer : JsonSerializer {
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 {
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 {
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 {
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:JsonDeserializer>{
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ResponseWrapper {
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(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(val code:Int = -1,
val msg:String = "",
val version:String = "",
val timestamp:Long = 0,
@Transient val data: T): Serializable
Retrofit

OkHttp和Gson准备好后,接下来就可以创建Retrofit对象了。通过单例实现

object ApiService {
private var mIClient:IClient = DefaultOkHttpClient()
private val mRetrofitMap:MutableMap = mutableMapOf()
fun get(baseUrl: String, service: Class): T {
return this.getRetrofit(baseUrl).create(service)
}
private fun 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()))
.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对象

注入

准备好后接下来就是在Application的onCreate方法里面注入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? = 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):HttpUrl.Builder{
if(mQueryParameters.isNotEmpty()){
mQueryParameters.forEach {
this.addQueryParameter(it.key,it.value)
}
}
return this
}
fun defaultBaseParameters(originalHttpUrl:HttpUrl):MutableMap{
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>
}

注意,返回的bean里面不能用泛型,否则会报错,这里ArticleListResponse是json返回的数据,这里只是随便定义一些数据。

open class BaseResponse:Serializable
open class PageResponse : BaseResponse() {
var page = 0
var size = 0
var total = 0
}
class ArticleListResponse : PageResponse() {
val data: List = 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{
return ApiClient(CommService::class.java).articleList(pageInfo.page,pageInfo.size,id).responseWrapperLogic()
}
fun ApiClient(service: Class):T{
return ApiService.get(BuildConfig.BASE_URL,service)
}
private fun Observable>.responseWrapperLogic() =
map { it.data}.compose{it.subscribeOn(Schedulers.io())}.observeOn(AndroidSchedulers.mainThread())
}

这里只是随便定义接口方法而已,可以根据自己相应的接口添加。

使用

最简单调用

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

接下来可以进一步结合compose来写请求的加载框或者其他加载动画。其中subscribeWith是定义的一个DisposableObserver,bindTo是结合 CompositeDisposable一起绑定多个Disposable,后面可以更好的管理绑定和解绑

fun Observable.subscribeWith(onNext:((res: T) ->Unit)?=null,
onError:((e: Throwable) ->Unit)?=null,
onComplete:(() ->Unit)?=null):DisposableObserver{
return this.subscribeWith(object : DisposableObserver() {
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 DisposableObserver.bindTo(sub: CompositeDisposable) {
sub.add(this)
}

mvpView.sub是在BasePresenter里面定义的CompositeDisposable对象