0. 前言
转载请注明出处:Android开发——常见的内存泄漏以及解决方案(一)_SEU_Calvin的博客-CSDN博客
Android的内存泄漏是Android开发领域永恒的话题,那今天就总结一下常见的内存泄漏吧。
在Android Studio里可以通过一些分析工具比如MAT来找出潜在的内存泄漏,在Android Device Monitor中进行Dump HPROF File,并对这个文件在SDK的platform-tools目录下进行<hprof-conv infilePath outfileName>即可在platform-tools目录下生成使用MAT工具查看的hprof文件。
如果不想这么麻烦,可以使用LeakCanary进行自动化的OOM检测。使用起来非常简单,具体可以查看LeakCanary中文使用说明。
//LeakCanary原理总结
//1. 使用ActivityLifecycleCallbacks监控所有Activity的onDestory(),将该Activity添加到内存泄漏监控队列中
//2. 在后台线程检查引用是否被清除,如果没有,调用GC
//3. 如果引用还是未被清除,把heap内存dump到一个 .hprof 文件中
//4. 使用一个独立进程中的服务计算出到GC Roots的最短强引用路径来判断是否发生Leak
//5. 建立导致泄漏的引用链,结果在Log中打印出来
此篇将从静态变量引用Activity、匿名类、内部类、Handler、以及监听器等方面的实例说明内存泄漏的发生原因以及解决方案。
1. 单例模式导致内存泄漏(实质是静态变量引用Activity)
如果不了解单例模式的小伙伴可以查看我之前写过的设计模式——单例模式解析。已经对单例模式分析的很清楚了。这里就不多赘述了。
public class SingleUtils {
private static SingleUtils mInstance = null;
private Context context;
private SingleUtils (Context context){
this.context = context;
}
public static SingleUtils getInstance(Context context){
if(mInstance == null){
mInstance = new SingleUtils (context);
}
return mInstance;
}
public Object getObject(){//根据业务逻辑传入参数
//返回业务逻辑结果,这里需要用到context
}
}
单例由于它的静态特性使得其生命周期跟应用一样长,如果我们把上下文context(比如说一个Activity)传入到了单例类中的执行业务逻辑,这时候静态变量就引用了我们的Activity,如果没有及时置空,就会在这个Activity finish的时候,导致该Activty一直驻留在内存中,并发生内存泄漏。
解决方案:
在单例中我们尽可能的引用生命周期较长的对象,将第10行代码修改如下即可。
mInstance = new SingleUtils (context.getApplicationContext());
2. 内部类导致内存泄漏
如果我们在一个外部类中定义一个静态变量,这个静态变量是非静态内部类对象,这就会导致内存泄漏,因为非静态内部类会持有外部类的引用,从而间接导致静态地引用了外部类。
public class MyActivity extends Activity {
//非静态内部类InnerClass创建的静态实例mInnerClass
private static InnerClass mInnerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mInnerClass = new InnerClass();
}
class InnerClass{
}
}
解决方案:
(1)在onDestroy方法中手动将mInnerClass置为null。
(2)将内部类定义为静态内部类,使其不能与外部类建立关系。
3.匿名内部类导致内存泄漏
匿名内部类同样会持有一个外部类的引用,比如说在Activity的onCreate()方法中定义了一个匿名的AsyncTask对象。如果Activity被销毁之后AsyncTask仍然在执行,那就会阻止垃圾回收器回收Activity对象,进而导致内存泄漏,直到执行结束才能回收Activity。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
//子线程中持有Activity的引用
//子线程在Activity销毁后依然会继续执行,导致该Activity内存泄漏
while (true) ;
}
}.execute();
}
解决方案:
(1)在onDestroy中中断子线程的运行。
(2)使用全局的线程池代替在类中创建子线程。
4.Handler导致内存泄漏
如下所示我们在Activity中定义了一个handler,然后在Activity的onCreate()方法中,发送了一条延迟消息。
当Activity finish的时候,延时消息还保存在主线程的消息队列里。而且,这条消息持有对handler的引用,而handler又持有对Activity引用。这条引用关系会保持到消息被处理,从而,这就阻止了Activity被垃圾回收器回收,造成内存泄漏。
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, Integer.MAX_VALUE);
}
解决方案:
如果一个内部类实例的生命周期比Activity更长,那么我们就不要使用非静态的内部类。最好的做法是使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。
public class SampleActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
5.监听器导致内存泄漏
很多系统服务负责执行某些后台任务。如果context对象想要在服务事件发生时被通知,就需要把自己注册到服务的监听器中。这就会让服务持有Activity的引用,如果忘记在Activity销毁时取消注册,那就会导致Activity泄漏了。
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
View mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
}
});
6. 使用IntentService
如果Service停止失败也会导致内存泄漏。
因为系统会倾向于把这个Service所依赖的进程进行保留,如果这个进程很耗内存,就会造成内存泄漏。
解决方案:
所以推荐使用IntentService,它会在后台任务执行结束后自动停止,从而避免了Service停止失败导致发生内存泄漏的可能性。
7. Adapter中引用了Activity如何避免内存泄漏
有时需要点击ListView条目里的某个按钮实现界面跳转,getView()方法inflate布局的时候需要上下文,而且点击按钮后的跳转逻辑也需要上下文。所以我们经常会把Activity传入到Adapter中,如果Adapter中有很多耗时操作,可能就会防止Activity finish的时候被回收。
解决方案:
inflate布局是可以使用getView()方法里的parent参数的,通过parent.getActivity()获得上下文。
跳转逻辑的话,可以使用接口回调的方式,在Activity中实现这个接口并且覆写里面的方法,跳转逻辑就在这个回调方法里进行。
具体可以查看Android开发——告诉你Adapter应该写在Activity里面还是外面。
下一篇将会从资源角度来说明内存泄漏的问题。