前言
当我们要从零去搭建一个自己的应用框架时 。做为2017
年Android
程序员的我,就会把Kotlin+Retrofit+MVP+RX
系列拿的去实战。整体框架模式构思好后,那就得想想大概实现的步骤。说到这里,就得整理下应用大概有哪些东西了。
目前个人能想到的也就这些,这样就有个引导的步骤和思路了。所以写了下面几篇文章
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
大概需要下面的几个步骤
- 配置一个
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<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
方法会回调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 <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
或者JsonSerializer
和JsonDeserializer
等等
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)
}
}
NullStringAdapter
对String
类型的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
OkHttp
和Gson
准备好后,接下来就可以创建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
对象
注入
准备好后接下来就是在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<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
里面不能用泛型,否则会报错,这里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<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封装
进一步讲解。