在开发中我们经常用Handler来进行子线程修改UI界面,进程间通讯等等,在用的过程中稍不注意就有可能发出handler leaks的警告,即“This Handler class should be static or leaks might occur ..”的警告。Google给出的解释如下:
Issue: Ensures that Handler classes do not hold on to a reference to an outer class。(确保Hanler类不持有外部类的引用)
In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class。(在Android中, Handler类应是static的, 否则可能会发生泄漏. 程序线程的消息队列的Message也持有对Handler对象的引用. 如果Handler是内部类, 那么(回收内存时)其外部类也会被保留.(使用Handler的Service和Activity就也无法被回收. 这就可能导致内存泄露.) 为了避免泄露其外部类, 可将Handler声明为static并持有其外部类的WeakReference(弱引用).
下面结合具体的例子来分析Handler在使用中造成内存泄露的原因。
1.Android中使用Handler造成内存泄露的原因
如果在Activity中定义了一个内部Handler类,如下代码:
public class MainActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//TODO handle message...
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
}
这时会出现一个内存泄露警告:
This Handler class should be static or leaks might occur (com.example.ta.MainActivity.1)
Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak
In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class.
原因是这样子的:
当Android应用启动的时候,会先创建一个应用主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。当在主线程中初始化Handler时,该Handler和Looper的消息队列关联,发送到消息队列的Message会引用发送该消息的Handler对象,这样系统就可以调用 Handler#handleMessage(Message) 来分发处理该消息。然而,我们都知道在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象。如果外部类是Activity,则会引起Activity泄露 。因为当Activity finish后,延时消息会继续存在主线程消息队列中,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。也就是如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到 MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
另外,像这样使用Handler来处理耗时的后台任务,如下所示:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎 么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这 个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有 Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收 (即内存泄露),直到网络请求结束(例如图片下载完毕)。
以上就是我觉得Android中使用Handler可能造成内存泄漏的原因。接下来分析解决方法。
2.Android中使用Handler导致内存泄露的解决方法
要修改该问题,只需要按照警告提示的那样,把Handler类定义为静态即可,然后通过WeakReference 来保持外部的Activity对象。
(1)方法一:通过完善自己的代码逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
(2).将Handler声明为静态类,然后通过WeakReference 来保持外部的Activity对象。
由于静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。
代码如下:
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
private final WeakReference<Activity> mActivity;
public MyHandler(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
@Override
public void handleMessage(Message msg) {
Log.i("Message"+msg);
if(mActivity.get() == null) {
return;
}
}
}
如果是后台耗时任务,也是这样做:
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
综上所述:
当我们在Activity中使用内部类的时候,需要时刻考虑是否可以控制该内部类的生命周期,如果不可以,则最好定义为静态内部类,以免造成内存泄漏。这是Android开发过程中经常被忽略掉的,特别是在开发自定义View组件的过程中经常忘记而导致内存泄漏。