前一篇文章(也是我在簡書上的第一篇技術文章.)講了Android三劍客的基礎用法和簡單封裝,有一些封裝只是一筆帶過,還有些用法被遺漏沒講到的,所以在這篇里統一做下查漏補缺。
0x00 先做一下糾正:
https和失敗重連,OkHttp默認是支持的,并不用手動去設置(在OkHttpClient.Builder中已默認設置),所以OkHttpClient.Builder的初始化可以簡化為:
// 創建OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 超時設置
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
// cookie管理
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));
0x01 Cookie持久化管理
這部分主要參考了這篇文章。
- 不帶持久化
builder.cookieJar(new CookieJar() {
private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url, cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies != null ? cookies : new ArrayList<Cookie>();
}
});
這種簡單的實現,每次重啟App都會需要重新登錄,不可取。
- 帶持久化
CookieHandler cookieHandler = new CookieManager(
new PersistentCookieStore(context), CookiePolicy.ACCEPT_ALL);
builder.cookieJar(new JavaNetCookieJar(cookieHandler));
這里出現了兩個類:JavaNetCookieJar和PersistentCookieStore
- JavaNetCookieJar就是對CookieJar的封裝實現,里面實現了對Cookie持久化存儲和獲取的調用邏輯,OkHttp已經幫我們實現了這個類,需要引入下面這個包:
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.5.0'
PersistentCookieStore是具體實現Cookie持久化的類,使用的是SharedPreferences,具體代碼實現可參考這篇。
當然,如果你想通過數據庫實現持久化,也可以自己封裝一個類似的類去實現。再介紹一個封裝了Cookie持久化的第三方庫(推薦)
ClearableCookieJar cookieJar = new PersistentCookieJar(
new SetCookieCache(), new SharedPrefsCookiePersistor(context));
builder.cookieJar(cookieJar);
需要引入下面這個包:
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0
0x02. 攔截器
-
addInterceptor和addNetworkInterceptor的區別
前一篇文章有同學問到兩者的區別,okHttp官方對攔截器做了解釋,并給了一張圖,感覺挺一目了然的。
Paste_Image.png
兩種攔截器簡單來說就是調用時機的區別,應用攔截器調用時機較早,也就是進入chain.proceed的遞歸較早,相應的完成遞歸得到response會較晚;而網絡攔截器則相反,request請求調用時機較晚,會較早完成chain.proceed遞歸調用,得到response的時機較早。
簡單來說就是應用攔截器較上層,而網絡攔截器較底層,所有攔截器就是一個由淺入深的遞歸調用。具體還是得看源碼。 Http Header
可以通過這個攔截器為Request添加全局統一的Header。
/**
* 網絡請求公共頭信息插入器
*
* Created by XiaoFeng on 17/1/18.
*/
public class HttpHeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", "Android, xxx")
.header("Accept", "application/json")
.header("Content-type", "application/json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}
- 公共參數
主要參考這篇
/**
* 網絡請求公共參數插入器
* <p>
* Created by XiaoFeng on 2017/1/25.
*/
public class CommonParamsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.method().equals("GET")) {
HttpUrl httpUrl = request.url().newBuilder()
.addQueryParameter("version", "xxx")
.addQueryParameter("device", "Android")
.addQueryParameter("timestamp", String.valueOf(System.currentTimeMillis()))
.build();
request = request.newBuilder().url(httpUrl).build();
} else if (request.method().equals("POST")) {
if (request.body() instanceof FormBody) {
FormBody.Builder bodyBuilder = new FormBody.Builder();
FormBody formBody = (FormBody) request.body();
for (int i = 0; i < formBody.size(); i++) {
bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
}
formBody = bodyBuilder
.addEncoded("version", "xxx")
.addEncoded("device", "Android")
.addEncoded("timestamp", String.valueOf(System.currentTimeMillis()))
.build();
request = request.newBuilder().post(formBody).build();
}
}
return chain.proceed(request);
}
}
- 緩存策略
/**
* 網絡請求緩存策略插入器
*
* Created by XiaoFeng on 17/1/17.
*/
public class HttpCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 無網絡時,始終使用本地Cache
if (!NetworkUtil.isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (NetworkUtil.isNetworkConnected()) {
// 有網絡時,設置緩存過期時間0個小時
int maxAge = 0;
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma") // 清除頭信息,因為服務器如果不支持,會返回一些干擾信息,不清除下面無法生效
.build();
} else {
// 無網絡時,設置緩存過期超時時間為4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return response;
}
}
- 調試工具
使用的是Facebook推出的一個集成到Chrome中的調試工具,需要引入下面兩個庫:
compile 'com.facebook.stetho:stetho:1.4.1'
compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'
在Application中初始化就可以用了
Stetho.initializeWithDefaults(this);
如何調試?
- 打開Chrome瀏覽器
- 地址欄輸入
chrome://inspect
- 進入頁面后,在左邊的DevTools -> Devices -> Remote Target下,可以找到你連接的手機設備,點開后就會出現調試頁面了,后面就自己研究吧,不光可以調試網絡請求,還可以查看手機中的數據庫和SharePreference等持久化文件,而且不用root,很強大!
0x03. FastJson解析庫封裝
網上很多介紹retrofit的文章,對網絡請求返回的結果,使用的都是默認的Gson庫,雖然可以滿足大部分人的需求,但是有些對性能要求高一點的人,還是習慣使用FastJson庫做解析,這里就講講如何把默認的Gson庫替換成FastJson庫。
首先,默認Gson庫的設置是這樣的:
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
用FastJson庫替換后是這樣的:
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
是不是很像,沒錯,就是把ConverterFactory替換了一下而已。
至于FastJsonConvertFactory的實現,其實就是仿造GsonConverterFactory的源碼來寫的,并不復雜。
主要有三個類:
- 工廠類:FastJsonConvertFactory,里面就是分別創建了Request和Response的轉換類。
/**
*
* Created by XiaoFeng on 2017/1/17.
*/
public class FastJsonConvertFactory extends Converter.Factory {
public static FastJsonConvertFactory create() {
return new FastJsonConvertFactory();
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return new FastJsonRequestConverter<>();
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new FastJsonResponseConverter<>(type);
}
}
- Request轉換類:FastJsonRequestConverter
/**
*
* Created by XiaoFeng on 2017/1/17.
*/
public class FastJsonRequestConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
@Override
public RequestBody convert(T value) throws IOException {
return RequestBody.create(MEDIA_TYPE, JSON.toJSONBytes(value));
}
}
- Response轉換類:FastJsonResponseConverter
/**
*
* Created by XiaoFeng on 2017/1/17.
*/
public class FastJsonResponseConverter<T> implements Converter<ResponseBody, T> {
private final Type type;
public FastJsonResponseConverter(Type type) {
this.type = type;
}
@Override
public T convert(ResponseBody value) throws IOException {
BufferedSource buffer = Okio.buffer(value.source());
String s = buffer.readUtf8();
buffer.close();
return JSON.parseObject(s, type);
}
}
是不是很簡單,如果想再換成別的第三方json解析庫,照著這個寫就可以了。
0x04. 生命周期
上一篇中還有同學提到RxJava的生命周期管理,防止內存泄漏,這個可以直接使用第三方庫,參考這篇。
一般引用下面兩個庫就夠了:
compile 'com.trello:rxlifecycle:1.0'
compile 'com.trello:rxlifecycle-components:1.0'
有兩種使用方式:
1. 自動取消訂閱,使用bindToLifecycle。
需要繼承至RxActivity或者RxFragment等基類。
@Override
protected void onStart() {
super.onStart();
// Using automatic unsubscription, this should determine that the correct time to
// unsubscribe is onStop (the opposite of onStart).
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
Log.i(TAG, "Unsubscribing subscription from onStart()");
}
})
// 因為bindToLifecycle是在onStart的時候調用,所以在onStop的時候自動取消訂閱
.compose(this.<Long>bindToLifecycle())
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, "Started in onStart(), running until in onStop(): " + num);
}
});
}
從下面這段核心函數可以看清自動取消訂閱的規則,就是在哪個生命周期內調用bindToLifecycle,就在與其對應的結束生命周期函數調用時自動取消訂閱。
private static final Func1<ActivityEvent, ActivityEvent> ACTIVITY_LIFECYCLE =
new Func1<ActivityEvent, ActivityEvent>() {
@Override
public ActivityEvent call(ActivityEvent lastEvent) {
switch (lastEvent) {
case CREATE:
return ActivityEvent.DESTROY;
case START:
return ActivityEvent.STOP;
case RESUME:
return ActivityEvent.PAUSE;
case PAUSE:
return ActivityEvent.STOP;
case STOP:
return ActivityEvent.DESTROY;
case DESTROY:
throw new OutsideLifecycleException("Cannot bind to Activity lifecycle when outside of it.");
default:
throw new UnsupportedOperationException("Binding to " + lastEvent + " not yet implemented");
}
}
};
2. 手動取消訂閱,使用bindUntilEvent。
需要繼承至RxActivity或者RxFragment等基類。
@Override
protected void onResume() {
super.onResume();
// `this.<Long>` is necessary if you're compiling on JDK7 or below.
// If you're using JDK8+, then you can safely remove it.
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
Log.i(TAG, "Unsubscribing subscription from onResume()");
}
})
// 手動設置在Activity onDestroy的時候取消訂閱
.compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, "Started in onResume(), running until in onDestroy(): " + num);
}
});
}
3. 自定義RxActivity/RxFragment
直接繼承RxActivity/RxFragment有時會碰到問題,因為有可能本身已經有一個基類需要繼承,java不能多繼承。不過不要慌,我們可以自定義一個自己的基類,實現方式參考RxActivity。
public abstract class RxActivity extends Activity implements LifecycleProvider<activityevent> {
private final BehaviorSubject<activityevent> lifecycleSubject = BehaviorSubject.create();
public RxActivity() {
}
@NonNull
@CheckResult
public final Observable<activityevent> lifecycle() {
return this.lifecycleSubject.asObservable();
}
@NonNull
@CheckResult
public final <t> LifecycleTransformer<t> bindUntilEvent(@NonNull ActivityEvent event) {
return RxLifecycle.bindUntilEvent(this.lifecycleSubject, event);
}
@NonNull
@CheckResult
public final <t> LifecycleTransformer<t> bindToLifecycle() {
return RxLifecycleAndroid.bindActivity(this.lifecycleSubject);
}
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.lifecycleSubject.onNext(ActivityEvent.CREATE);
}
@CallSuper
protected void onStart() {
super.onStart();
this.lifecycleSubject.onNext(ActivityEvent.START);
}
@CallSuper
protected void onResume() {
super.onResume();
this.lifecycleSubject.onNext(ActivityEvent.RESUME);
}
@CallSuper
protected void onPause() {
this.lifecycleSubject.onNext(ActivityEvent.PAUSE);
super.onPause();
}
@CallSuper
protected void onStop() {
this.lifecycleSubject.onNext(ActivityEvent.STOP);
super.onStop();
}
@CallSuper
protected void onDestroy() {
this.lifecycleSubject.onNext(ActivityEvent.DESTROY);
super.onDestroy();
}
}
突然發現寫文章真是一個知識梳理,自我學習的好方法,比沒有目的性的看很多技術文章有用很多倍,極力推薦有能力的同學都去嘗試寫屬于自己的技術博客。^ ^
參考:
https://gold.xitu.io/entry/572ed42ddf0eea0063186e1f
https://gist.github.com/franmontiel/ed12a2295566b7076161
https://gold.xitu.io/entry/5825300b2f301e005c47fac5
http://www.codexiu.cn/android/blog/39432/
http://androidxx.ren/forum.php?mod=viewthread&tid=19
https://gold.xitu.io/entry/58290ea2570c35005878ce8f