個(gè)人原因贿讹,此系列更新已停止千诬,抱歉静袖。
上一篇《看我開發(fā)干貨集中營App(二) ~ APP初始化》中講述了此APP所依賴的一些第三方庫脏里,包括Retrofit2,Okhttp3,RxJava,Gilde等,對(duì)于使用第三方依賴庫球切,我自己一直都秉承著封裝一層在使用的原則。這樣做的好處除了方便實(shí)際使用外绒障,就是萬一需要替換對(duì)應(yīng)依賴庫不用滿世界去修改代碼吨凑,只需要在封裝入口處修改就好了。關(guān)于使用第三方開源項(xiàng)目户辱,大家可以參考下這篇文字:如何正確使用開源項(xiàng)目鸵钝?
接下來介紹下上面幾個(gè)庫的基本封裝使用,提醒下以下內(nèi)容將會(huì)基于你已經(jīng)了解過對(duì)應(yīng)的開源項(xiàng)目庐镐,而且知道如何使用恩商,所以如果還未了解過相關(guān)庫的請(qǐng)自行Google了解一番再回來看哦。
OK必逆,先預(yù)覽下《干貨精選》的目錄結(jié)構(gòu):
關(guān)于Android App目錄結(jié)構(gòu)怠堪,個(gè)人比較推薦按模塊分類。直接看看包名下的目錄:
-
commonui
: 通用UI封裝類存放目錄 -
core
: 存放底層核心的封裝類名眉,如retrofit粟矿,rxjava等 -
modules
: 按模塊存放文件目錄,主要是mvp開發(fā)模式的文件损拢,如Google Todo-Mvp分支陌粹,
Retrofit2封裝
創(chuàng)建工具類:core/retofit/RetrofitHelper
RetrofitHelper特性:
- 單例形式創(chuàng)建Retrofit實(shí)例;
- 使用okhttp3作為請(qǐng)求客戶端福压;
- 使用gson作為數(shù)據(jù)轉(zhuǎn)換器掏秩;
- 使用RxJava優(yōu)化異步請(qǐng)求流程或舞;
- 開啟數(shù)據(jù)緩存,無網(wǎng)絡(luò)時(shí)可從緩存讀取數(shù)據(jù)蒙幻;
- 輔助類靜態(tài)方法獲取Retrofit Service實(shí)例映凳。
節(jié)省篇幅,上代碼看注釋:
public class RetrofitHelper {
private volatile static Retrofit retrofitInstance = null;
/**
* 創(chuàng)建Retrofit請(qǐng)求Api
* @param clazz Retrofit Api接口
* @return api實(shí)例
*/
public static <T> T createApi(Class<T> clazz){
return getInstance().create(clazz);
}
// ===============================================================
// private methods =================================================
/**
* 獲取Retrofit實(shí)例
* @return Retrofit
*/
private static Retrofit getInstance(){
if(null == retrofitInstance){
synchronized (Retrofit.class){
if(null == retrofitInstance){ // 雙重檢驗(yàn)鎖,僅第一次調(diào)用時(shí)實(shí)例化
retrofitInstance = new Retrofit.Builder()
// baseUrl總是以/結(jié)束杆煞,@URL不要以/開頭
.baseUrl(BuildConfig.API_SERVER_URL)
// 使用OkHttp Client
.client(buildOKHttpClient())
// 集成RxJava處理
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
// 集成Gson轉(zhuǎn)換器
.addConverterFactory(buildGsonConverterFactory())
.build();
}
}
}
return retrofitInstance;
}
/**
* 構(gòu)建OkHttpClient
* @return OkHttpClient
*/
private static OkHttpClient buildOKHttpClient(){
// 添加日志攔截器魏宽,非debug模式不打印任何日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(AppUtil.isDev() ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE) ;
return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) // 添加日志攔截器
//.addInterceptor(buildTokenInterceptor()) // 添加token攔截器
.addNetworkInterceptor(buildCacheInterceptor()) // 添加網(wǎng)絡(luò)緩存攔截器
.cache(getCache()) // 設(shè)置緩存文件
.retryOnConnectionFailure(true) // 自動(dòng)重連
.connectTimeout(15, TimeUnit.SECONDS) // 15秒連接超時(shí)
.readTimeout(20, TimeUnit.SECONDS) // 20秒讀取超時(shí)
.writeTimeout(20, TimeUnit.SECONDS) // 20秒寫入超時(shí)
.build();
}
/**
* 獲取緩存對(duì)象
* @return Cache
*/
private static Cache getCache(){
// 獲取緩存目標(biāo),SD卡
File cacheFile = new File(AppUtil.getContext().getCacheDir(), ResourceUtil.getString(R.string.app_name_en));
// 創(chuàng)建緩存對(duì)象,最大緩存50m
return new Cache(cacheFile, 1024*1024*20);
}
/**
* 構(gòu)建緩存攔截器
* @return Interceptor
*/
private static Interceptor buildCacheInterceptor(){
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 無網(wǎng)絡(luò)連接時(shí)請(qǐng)求從緩存中讀取
if (!AppUtil.isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
// 響應(yīng)內(nèi)容處理
// 在線時(shí)緩存5分鐘
// 離線時(shí)緩存4周
Response response = chain.proceed(request);
if (AppUtil.isNetworkConnected()) {
int maxAge = 300;
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")// 清除頭信息,因?yàn)榉?wù)器如果不支持决乎,會(huì)返回一些干擾信息队询,不清除下面無法生效
.build();
}else {
// 無網(wǎng)絡(luò)時(shí),設(shè)置超時(shí)為4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return response;
}
};
}
/**
* 構(gòu)建GSON轉(zhuǎn)換器
* @return GsonConverterFactory
*/
private static GsonConverterFactory buildGsonConverterFactory(){
GsonBuilder builder = new GsonBuilder();
builder.setLenient();
// 注冊(cè)類型轉(zhuǎn)換適配器
builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return null == json ? null : new Date(json.getAsLong());
}
});
Gson gson = builder.create();
return GsonConverterFactory.create(gson);
}
}
其中有幾點(diǎn)需要說明下:
- AppUtil是另外一個(gè)工具類构诚,封裝了APP常用的一些方法蚌斩,如判斷網(wǎng)絡(luò)狀態(tài),獲取App版本范嘱、緩存路徑等送膳。
-
API_SERVER_URL
是定義在 build.gradle 中的一個(gè)常量(干貨集中營api基本URL),實(shí)際產(chǎn)品研發(fā)中可以使用同樣方法定義不同開發(fā)環(huán)境(本地開發(fā)丑蛤、測(cè)試叠聋、正式環(huán)境)的服務(wù)器地址。 - 緩存設(shè)置如果服務(wù)器響應(yīng)Header不支持緩存,貌似緩存不起作用受裹,歡迎大家?guī)兔y(cè)試碌补。
使用方法:
假設(shè)有Retrofit Service Api如下:
public interface ArticleServiceApi {
@GET("day/{year}/{month}/{day}")
Observable<List<Article>> fetchByDate(@Path("year") String year, @Path("month") String month,
@Path("day") String day);
}
執(zhí)行api請(qǐng)求如下:
RetrofitHelper.createApi(ArticleServiceApi.class)
.fetchByDate("2016", "09", "17")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Article>>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(List<Article> articles) {}
});
是不是比原始的請(qǐng)求簡單不少。接下來對(duì)RxJava封裝一層棉饶,使上面的請(qǐng)求更加簡單點(diǎn)厦章。
RxJava封裝
再Retrofit中使用RxJava,需要封裝的地方僅有2個(gè)地方照藻,
- 切換線程操作
- 響應(yīng)結(jié)果處理:onComplete()方法可選袜啃,通用錯(cuò)誤響應(yīng),保留onSuccess()供用戶調(diào)用
從干貨集中營API返回結(jié)果可知幸缕,響應(yīng)格式如下:
{
error: false,
results: [object, object]
}
// 或者
{
error: false,
category: [String,String...],
results: {
"Android": [{}, {}],
"IOS": [{}, {}]
}
}
為此群发,首先創(chuàng)建響應(yīng)結(jié)果的實(shí)體基類 core/response/ResponseData
:
public class ResponseData {
private boolean error; // 是否請(qǐng)求錯(cuò)誤
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
然后建立干貨的實(shí)體類: modules/articles/entity/Article
,以及API響應(yīng)結(jié)果實(shí)體類modules/articles/entity/DailyArticles
发乔。
基于以上響應(yīng)實(shí)體類也物,我們創(chuàng)建RxJava訂閱者的封裝基類:core/rx/RxSubscriber
public abstract class RxSubscriber<T> extends Subscriber<T> {
@Override
public void onCompleted() {
// 忽略操作,需要可覆寫該方法
}
@Override
public void onError(Throwable e) {
if(e instanceof HttpException){
String errNetwork = ResourceUtil.getString(R.string.err_network);
LogUtil.error(e, "onError: " + errNetwork);
ToastUtil.show(errNetwork);
}
// TODO: 處理其它通用錯(cuò)誤
// 覆寫此方法自行處理異常列疗,通用異常使用super.onError(e)保留
e.printStackTrace();
}
@Override
public void onNext(T t) {
if(t instanceof ResponseData){
ResponseData response = (ResponseData) t;
// 判斷是否請(qǐng)求錯(cuò)誤滑蚯,出錯(cuò)直接轉(zhuǎn)到onError()
if(response.isError()){
Throwable e = new Throwable(ResourceUtil.getString(R.string.err_default));
this.onError(e);
return;
}
}
onSuccess(t);
}
/**
* 請(qǐng)求成功回調(diào)
* @param t 最終響應(yīng)結(jié)果
*/
public abstract void onSuccess(T t);
}
這里封裝并沒有做太多處理,主要是對(duì)公共的異常做了一次處理,保留onSuccess()更多關(guān)注業(yè)務(wù)處理告材。這樣封裝感覺還不是很好坤次,比如錯(cuò)誤處理可以通過事件方式傳遞給View等,不過因?yàn)闀簳r(shí)對(duì)Android研究不深斥赋,還沒有更好的想法缰猴。
接下來,對(duì)線程切換操作再做一個(gè)封裝:core/rx/RxJavaHelper
疤剑。
對(duì)線程切換的封裝需要用到RxJava的 compose()
方法滑绒,如果不太了解它的使用,可以參考下:給 Android 開發(fā)者的 RxJava 詳解隘膘。
public class RxJavaHelper {
/**
* 切換線程操作
* @return Observable轉(zhuǎn)換器
*/
public static <T> Observable.Transformer<T, T> observeOnMainThread() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> tObservable) {
return tObservable
.subscribeOn(Schedulers.io()) // 在io線程中請(qǐng)求
.observeOn(AndroidSchedulers.mainThread()); // 請(qǐng)求完成后返回主線程處理
}
};
}
}
封裝基本完畢疑故,再來看看請(qǐng)求api代碼:
RetrofitHelper.createApi(ArticleServiceApi.class)
.fetchByDate("2016", "09", "01")
.compose(RxJavaHelper.<DailyArticles>observeOnMainThread())
.subscribe(new RxSubscriber<DailyArticles>() {
@Override
public void onSuccess(DailyArticles dailyArticles) {
LogUtil.debug("onSuccess: " + dailyArticles.getCategory().toString());
}
});
是不是有所改善~~
來看看請(qǐng)求結(jié)果:
D/OkHttp: --> GET http://gank.io/api/day/2016/09/01 http/1.1
D/OkHttp: --> END GET
D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
[ 09-21 20:50:57.557 2870: 2870 D/ ]
HostConnection::get() New Host Connection established 0xa230c570, tid 2870
[ 09-21 20:50:57.625 2870: 2918 D/ ]
HostConnection::get() New Host Connection established 0xae427510, tid 2918
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OkHttp: <-- 200 OK http://gank.io/api/day/2016/09/01 (250ms)
D/OkHttp: Server: nginx/1.4.6 (Ubuntu)
D/OkHttp: Date: Wed, 21 Sep 2016 12:50:57 GMT
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 4945
D/OkHttp: Connection: keep-alive
D/OkHttp: <-- END HTTP
D/GankEssence: ╔════════════════════════════════════════════════════════════════════════════════════════
D/GankEssence: ║ RxSubscriber.onNext (RxSubscriber.java:51)
D/GankEssence: ║ MainActivity$1.onSuccess (MainActivity.java:23)
D/GankEssence: ╟────────────────────────────────────────────────────────────────────────────────────────
D/GankEssence: ║ onSuccess: [瞎推薦, Android, 福利, 休息視頻, iOS]
D/GankEssence: ╚════════════════════════════════════════════════════════════════════════════════════════
OK,還可以弯菊,Retrofit2 + RxJava 封裝就暫時(shí)這樣了纵势。如果使用過程有什么問題再修改修改!管钳!
本來還想再把Logger以及Glide的封裝再說說钦铁,不過發(fā)現(xiàn)篇幅好長啊,而且Glide本身應(yīng)實(shí)在夠好了才漆,不需要太多封裝牛曹,只需封裝個(gè)工具類方便我們自己使用就好了,Logger也是一樣醇滥。大家有需要可以直接看看源碼:ImageUtil 和 LogUtil黎比。
本篇文章主要是簡單說了第三方開源庫的封裝使用,其它的沒說腺办。對(duì)于Activity焰手、Fragment等各類View組件使用糟描,后面肯定會(huì)有封裝一些適當(dāng)?shù)幕惢澈恚貏e是RecyclerView,會(huì)在后面開發(fā)過程提到的船响。
預(yù)告躬拢,下一篇《看我開發(fā)干貨集中營App(四)~ 首頁開發(fā)》將開始構(gòu)建APP應(yīng)用,功能點(diǎn)有RecyclerView使用见间、下拉刷新聊闯、上拉加載更多、HTML內(nèi)容的解析等等米诉。
Tags: 看我開發(fā) Android開發(fā) 干貨集中營 開放api Retroft2封裝 RxJava封裝 Glide封裝
系列文章: