用正確的姿勢來說說Android上的記憶體洩漏問題
相信大家對App的記憶體管理都是相當關心的,在專案上線前的幾天時間也會藉助相關工具突擊下嚴重的洩漏問題;
針對記憶體洩漏先提出幾個疑問:
- 什麼是記憶體洩漏?
- 記憶體洩漏帶來的危害又是什麼?
- 哪些程式碼的寫法會導致記憶體洩漏?
- 如何找到洩露的地方,以及如何修復它?
一、什麼是記憶體洩漏?
- 無用的物件沒有被及時釋放引用,導致GC無法回收,就有可能出現記憶體洩漏。
二、記憶體洩漏帶來的危害是什麼?
- 記憶體洩漏會增加記憶體佔用和OOM機率。
三、哪些程式碼的寫法會導致記憶體洩漏?
- 常見五種導致 APP 記憶體洩漏的地方
- 靜態 Activity
洩漏 activity 最簡單的方法就是在 activity 類中定義一個 static 變數,並且將其指向一個執行中的 activity 例項。如果在 activity 的生命週期結束之前,沒有清除這個引用,那它就會洩漏了。這是因為 activity(例如 MainActivity) 的類物件是靜態的,一旦載入,就會在 APP 執行時一直常駐記憶體,因此如果類物件不解除安裝,其靜態成員就不會被垃圾回收。
- 靜態 Activity
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”
洩露的具體資訊如圖:
- 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的程式碼編輯頁就能開啟該檔案,然後就可以漫漫的去品味它了