Android中使用Thread造成記憶體洩露的分析和解決

一葉飄舟發表於2016-05-13

Thread 記憶體洩露

執行緒也是造成記憶體洩露的一個重要的源頭。執行緒產生記憶體洩露的主要原因在於執行緒生命週期的不可控。

看一下下面是否存在問題

    public class ThreadActivity extends Activity {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new MyThread().start();
        }

        private class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                dosomthing();
            }
        }
        private void dosomthing(){

        }
    }

這段程式碼很平常也很簡單,是我們經常使用的形式。


真的沒有問題嗎

我們思考一個問題:假設MyThread的run函式是一個很費時的操作,當我們開啟該執行緒後,將裝置的橫屏變為了豎屏,
一般情況下當螢幕轉換時會重新建立Activity,按照我們的想法,老的Activity應該會被銷燬才對,然而事實上並非如此。
由於我們的執行緒是Activity的內部類,所以MyThread中儲存了Activity的一個引用,當MyThread的run函式沒有結束時,
MyThread是不會被銷燬的,因此它所引用的老的Activity也不會被銷燬,因此就出現了記憶體洩露的問題。

這種執行緒導致的記憶體洩露問題應該如何解決呢?

  1. 將執行緒的內部類,改為靜態內部類。
  2. 線上程內部採用弱引用儲存Context引用。

程式碼如下:

public class ThreadAvoidActivity extends Activity {
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                new MyThread(this).start();
            }

            private void dosomthing() {

            }

            private static class MyThread extends Thread {
                WeakReference<ThreadAvoidActivity> mThreadActivityRef;

                public MyThread(ThreadAvoidActivity activity) {
                    mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
                            activity);
                }

                @Override
                public void run() {
                    super.run();
                    if (mThreadActivityRef == null)
                        return;
                    if (mThreadActivityRef.get() != null)
                        mThreadActivityRef.get().dosomthing();
                    // dosomthing
                }
            }
        }

上面的兩個步驟其實是切換兩個物件的雙向強引用連結
靜態內部類:切斷Activity 對於 MyThread的強引用。
弱引用: 切斷MyThread對於Activity 的強引用。

AsynTask 內部類會如何呢?

有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,
Thread只有在run函式不結束時才出現這種記憶體洩露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,
該類產生的Thread物件的生命週期是不確定的,是應用程式無法控制的,
因此如果AsyncTask作為Activity的內部類,就更容易出現記憶體洩露的問題。

在看android聯絡人2.3原始碼的時候看到一個類WeakAsyncTask, 內部用到了WeakReference軟引用, 這樣可以解決記憶體洩露的問題; 其實給AysncTask加上static, 靜態的內部類不會持有對外部類的引用, 就能夠解決問題, 只不過這樣如果AysncTask的方法用到的成員變數都需要加上static;

WeakReference和AsyncTask的美妙結合

為了避免開發者在UI執行緒上做耗時操作,Android提供了不少非同步API,其中之一就是AsyncTask。而對於某些頻繁運算元據庫的應用(例如,Phonebook)而言,需要一種非同步的並且低耗資源的(低耗是兩個方面的事情,要麼是你佔有的多點,但是能快速釋放;要麼是你本身就佔有的少。這兩種都可以保證其它應用有資源可用)元件。所以,那就來個WeakAsyncTask吧,唔,美妙的產物,既保證佔有資源的快速釋放,又保證操作是非同步進行。
那為什麼不是soft reference呢?
這個,來看看weak和soft兩者的區別:

* A SoftReference should be cleared and enqueued as late as possible, that is, in case the VM is in danger of running out of memory.
* A WeakReference may be cleared and enqueued as soon as is known to be weakly-referenced.

下面是WeakAsyncTask的原始碼:

public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
    extends AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;

public WeakAsyncTask(WeakTarget target) {
    mTarget = new WeakReference<WeakTarget>(target);
}

@Override
protected final void onPreExecute() {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        this.onPreExecute(target);
    }
}

@Override
protected final Result doInBackground(Params... params) {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        return this.doInBackground(target, params);
    } else {
        return null;
    }
}

@Override
protected final void onPostExecute(Result result) {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        this.onPostExecute(target, result);
    }
}

protected void onPreExecute(WeakTarget target) {
    // Nodefaultaction
}

protected abstract Result doInBackground(WeakTarget target,
        Params... params);

protected void onPostExecute(WeakTarget target, Result result) {
    // Nodefaultaction
}
}

使用也很簡單, 用法也幾乎和AsyncTask一樣:

private static class MyTask extends WeakAsyncTask<Void, Void, String, MainActivity> {
        public MyTask(MainActivity target) {
            super(target);
        }

        @Override
        protected String doInBackground(MainActivity target, Void... params) { // 獲取context,
            // 執行一些操作
            Context context = target;
            return "Hello Android    !!!!!";
        }

        @Override
        protected void onPostExecute(MainActivity target, String s) {
            // 執行操作
        }
    }

直接呼叫執行:

new MyTask(this).execute(); 

其實還可以根據這個思想寫一個WeakHandler!
Android Weak Handler:可以避免記憶體洩漏的Handler庫
http://blog.csdn.net/jdsjlzx/article/details/51388862

本文部分來自:http://blog.csdn.net/zhuanglonghai/article/details/37909553

相關文章