Android中使用Rxjava時,內存泄漏了嗎?
今天有位同學問了我一個問題,話說,問我
“有遇到網絡請求一半,退出Activity造成的Theard泄露嗎?已在銷毀時調用了un了
我去查看了下rx的源碼的unsubscribe方法,定位到一個實現類,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的話,可能還有后臺線程在做一些耗時任務。那么會不會造成內存泄露呢?
我覺得還是要源碼來說說話吧
/**
* 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中調用unsubscribe之后,是不會造成內存泄露的。但是真的是這樣的嗎?讓我們來做做實驗吧,結果說明一切。
為了能夠更好的證明這一點,我還特意做了一個app demo去驗證。
主要代碼:
demo地址已經放到了github上: 內存泄露分析
啟動app,首先進入的是MainActivity,然后,我們進入SecondActivity這時候,onresume執行,我們的任務也就開始了,稍微過幾秒,我們退出SecondActivity,回到MainActivity,這之后,顯然,SecondActivity的ondestory方法會被執行,我們可以發現日志也停止了打印。
耗時任務正在執行
如上圖所示,停留在了5就不在執行了。
這時候,我們GC一下,在導出hprof
文件,注意,為什么要手動GC一下呢?因為android虛擬機去GC也是有策略的,有GC周期的。這時候可能并沒有GC過,也就是說,SecondActivity的內存可能并沒有被釋放,但并不等于說,SecondActivity就泄露了,因為他也有可能是可以被GC的,只是還沒有來得及被GC而已。總之,在導出hprof
文件之前,最好先手動GC一下。就是下圖那個車子,嗯,點一下吧,放心點。
然后熟悉查看hprof
文件的同學這時候可能就看到了,執行分析之后,并沒有看到內存泄露。
從上圖我們看到SecondeActivity已經是紅色,標明被回收了,不存在內存泄漏。
同時,我調試跟蹤了一下unsubscribe
調用棧,因為是一堆抽象類及接口,又有一堆的實現類,所以,最效率的方法還是調試跟蹤,這時候路徑就出來了,具體怎么個調發請看我的手稿,比較粗糙。
最終發現,最后的根源就是hander
的 removeCallbacksAndMessages
方法被調用了。
因此是不存在內存泄漏的,是這樣的嗎??讓我們來在看一個例子!
假如耗時任務本來就是一個異步任務呢?
修改一下
跑幾秒鐘,然后回到MainActivity,這時候你在GC一下,hprof文件導出看看,我去,泄漏了。
真的泄漏了
在看看控制臺,我去,一直執行到跑完。
一直跑完了
然后等跑完了,在GC一下,在導出hprof文件看看,內存泄漏依然還在,SecondActivity永久放入了你的內存,知道APP進程死掉才會釋放。
不信的話,可以多啟動SecondActivity,退出SecondActivity幾次 ,你會發現內存不斷飆升~~
內存不斷飆升
我繼續在思考,是不是這個耗時任務本身就丟在一個線程中執行,所以,如果我們rx不切換線程,是不是就不會泄露呢?
所以,還是不服,在改改代碼,繼續~
rx不切換線程了,反正是在非主線程做耗時操作
結果,并沒有什么卵用,和上述情況一致,多次啟動、關閉SecondActivity ,你會發現內存一樣會飆升,GC后,導出hprof
文件看看,一樣泄露了了。
所以,大家應該懂了使用 rx的正確知識,自己的任務都同步寫,線程切換交給Rx,因為Rx更懂你~~。