Android内存泄漏方式及解决方案
文章目录
常见内存泄漏
1、非静态内部类/匿名类默认持有外部类的引用(例如:Handler、AsyncTask、Thread)
- 解析(匿名类[Handler、Thread] 非静态内部类[AsyncTask])
public class TestActivity extends Activity{
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ① 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 发送耗时消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
// ③ 非静态内部类 MyAscnyTask 进行耗时操作
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
上面通过在Activity中操作:
-
① new 一个匿名线程进行耗时操作
-
② new 一个匿名Handler发送一个延迟消息
-
③ new 一个非静态MyAscnyTask进行耗时操作
当Activity被销毁后GC依然无法回收Activity,因为 ①匿名线程类、②匿名Handler(或者是非静态Handler)、③非静态MyAscnyTask 都会持有外部Activity的一个引用,生命周期较长的对象持有生命周期短的对象,导致GC无法回收造成内存泄漏。
而Handler为常见的线程间通信成员之一,主要用来发送和处理消息,其中在sendMessage发送消息时候最终会调用内部通过 enqueueMessage 方法将消息添加到消息队列 MessageQueue:
mHandler.sendMessage(msg);//发送消息,具体流程如下
- sendMessage(Message msg)
- endMessageDelayed(Message msg, long delayMillis)
- sendMessageAtTime(Message msg, long uptimeMillis)
- enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
其中 enqueueMessage 源码如下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1. 把 当前的Handler实例对象作为msg的target属性,这里可以看到Message持有了Handler的引用
msg.target = this;
// 2. 调用消息队列的enqueueMessage()
// 即:Handler发送的消息,最终是保存到消息队列
return queue.enqueueMessage(msg, uptimeMillis);
}
参考:https://www.jianshu.com/p/b4d745c7ff7a
可见在 enqueueMessage中Message持有了Handler的引用,而Handler持有TestActivity的引用,因此如果TestActivity被销毁,但是其引用一致存在于MessageQueue中的Message中,所以无法被GC回收,最终造成内存泄漏。
-
解决方案
-
匿名线程改为 继承 Thread 的静态内部类方式。
-
匿名(非静态内部)类Handler改为继承 Handler的静态内部类方式(同时将外部类使用弱引用(WeakReference)方式提供给Handler),以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
-
非静态内部类改为静态内部类。
-
public class TestActivity extends Activity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ① 线程更改为静态内部类线程
new MyThread().start();
// ② 匿名Handler更改为静态内部类Handler,并使用弱引用提供Context。
Message message = Message.obtain();
new MyHandler(this).sendMessageDelayed(message, 60000);
// ③ 非静态内部类 MyAscnyTask 更改为静态内部类
new MyAscnyTask().execute();
}
@Override
protected void onDestroy() {
super.onDestroy();
//Activity销毁时候清空Handler的消息和回调
myHandler.removeCallbacksAndMessages(null);
}
//静态内部类线程
static class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//静态内部类Handler
static class MyHandler extends Handler {
WeakReference<SplashActivity> weakReference;
public MyHandler(SplashActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
}
}
//静态内部类 MyAscnyTask
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
2、单例模式持有外部类的引用
- 解析
public class TestSingle {
private static TestSingle testSingle = null;
private Context context;
private TestSingle(Context context) {
this.context = context;
}
public static TestSingle newInstance(Context context) {
if (testSingle == null) {
synchronized (TestSingle.class) {
if (testSingle == null) {
testSingle = new TestSingle(context);
}
}
}
return testSingle;
}
}
上面为单例模式,其中需要传入Context作为构造单例实例的参数,如果Context是一个Activity,当Activity退出后,GC依然无法回收,因为单例持有此Activity的引用,直到进程退出,这样就造成内存泄漏。
-
解决方案
-
使用全局Application的Context取代(如果Context允许使用Application的Context的话)
public class TestSingle { private static TestSingle testSingle = null; private Context context; private TestSingle(Context context) { //这里使用context.getApplicationContext()获取Application的Context this.context = context.getApplicationContext(); } public static TestSingle newInstance(Context context) { if (testSingle == null) { synchronized (TestSingle.class) { if (testSingle == null) { testSingle = new TestSingle(context); } } } return testSingle; } }
-
如果Context不可使用Application的Context替代,那么使用弱引用方式来提供Context
public class TestSingle { private static TestSingle testSingle = null; WeakReference<Context> weakReference; private TestSingle(Context context) { //使用弱引用方式来提供Context weakReference = new WeakReference<>(context); } public static TestSingle newInstance(Context context) { if (testSingle == null) { synchronized (TestSingle.class) { if (testSingle == null) { testSingle = new TestSingle(context); } } } return testSingle; } public void test(){ //weakReference.get() TODO... } }
-
3、静态成员变量持有外部类的引用
- 解析
public class B{
private static Activity mActivity;
public static b(Activity activity){
this.mActivity = activity;
//mActivity TODO...
}
}
如上:B类中的静态成员变量mActivity持有了外部类Activity的引用,因此这个静态成员变量会一致存在进程中,造成内存泄漏。
-
解决方案
-
取消类中静态成员变量引用,方法中直接使用外部类
public class B{ public static b(Activity activity){ //activity TODO... } }
-
如果非要引用外部类,使用弱引用方式提供
public class B{ private WeakReference weakRefrence; public B(Activity activity){ //使用弱引用方式提供Activity weakRefrence = new WeakReference<>(activity); } public void b(){ Activity activity = weakRefrence.get(); //activity TODO... } }
-
4、集合类
-
解析
static List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object obj = new Object(); objectList.add(obj); obj = null; } // 虽释放了集合元素引用的本身:obj=null // 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
上面为一个循环每次new一个对象添加到静态集合List中,因为静态变量生命周期一直会维持到进程结束,因此他内部所引用的对象也不能释放,这样便造成了内存泄漏。
-
解决方案
在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。
objectList.clear(); objectList = null;
5、资源对象使用后未关闭
-
解析
- 网络、文件等流忘记关闭
- 手动注册广播时,退出时忘记 unregisterReceiver()
- Service 执行完后忘记 stopSelf()
- EventBus 等观察者模式的框架忘记手动解除注册
-
解决方案
-
对于 广播BraodcastReceiver
注销注册 unregisterReceiver()
-
对于 文件流File
关闭流 InputStream / OutputStream.close()
-
对于数据库游标cursor
使用后关闭游标 cursor.close()
-
对于 图片资源Bitmap
当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
Bitmap.recycle(); Bitmap = null;
-
-
ListView 的 Item 泄露
-
WebView造成的内存泄漏
6、其他情况
除了上述常见情况,还有一些日常的使用会导致内存泄露
主要包括:Context
、WebView
、Adapter
,具体介绍如下
总结
下面,我将用一张图总结Android
中内存泄露的原因 & 解决方案
内存泄露的分析工具
哪怕完全了解 内存泄露的原因,但难免还是会出现内存泄露的现象
下面将简单介绍内存泄露的分析工具
Android Lint
Lint 是 Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码机构 / 质量问题,同时提供一些解决方案,检测内存泄露当然也不在话下,使用也是非常的简单,可以参考下这篇文章:Android 性能优化:使用 Lint 优化代码、去除多余资源
Leakcanary
LeakCanary 是 Square 公司开源的「Android 和 Java 的内存泄漏检测库」,Square 出品,必属精品,功能很强大,使用也很简单。建议直接看 Github 上的说明:leakcanary,也可以参考这篇文章:Android内存优化(六)LeakCanary使用详解 或者 LeakCanary: 让内存泄露无所遁形