前言
當我們要從零去搭建一個自己的應用框架時 。做爲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封裝
進一步講解。