根本原因
應該被回收的對象沒有被回收,一直佔用內存,導致內存泄漏,如果內存泄漏過多,有可能造成內存溢出(OOM)。
內存溢出
當內存泄漏過多時,應用需要的內存超過系統分配的內存限額時會導致內存溢出引發crash。
常見的內存泄漏
1、單例(靜態變量引起)
如果單例需要持有一個上下文,而如果傳入的上下文是短生命週期的activity,單例的生命週期跟app一致,是長生命週期的上下文持有短生命週期的實例。
解決辦法:將單例持有的上下文換成applicationcontext。總之:傳入的上下文生命週期不能短於單例的生命週期。比較常見,不發代碼了。
2、非靜態內部類或匿名內部類
在Java中非靜態內部類或匿名內部類會隱式持有外部類實例。當匿名內部類進行耗時操作,在外部類已經銷燬時還在持有,就會導致外部類無法被回收造成內存泄漏。
例如:
1、最經典的就是Handler導致的內存泄漏。 非靜態內部類,做耗時操作一直持有外部類的實例,當外部類銷燬時無法被回收。
2、創建的匿名線程類,隱式持有外部類的實例,線程做耗時操作,當外部類銷燬時線程還未銷燬,因持有外部類實例外部類無法被回收造成內存泄漏。
3、非靜態內部類,在外部類中創建了靜態實例,該情況類似於 5、靜態變量。
**總之:**也就是說當非靜態內部類或匿名內部類的存活時間長於持有的外部類的時間就可能出現內存泄漏。
示例1及解決方案:Handler使用靜態內部類,並若引用外部activity。詳見博客地址:
示例2:
匿名內部類創建線程,並在線程內執行耗時操作。
public class MainActivit extents Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread leakClass = new MyThread();
MyThread.start();
}
class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//或者類似這樣的匿名內部類
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
解決辦法1:在Activity銷燬時停止線程的運行,讓它釋放外部類。
class MyThread extends Thread {
@Override
public void run() {
while (flag) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//在onDestory方法中將flag置爲false
@Override
protected void onDestory(){
super.onDestory();
flag = false;
}
解決辦法2:將匿名內部類定義爲靜態內部類,因爲靜態內部類不會持有外部類的引用。
static class MyThread extends Thread {
@Override
public void run() {
while (flag) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
解決方案總結:
- 通過邏輯判斷,比如在外部類銷燬時,停止耗時操作,讓外部類在合適的時候被回收。
- 在外部類銷燬時移除延時消息及runable。mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。
- 修改爲靜態內部類,靜態內部類不會持有外部類的引用,外部類可以被隨意回收,但也因此內部類無法操作外部類的對象,所以需要增加對外部類的弱引用。
3、廣播、使用的三方框架未及時關閉
如廣播的unregisterReceiver
private void unRegistReceiver(BroadcastReceiver receiver){
try {
getContext().unregisterReceiver(receiver);
} catch (IllegalArgumentException e) {
// ignore
}
}
4、IO流,數據庫的cursor、ContentObserver等資源沒有在Activity銷燬時及時關閉,bitmap沒有recycle(),導致資源無法會回收。
5、靜態變量
當一個靜態變量持有靜態上下文時,持有的上下文銷燬了,但由於還被靜態變量持有導致不能被回收造成內存泄漏。例如:
private static Context context;
private static TextView textView;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(context);
findViewById(R.id.skip).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,TestLeakActivity.class));
finish();
}
});
}
通過LeakCanary通知,MainActivity.context發生內存泄漏。
解決:不讓靜態變量持有短生命週期的上下文。
private Context context;
private TextView textView;
6、動畫無限播放
當activity中有無限播放的屬性動畫,當activity銷燬時,動畫還在播放,此時view持有activity的引用導致無法被回收引發內存泄漏。
ObjectAnimator animator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
以上程序在測試時,體會GC being lazy ,是有可能造成內存泄漏。
爲保險起見,在activity銷燬時停止動畫的播放。
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
animator.cancel();
}
檢測內存泄漏的工具
1、Android Profiler
AS自帶的內存分析工具,一般用來檢測是否發生了內存泄漏,如果要定位內存泄漏需要一個一個查找,比較麻煩。用法如下:
- 啓動應用,在AS下方點擊Android profiler
其他步驟見博客:https://blog.csdn.net/MarinaTsang/article/details/84786577
2、LeakCanary
見博客:https://blog.csdn.net/MarinaTsang/article/details/84786577
3、MAT(Memory analyst tool)
詳見郭霖大神博客:https://blog.csdn.net/guolin_blog/article/details/42238633。非常詳細。
另外Mac版的MAT可能打開提示錯誤,不要放在Download目錄下即可。
.hrof 文件在Android profiler中導出即可。
看了很多篇文章,就不一一列舉了,比如:
https://blog.csdn.net/wanghao200906/article/details/50426881