Android中使用Rxjava時,內存泄漏了嗎?

kpns9444 8年前發布 | 20K 次閱讀 RxJava Android Android開發 移動開發

今天有位同學問了我一個問題,話說,問我

“有遇到網絡請求一半,退出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去驗證。

主要代碼:

你使用Rxjava時,內存泄漏了嗎?

你使用Rxjava時,內存泄漏了嗎?

demo地址已經放到了github上: 內存泄露分析

啟動app,首先進入的是MainActivity,然后,我們進入SecondActivity這時候,onresume執行,我們的任務也就開始了,稍微過幾秒,我們退出SecondActivity,回到MainActivity,這之后,顯然,SecondActivity的ondestory方法會被執行,我們可以發現日志也停止了打印。

你使用Rxjava時,內存泄漏了嗎?

耗時任務正在執行

如上圖所示,停留在了5就不在執行了。

這時候,我們GC一下,在導出hprof文件,注意,為什么要手動GC一下呢?因為android虛擬機去GC也是有策略的,有GC周期的。這時候可能并沒有GC過,也就是說,SecondActivity的內存可能并沒有被釋放,但并不等于說,SecondActivity就泄露了,因為他也有可能是可以被GC的,只是還沒有來得及被GC而已。總之,在導出hprof文件之前,最好先手動GC一下。就是下圖那個車子,嗯,點一下吧,放心點。

你使用Rxjava時,內存泄漏了嗎?

然后熟悉查看hprof文件的同學這時候可能就看到了,執行分析之后,并沒有看到內存泄露。

你使用Rxjava時,內存泄漏了嗎?

從上圖我們看到SecondeActivity已經是紅色,標明被回收了,不存在內存泄漏。

同時,我調試跟蹤了一下unsubscribe調用棧,因為是一堆抽象類及接口,又有一堆的實現類,所以,最效率的方法還是調試跟蹤,這時候路徑就出來了,具體怎么個調發請看我的手稿,比較粗糙。

最終發現,最后的根源就是handerremoveCallbacksAndMessages 方法被調用了。

你使用Rxjava時,內存泄漏了嗎?

你使用Rxjava時,內存泄漏了嗎?

因此是不存在內存泄漏的,是這樣的嗎??讓我們來在看一個例子!

假如耗時任務本來就是一個異步任務呢?

你使用Rxjava時,內存泄漏了嗎?

修改一下

跑幾秒鐘,然后回到MainActivity,這時候你在GC一下,hprof文件導出看看,我去,泄漏了。

你使用Rxjava時,內存泄漏了嗎?

真的泄漏了

在看看控制臺,我去,一直執行到跑完。

你使用Rxjava時,內存泄漏了嗎?

一直跑完了

然后等跑完了,在GC一下,在導出hprof文件看看,內存泄漏依然還在,SecondActivity永久放入了你的內存,知道APP進程死掉才會釋放。

不信的話,可以多啟動SecondActivity,退出SecondActivity幾次 ,你會發現內存不斷飆升~~

你使用Rxjava時,內存泄漏了嗎?

內存不斷飆升

我繼續在思考,是不是這個耗時任務本身就丟在一個線程中執行,所以,如果我們rx不切換線程,是不是就不會泄露呢?

所以,還是不服,在改改代碼,繼續~

你使用Rxjava時,內存泄漏了嗎?

rx不切換線程了,反正是在非主線程做耗時操作

結果,并沒有什么卵用,和上述情況一致,多次啟動、關閉SecondActivity ,你會發現內存一樣會飆升,GC后,導出hprof文件看看,一樣泄露了了。

所以,大家應該懂了使用 rx的正確知識,自己的任務都同步寫,線程切換交給Rx,因為Rx更懂你~~。


閱讀原文

 本文由用戶 kpns9444 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!