1. 程式人生 > >用正確的姿勢來說說Android上的記憶體洩漏問題

用正確的姿勢來說說Android上的記憶體洩漏問題

相信大家對App的記憶體管理都是相當關心的,在專案上線前的幾天時間也會藉助相關工具突擊下嚴重的洩漏問題;

針對記憶體洩漏先提出幾個疑問:

  • 什麼是記憶體洩漏?
  • 記憶體洩漏帶來的危害又是什麼?
  • 哪些程式碼的寫法會導致記憶體洩漏?
  • 如何找到洩露的地方,以及如何修復它?

一、什麼是記憶體洩漏?

  • 無用的物件沒有被及時釋放引用,導致GC無法回收,就有可能出現記憶體洩漏。

二、記憶體洩漏帶來的危害是什麼?

  • 記憶體洩漏會增加記憶體佔用和OOM機率。

三、哪些程式碼的寫法會導致記憶體洩漏?

  • 常見五種導致 APP 記憶體洩漏的地方
    1. 靜態 Activity
      洩漏 activity 最簡單的方法就是在 activity 類中定義一個 static 變數,並且將其指向一個執行中的 activity 例項。如果在 activity 的生命週期結束之前,沒有清除這個引用,那它就會洩漏了。這是因為 activity(例如 MainActivity) 的類物件是靜態的,一旦載入,就會在 APP 執行時一直常駐記憶體,因此如果類物件不解除安裝,其靜態成員就不會被垃圾回收。
    void setStaticActivity() {
      activity = this;
    }

    View saButton = findViewById(R.id.sa_button);
    saButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
            setStaticActivity();
            nextActivity();
      }
    });
 ![這裡寫圖片描述](https://img-blog.csdn.net/20160712180226946)

2. 內部類或者匿名內部類
類似的,匿名類同樣會持有定義它們的物件的引用。因此如果在 activity 內定義了一個匿名的 AsyncTask 物件,就有可能發生記憶體洩漏了。如果 activity 被銷燬之後 AsyncTask 仍然在執行,那就會組織垃圾回收器回收 activity 物件,進而導致記憶體洩漏,直到執行結束才能回收 activity。

    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground
(Void... params) { while(true); } }.execute(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); nextActivity(); } });
 ![這裡寫圖片描述](https://img-blog.csdn.net/20160712181354592)

3. Handlers
同樣的,定義一個匿名的 Runnable 物件並將其提交到 Handler 上也可能導致 activity 洩漏。Runnable 物件間接地引用了定義它的 activity 物件,而它會被提交到 Handler 的 MessageQueue 中,如果它在 activity 銷燬時還沒有被處理,那就會導致 activity 洩漏了。

    void createHandler() {
        new Handler() {
            @Override public void handleMessage(Message message) {
                   super.handleMessage(message);
                }
            }.postDelayed(new Runnable() {
                @Override public void run() {
                        while(true);
                }
          }, Long.MAX_VALUE >> 1);
    }

    View hButton = findViewById(R.id.h_button);
    hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
        nextActivity();
        }
});
 ![這裡寫圖片描述](https://img-blog.csdn.net/20160712183514777)

4. EditText
EditText所處的上下文物件會一直保留在(4.0.x,4.1.x,4.2.x)的系統版本中。大家在用著它這麼長的時間不知道系統還有這麼個bug(具體可參考:https://dddpaul.github.io/blog/2014/08/01/android-memory-leaks/),但好在Android 4.3, API 18的系統版本上修復了該問題,但是對於18以下的就要注意啦,需要做些特殊處理,不然就會導致洩漏的,其實解決辦法也是很簡單:修改EditText的android:inputType= “textNoSuggestions”
洩露的具體資訊如圖:
洩露資訊

  1. Threads
    同樣的,使用 Thread 和 TimerTask 也可能導致 activity 洩漏。
void spawnThread() {
        new Thread() {
            @Override public void run() {
                  while(true);
            }
        }.start();
    }

    View tButton = findViewById(R.id.t_button);
    tButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
          spawnThread();
          nextActivity();
      }
    });
 ![這裡寫圖片描述](https://img-blog.csdn.net/20160712190045847)

注: 還有就是成對出現的方法或者頁面銷燬該銷燬的銷燬,該清空的清空

四、如何找到洩露的地方,以及修復它?

  • 藉助相關工具,比如當下使用較多的LeakCanary,mat;

    1.LeakCanary的用法見:http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
    2.LeakCanary的github地址:https://github.com/square/leakcanary
    以上兩個地址都有詳細的介紹LeakCanary的使用和常見問題解答等功能,相信聰明的你們,很容易就能理解
    3.如果想深入分析洩漏原因的同學,需要Dump HPROF file,先將hprof檔案pull到自己的電腦上,然後將檔案直接拖到android studio的程式碼編輯頁就能開啟該檔案,然後就可以漫漫的去品味它了