RxJava系列文章目錄導(dǎo)讀:
一黍少、RxJava create操作符的用法和源碼分析
二魂角、RxJava map操作符用法詳解
三、RxJava flatMap操作符用法詳解
四、RxJava concatMap操作符用法詳解
五、RxJava onErrorResumeNext操作符實(shí)現(xiàn)app與服務(wù)器間token機(jī)制
六唇辨、RxJava retryWhen操作符實(shí)現(xiàn)錯(cuò)誤重試機(jī)制
七、RxJava 使用debounce操作符優(yōu)化app搜索功能
八、RxJava concat操作處理多數(shù)據(jù)源
九搜变、RxJava zip操作符在Android中的實(shí)際使用場(chǎng)景
十绩社、RxJava switchIfEmpty操作符實(shí)現(xiàn)Android檢查本地緩存邏輯判斷
十一摔蓝、RxJava defer操作符實(shí)現(xiàn)代碼支持鏈?zhǔn)秸{(diào)用
十二、combineLatest操作符的高級(jí)使用
十三愉耙、RxJava導(dǎo)致Fragment Activity內(nèi)存泄漏問(wèn)題
十四贮尉、interval、takeWhile操作符實(shí)現(xiàn)獲取驗(yàn)證碼功能
一般我們?cè)趯?shí)際的開(kāi)發(fā)中朴沿,RxJava和Retrofit2結(jié)合使用的比較多猜谚,因?yàn)樗麄兛梢詿o(wú)縫集成,例如我們下面的一個(gè)網(wǎng)絡(luò)請(qǐng)求:
public interface OtherApi {
@GET("/timeout")
Observable<Response> testTimeout(@Query("timeout") String timeout);
}
private void getSomething(){
subscription = otherApi.testTimeout("10000")
.subscribe(new Action1<Response>() {
@Override
public void call(Response response) {
String content = new String(((TypedByteArray) response.getBody()).getBytes());
Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
}
上面的代碼非常簡(jiǎn)單赌渣,用過(guò)Retrofit2和RxJava一眼就看明白了魏铅,我們知道還需要在界面destroy的時(shí)候,把subscription反注銷掉坚芜,避免內(nèi)存泄漏览芳,如:
@Override
public void onDestroy() {
super.onDestroy();
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
Log.d("RxJavaLeakFragment", "subscription.unsubscribe()");
}
}
但是這真的能避免內(nèi)存泄漏嗎?下面我們來(lái)做一個(gè)實(shí)驗(yàn)鸿竖。
操作步驟:我們進(jìn)入某個(gè)界面(Activity沧竟、Fragment)铸敏,點(diǎn)擊按鈕請(qǐng)求網(wǎng)絡(luò),故意讓該網(wǎng)絡(luò)請(qǐng)求執(zhí)行10秒悟泵,在網(wǎng)絡(luò)返回前杈笔,我們關(guān)閉界面。
后端代碼如下:
//如果用戶傳進(jìn)來(lái)的timeout>0則當(dāng)前線程休眠timeout糕非,否則休眠20秒
@WebServlet("/timeout")
public class TimeoutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String timeout = request.getParameter("timeout");
long to = getLong(timeout);
if (to <= 0) {
to = 20000;
}
try {
Thread.sleep(to);
} catch (InterruptedException e) {
e.printStackTrace();
}
ResponseJsonUtils.json(response, "timeout success");
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
static long getLong(String value) {
try {
return Long.parseLong(value);
} catch (Exception e) {
}
return -1;
}
}
Fragment的代碼如下:
//點(diǎn)擊按鈕請(qǐng)求網(wǎng)絡(luò)蒙具,在成功回調(diào)方法里輸出服務(wù)器返回的結(jié)果和當(dāng)前Fragment的對(duì)象
@Override
public void onClick(View v) {
super.onClick(v);
switch (v.getId()) {
case R.id.btn_request_netword_and_pop:
if (otherApi == null) {
otherApi = ApiServiceFactory.createService(OtherApi.class);
}
subscription = otherApi.testTimeout("10000")
.subscribe(new Action1<Response>() {
@Override
public void call(Response response) {
String content = new String(((TypedByteArray) response.getBody()).getBytes());
Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
break;
}
}
//用戶按返回按鈕關(guān)閉當(dāng)前界面,subscription執(zhí)行unsubscribe()方法
@Override
public void onDestroy() {
super.onDestroy();
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
Log.d("RxJavaLeakFragment", "subscription.unsubscribe()");
}
}
點(diǎn)擊網(wǎng)絡(luò)請(qǐng)求按鈕后朽肥,立馬關(guān)閉當(dāng)前界面禁筏,等待我們?cè)O(shè)定的超時(shí)時(shí)間10秒,測(cè)試輸出結(jié)果如下:
D/Retrofit: ---> HTTP GET http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000
D/Retrofit: Authorization: test
D/Retrofit: ---> END HTTP (no body)
I/DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
D/RxJavaLeakFragment: subscription.unsubscribe()
D/Retrofit: <--- HTTP 200 http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000 (10086ms)
D/Retrofit: : HTTP/1.1 200 OK
D/Retrofit: Content-Type: text/plain;charset=UTF-8
D/Retrofit: Date: Tue, 28 Mar 2017 11:06:07 GMT
D/Retrofit: Server: Apache-Coyote/1.1
D/Retrofit: Transfer-Encoding: chunked
D/Retrofit: X-Android-Received-Millis: 1490699154102
D/Retrofit: X-Android-Response-Source: NETWORK 200
D/Retrofit: X-Android-Selected-Protocol: http/1.1
D/Retrofit: X-Android-Sent-Millis: 1490699144047
D/Retrofit: "timeout success"
D/Retrofit: <--- END HTTP (17-byte body)
D/RxJavaLeakFragment: RxJavaLeakFragment{60678c5}:"timeout success"
最后一行日志道出了真相鞠呈,雖然我們關(guān)閉了界面融师,但是回調(diào)依然對(duì)Fragment有引用,所以當(dāng)服務(wù)器返回界面的時(shí)候蚁吝,依然可以打印Fragment的對(duì)象旱爆。
Rxjava 為我們提供onTerminateDetach
操作符來(lái)解決這樣的問(wèn)題,在RxJava 1.1.2
版本還沒(méi)有這個(gè)操作符的窘茁,在RxJava1.2.4
是有這個(gè)操作符怀伦。
/**
* Nulls out references to the upstream producer and downstream Subscriber if
* the sequence is terminated or downstream unsubscribes.
*/
@Experimental
public final Observable<T> onTerminateDetach() {
return create(new OnSubscribeDetach<T>(this));
}
上面的注釋意思就是說(shuō) 當(dāng)執(zhí)行了反注冊(cè)u(píng)nsubscribes或者發(fā)送數(shù)據(jù)序列中斷了,解除上游生產(chǎn)者與下游訂閱者之間的引用山林。
所以onTerminateDetach
操作符要和subscription.unsubscribe()
結(jié)合使用房待,因?yàn)椴粓?zhí)行subscription.unsubscribe()
的話,onTerminateDetach
就不會(huì)被觸發(fā)驼抹。
所以只要調(diào)用onTerminateDetach()
即可桑孩,如下所示:
subscription = otherApi.testTimeout("10000")
.onTerminateDetach()
.subscribe(new Action1<Response>() {
@Override
public void call(Response response) {
String content = new String(((TypedByteArray) response.getBody()).getBytes());
Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
測(cè)試結(jié)果如下 :
Retrofit: ---> HTTP GET http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000
Retrofit: Authorization: test
Retrofit: ---> END HTTP (no body)
DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
RxJavaLeakFragment: subscription.unsubscribe()
Retrofit: <--- HTTP 200 http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000 (10165ms)
Retrofit: : HTTP/1.1 200 OK
Retrofit: Content-Type: text/plain;charset=UTF-8
Retrofit: Date: Tue, 28 Mar 2017 11:20:46 GMT
Retrofit: Server: Apache-Coyote/1.1
Retrofit: Transfer-Encoding: chunked
Retrofit: X-Android-Received-Millis: 1490700033441
Retrofit: X-Android-Response-Source: NETWORK 200
Retrofit: X-Android-Selected-Protocol: http/1.1
Retrofit: X-Android-Sent-Millis: 1490700023314
Retrofit: "timeout success"
Retrofit: <--- END HTTP (17-byte body)
從日志可以看出,雖然服務(wù)器返回了數(shù)據(jù)框冀,但是RxJava Action1的回調(diào)并沒(méi)有執(zhí)行流椒,內(nèi)存泄漏的問(wèn)題已經(jīng)解決了。
本文的例子放在github上 https://github.com/chiclaim/android-sample/tree/master/rxjava