天天看點

記憶體優化(二)如何避免記憶體洩漏

文章目錄

    • 一、不同生命周期導緻的記憶體洩漏
        • 解決辦法
    • 二、非靜态内部類持有對象導緻的記憶體洩漏
        • 1. 非靜态内部類調用外部類的方法的
        • 2. 内部類是如何持有外部類對象?
        • 3. 如何處理非靜态内部類記憶體洩漏問題

一、不同生命周期導緻的記憶體洩漏

前面有分析了記憶體洩漏的原因,本該被回收的對象被占用,得不到回收便會記憶體洩漏。總歸到底的原因還是對象引用在類之間傳遞,它們的生命周期不同,導緻回收時發生問題。

舉個簡單的例子:

當單列模式中傳入的Activity是,ToastRouter便持有了MainActivity的強引用,當MainActivity結束時,便得不到回收,這是記憶體洩漏發生了

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ToastRouter.getInstance(this);
    }
}


...


public class ToastRouter {
    private static final ToastRouter ourInstance = new ToastRouter();
    private Context mContext;

    public static ToastRouter getInstance(Context context) {
		this.mContext=context;
        return ourInstance;
    }

    private ToastRouter() {
    }

}
           

解決辦法

  • 盡量避免生命周期不對等的對象引用傳遞,如果避免不了,應用對等生命周期的對象替代。如上Activity傳入,可以用ApplicationContext傳遞代替,因為ApplicationContext的生命周期與APP對等。
  • 與對象生命周期綁定釋放,如上例子,應該在MainActivity onDestary()方法中釋放傳入對象引用

二、非靜态内部類持有對象導緻的記憶體洩漏

非靜态内部類持有對象導緻的記憶體洩漏也很好了解,就是内部類持有了外部類的對象,導緻的外部類回收失敗造成的記憶體洩漏

1. 非靜态内部類調用外部類的方法的

  • 看一個簡單的JAVA代碼案例
    • 通過匿名内部類,内部類分别列印一個延時log日志。
    • 其中分别用的AsyncTask和Handler舉例
      /**
         * 通過匿名内部類,列印一個1.5s延時log
         */
        public void doAnonymousInnerClassTask() {
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    //睡眠1.5s
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    logMsg(" AsyncTask 匿名内部類 doInBackground");
                    return null;
                }
            }.execute();
        }
      
        /**
         * 通過内部類,列印一個1.5s延時log
         */
        public void doNormalInnerTask() {
            TestAsyncTask testAsyncTask = new TestAsyncTask();
            testAsyncTask.execute();
        }
        
        private class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
      
            @Override
            protected Void doInBackground(Void... voids) {
      
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logMsg(" TestAsyncTask内部類 doInBackground");
                return null;
            }
        }
       	
        private void logMsg(String msg) {
            Log.d(TAG, msg);
        }
                 
  • 我們通過.class檔案,檢視它的對象引用發現,在匿名内部類和内部類中,調用MainActivity的logMsg方法時,已持有了MainActivity.this的引用,是以當非靜态内部類生命周期比MainActivity長時,即發生了記憶體洩漏。
    public void doAnonymousInnerClassTask() {
          (new AsyncTask<Void, Void, Void>() {
              protected Void doInBackground(Void... voids) {
                  try {
                      Thread.sleep(1500L);
                  } catch (InterruptedException var3) {
                      var3.printStackTrace();
                  }
    
                  MainActivity.this.logMsg(" AsyncTask 匿名内部類 doInBackground");
                  return null;
              }
          }).execute(new Void[0]);
      }
    
      private class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
          private TestAsyncTask() {
          }
    
          protected Void doInBackground(Void... voids) {
              try {
                  Thread.sleep(1500L);
              } catch (InterruptedException var3) {
                  var3.printStackTrace();
              }
    
              MainActivity.this.logMsg(" TestAsyncTask内部類 doInBackground");
              return null;
          }
      }
               

2. 内部類是如何持有外部類對象?

上述通過.class檔案可以看到内部類在調用外部類的方法時,通過持有的外部類對象去調用。那麼外部類對象的引用是什麼時候傳入的呢。通過

參考文章:深入了解Java中為什麼内部類可以通路外部類的成員得到結論:

  • 編譯器自動為内部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用;
  • 編譯器自動為内部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法内部使用這個參數為1中添加的成員變量指派;
  • 在調用内部類的構造函數初始化内部類對象時, 會預設傳入外部類的引用。

由此,即使内部類并沒有調用外部類的方法或者變量,也一樣會持有外部類的引用。

3. 如何處理非靜态内部類記憶體洩漏問題

  1. 可以使用,但是前提是確定非靜态内部類的生命周期超過外部類
  2. 使用靜态内部類,在靜态内部類需要持有外部類引用時,通過關聯外部類的弱引用去調用。
    /**
      * 通過靜态内部類列印一個1.5s延時log
      */
     public void doStaticInnerClassTask() {
         mHandler.sendEmptyMessageDelayed(0, 1500);
     }
    
     private final StaticHandler mHandler = new StaticHandler(this);
    
     private static class StaticHandler<T> extends Handler {
    
         protected WeakReference<T> ref;
    
         public StaticHandler(T cls) {
             ref = new WeakReference<T>(cls);
         }
    
         public T getRef() {
             return ref != null ? ref.get() : null;
         }
    
         @Override
         public void handleMessage(Message msg) {
             super.handleMessage(msg);
             MainActivity _Activity = (MainActivity) ref.get();
             _Activity.logMsg("StaticHandler 靜态内部類log ");
         }
     }
               
  3. 在外部類生命周期結束前自主釋放外部類的引用

繼續閱讀