Android常见的内存泄漏
1、内部类导致内存泄漏
1、内部类实例会隐式持有外部类的引用。
例如在Activity中创建一个内部类实例,然后在内部类实例中执行耗时操作,在执行任务时,关掉Activity,这时Activity对象不会被释放,因为内部类还持有对Activity的引用,但此时Activity已经没用了,所以这时就出现了内存泄漏。
1.1 Thread线程
在Activity中创建一个内部类继承Thread,在该Thread中执行一些后台任务,未执行完时,关闭Activity,此时会出现内存泄漏。
如何解决:避免内部类隐式调用外部类Activity即可。
解决方案:
把这个内部类声明为静态类,这时内部类将不再隐式持有外部类Activity的引用。此时内部类也无法使用外部类中的方法、变量(只能引用静态的方法和变量)
问题:如何在防止内存泄漏的情况下,去访问外部类中的非静态方法、变量??
解决办法:
通过软引用或者弱引用的方式引用外部类Activity
Java强引用,软引用,弱引用,虚引用
1.2 Handler
public class MainActivity extends AppCompatActivity {
private static final int MESSAGE_DELAY = 0;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startDelayTask();
}
});
}
private void startDelayTask() {
//发送一条消息,该消息会被延时10秒后才处理
Message message = Message.obtain();
message.obj = "按钮点击15秒后再弹出";
message.what = MESSAGE_DELAY;
mHandler.sendMessageDelayed(message, 15000);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_DELAY:
Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
mButton.setText("延时修改了按钮的文本");
break;
}
}
};
}
在sendMessageDelayed()发送消息时,延时时间过长,在中途退出Activity,会出现内存泄漏,出现的原因也是因为内部类持有外部类的引用。
解决方案:
1)将MyHandler声明为静态的
2)将MyHandler作为一个独立的类,不作为内部类使用
在onDestory()中移除所有的消息
mHandler.removeCallbacksAndMessages(null);
2、非静态内部类的静态实例
非静态的内部类创建了一个静态实例。非静态内部类会持有外部类Activity的引用,后来又创建了一个这个内部类的静态实例。
这个静态实例不会在Activity被关掉时一块被回收(静态实例的生命周期跟应用的生命周期一样长,Activity被销毁后,静态实例并不会被释放)。
非静态内部类持有外部引用,而该内部类的静态实例不会及时回收,所以才导致了内存泄露。
解决方案:
将内部类申明为静态的内部类。
3、Context导致内存泄露
例如我们经常使用的工具类,需要传入Context对象,如果传入当前的Activity对象的话容易导致内存泄漏,因为工具类一般都是静态的,并且对其调用时机也不能很好的把控,这个时候需要传入该应用的Context来避免内存泄漏
系统中的Context的具体实现子类有:Activity、Application、Service。
虽然Context能做很多事,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。
出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
conext作用域.png上图中Application和Service所不推荐的两种使用情况:
如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错
javaandroid.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service的原因跟Application一致。
在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。一句话总结:凡是跟UI相关的,都建议使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例Context都可以,当然了,注意Context引用的持有,防止内存泄漏。