今天有位同學問了我一個問題工三,話說迁酸,問我
“有遇到網(wǎng)絡(luò)請求一半,退出Activity造成的Theard泄露嗎俭正?已在銷毀時調(diào)用了un了
我去查看了下rx的源碼的unsubscribe方法奸鬓,定位到一個實現(xiàn)類,NewThreadWorker的unsubscribe方法中掸读,源碼如下:
@Override
public void unsubscribe() {
isUnsubscribed = true;
executor.shutdownNow();
deregisterExecutor(executor);
}
@Override
public boolean isUnsubscribed() {
return isUnsubscribed;
}
這個shutdownNow()在java的注釋中寫的很清楚
There are no guarantees beyond best-effort attempts to stop* processing actively executing tasks.
public interface ExecutorService extends Executor {
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* <p>This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
*/
void shutdown();
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution.
*
* <p>This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
* do that.
*
* <p>There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. For example, typical
* implementations will cancel via {@link Thread#interrupt}, so any
* task that fails to respond to interrupts may never terminate.
*
* @return list of tasks that never commenced execution
*/
List<Runnable> shutdownNow();
它只是盡量保證停止所有tasks串远,因為,如果耗時操作沒有做完儿惫,finished掉activity澡罚,同時unsubscribe 掉Subscription的話,可能還有后臺線程在做一些耗時任務肾请。那么會不會造成內(nèi)存泄露呢留搔?
我覺得還是要源碼來說說話吧
/**
* Unsubscribe from all of the subscriptions in the list, which stops the receipt of notifications on
* the associated {@code Subscriber}.
*/
@Override
public void unsubscribe() {
if (!unsubscribed) {
List<Subscription> list;
synchronized (this) {
if (unsubscribed) {
return;
}
unsubscribed = true;
list = subscriptions;
subscriptions = null;
}
// we will only get here once
unsubscribeFromAll(list);
}
}
private static void unsubscribeFromAll(Collection<Subscription> subscriptions) {
if (subscriptions == null) {
return;
}
List<Throwable> es = null;
for (Subscription s : subscriptions) {
try {
s.unsubscribe();
} catch (Throwable e) {
if (es == null) {
es = new ArrayList<Throwable>();
}
es.add(e);
}
}
Exceptions.throwIfAny(es);
}
實際上會做一些清除引用,暫停任務的操作筐喳。因此催式,一般來講在activity的ondestroy中調(diào)用unsubscribe之后函喉,是不會造成內(nèi)存泄露的。但是真的是這樣的嗎荣月?讓我們來做做實驗吧管呵,結(jié)果說明一切。
為了能夠更好的證明這一點哺窄,我還特意做了一個app demo去驗證捐下。
主要代碼:
demo地址已經(jīng)放到了github上:
啟動app,首先進入的是MainActivity,然后萌业,我們進入SecondActivity這時候坷襟,onresume執(zhí)行,我們的任務也就開始了生年,稍微過幾秒婴程,我們退出SecondActivity,回到MainActivity抱婉,這之后档叔,顯然,SecondActivity的ondestory方法會被執(zhí)行蒸绩,我們可以發(fā)現(xiàn)日志也停止了打印衙四。
如上圖所示,停留在了5就不在執(zhí)行了患亿。
這時候传蹈,我們GC一下,在導出hprof
文件步藕,注意惦界,為什么要手動GC一下呢?因為android虛擬機去GC也是有策略的漱抓,有GC周期的表锻。這時候可能并沒有GC過,也就是說乞娄,SecondActivity的內(nèi)存可能并沒有被釋放瞬逊,但并不等于說,SecondActivity就泄露了仪或,因為他也有可能是可以被GC的确镊,只是還沒有來得及被GC而已》渡荆總之蕾域,在導出hprof
文件之前,最好先手動GC一下。就是下圖那個車子旨巷,嗯巨缘,點一下吧,放心點采呐。
然后熟悉查看hprof
文件的同學這時候可能就看到了若锁,執(zhí)行分析之后,并沒有看到內(nèi)存泄露斧吐。
從上圖我們看到SecondeActivity已經(jīng)是紅色又固,標明被回收了,不存在內(nèi)存泄漏煤率。
同時仰冠,我調(diào)試跟蹤了一下unsubscribe
調(diào)用棧,因為是一堆抽象類及接口蝶糯,又有一堆的實現(xiàn)類洋只,所以,最效率的方法還是調(diào)試跟蹤裳涛,這時候路徑就出來了木张,具體怎么個調(diào)發(fā)請看我的手稿,比較粗糙端三。
最終發(fā)現(xiàn),最后的根源就是hander
的 removeCallbacksAndMessages
方法被調(diào)用了鹃彻。
因此是不存在內(nèi)存泄漏的郊闯,是這樣的嗎?蛛株?讓我們來在看一個例子团赁!
假如耗時任務本來就是一個異步任務呢?
跑幾秒鐘谨履,然后回到MainActivity欢摄,這時候你在GC一下,hprof文件導出看看笋粟,我去怀挠,泄漏了。
在看看控制臺害捕,我去绿淋,一直執(zhí)行到跑完。
然后等跑完了尝盼,在GC一下吞滞,在導出hprof文件看看,內(nèi)存泄漏依然還在盾沫,SecondActivity永久放入了你的內(nèi)存裁赠,知道APP進程死掉才會釋放殿漠。
不信的話,可以多啟動SecondActivity佩捞,退出SecondActivity幾次 绞幌,你會發(fā)現(xiàn)內(nèi)存不斷飆升~~
我繼續(xù)在思考,是不是這個耗時任務本身就丟在一個線程中執(zhí)行失尖,所以啊奄,如果我們rx不切換線程,是不是就不會泄露呢掀潮?
所以菇夸,還是不服,在改改代碼仪吧,繼續(xù)~
結(jié)果,并沒有什么卵用薯鼠,和上述情況一致择诈,多次啟動、關(guān)閉SecondActivity 出皇,你會發(fā)現(xiàn)內(nèi)存一樣會飆升羞芍,GC后,導出hprof
文件看看郊艘,一樣泄露了了荷科。
所以,大家應該懂了使用 rx的正確知識纱注,自己的任務都同步寫畏浆,線程切換交給Rx,因為Rx更懂你~~狞贱。