2018年7月24日 星期二

AsyncTask 的坑 -- memory leak

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

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

通常使用 AsyncTask 時,都會用 non-static inner class 來使用,因為這樣可以很方便的引用 outer class 裡面的欄位,例如下面一個簡單的例子
  1. public class MainActivity extends Activity {
  2. private Object mMyObject;
  3. @Override protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. }
  7.  
  8. // Somewhere the AsyncTask is started
  9. public class MyAsyncTask extends AsyncTask<Void, Void, String> {
  10. @Override protected String doInBackground(Void... params) {
  11. // Do work
  12. longRunningProcess();
  13. return result;
  14. }
  15.  
  16. @Override protected void onPostExecute(String result) {
  17. Log.d("Object: " + mMyObject.toString());
  18. Log.d("MyAsyncTask", "Received result: " + result);
  19. }
  20. }
  21. }
但是,當你用 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 前檢查是否被回收了,如下
  1. public class MainActivity extends Activity {
  2. private Object mMyObject;
  3. @Override protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. }
  7.  
  8. // Somewhere the AsyncTask is started
  9. public static class MyAsyncTask extends AsyncTask<Void, Void, String> {
  10. private WeakReference<MainActivity> mActivity;
  11.  
  12. public MyAsyncTask(MainActivity activity) {
  13. mActivity = new WeakReference<>(activity);
  14. }
  15.  
  16. @Override protected String doInBackground(Void... params) {
  17. // Do work
  18. longRunningProcess();
  19. return result;
  20. }
  21.  
  22. @Override protected void onPostExecute(String result) {
  23. MainActivity activity = mActivity.get();
  24. if (activity == null || activity.isFinishing())
  25. return;
  26.  
  27. Log.d("Object: " + mMyObject.toString());
  28. Log.d("MyAsyncTask", "Received result: " + result);
  29. }
  30. }
  31. }
memory leak 只是 AsyncTask 的一個常見的坑之一,網路上找一找會發現它的坑還有很多,限制也不少,Android 本意是要提供一個好用方便的 class,讓開發者不用煩惱 background thread 跟 UI thread 交互作用的問題,專注於開發業務邏輯,但現在反而開發者要注意更多 AsyncTask 的坑與細節,看起來也並沒有減少多少開發者的工作量

沒有留言:

張貼留言