2018年7月24日 星期二

AsyncTask 的坑 -- memory leak

AsyncTask 是 Android 提供的一個讓開發者進行耗時操作的 helper class,他讓開發者很容易的將耗時操作放到 background thread 中執行,並且可以很容易的在 UI thread 中進行畫面更新

聽起來是個很有用的東西,但可惜的是它隱藏的問題其實也不少,其中一個就是它很容易造成 memroy leak

通常使用 AsyncTask 時,都會用 non-static inner class 來使用,因為這樣可以很方便的引用 outer class 裡面的欄位,例如下面一個簡單的例子
public class MainActivity extends Activity {
    private Object mMyObject;
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // Somewhere the AsyncTask is started
    public class MyAsyncTask extends AsyncTask<Void, Void, String> {
        @Override protected String doInBackground(Void... params) {
            // Do work
            longRunningProcess();
            return result;
        }

        @Override protected void onPostExecute(String result) {
            Log.d("Object: " + mMyObject.toString());
            Log.d("MyAsyncTask", "Received result: " + result);
        }
    }
}
但是,當你用 non-static inner class 來使用 AsyncTask 的時候,你就有可能造成 memroy leak,因為 non-static inner class 會隱式的持有對 outer 的 reference,例如在這個例子中 MyAsyncTask 就會持有 MainActivity 的 reference,若是在 longRunningProcess 函式操作途中 MainActivity 被銷毀的話,MainActivity 會因為被 MyAsyncTask 持有 reference 而無法被 GC 回收,造成 memory leak

那我們要怎樣避免 AsyncTask memory leak,就是不要使用 AsyncTask 將 AsyncTask 宣告為 static inner class,然後將外部的 activity 的 reference 當成參數傳進去,然後在 AsyncTask 內部用 WeakReference 來持有,並且在使用 activity reference 前檢查是否被回收了,如下
public class MainActivity extends Activity {
    private Object mMyObject;
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // Somewhere the AsyncTask is started
    public static class MyAsyncTask extends AsyncTask<Void, Void, String> {
        private WeakReference<MainActivity> mActivity;

        public MyAsyncTask(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override protected String doInBackground(Void... params) {
            // Do work
            longRunningProcess();
            return result;
        }

        @Override protected void onPostExecute(String result) {
            MainActivity activity = mActivity.get();
            if (activity == null || activity.isFinishing())
                return;

            Log.d("Object: " + mMyObject.toString());
            Log.d("MyAsyncTask", "Received result: " + result);
        }
    }
}
memory leak 只是 AsyncTask 的一個常見的坑之一,網路上找一找會發現它的坑還有很多,限制也不少,Android 本意是要提供一個好用方便的 class,讓開發者不用煩惱 background thread 跟 UI thread 交互作用的問題,專注於開發業務邏輯,但現在反而開發者要注意更多 AsyncTask 的坑與細節,看起來也並沒有減少多少開發者的工作量

沒有留言:

張貼留言