前言
当我们要从零去搭建一个自己的应用框架时 。做为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
大概需要下面的几个步骤
1. 配置一个OkHttp
对象
2. 配置BaseUrl
3. 需要返回Obserable
对象就配置RxJava2CallAdapterFactory
4. 需要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封装
进一步讲解。