在Android中,子线程不能直接更新主线程的UI,因此提供了Handler来方便我们操作。在子线程调用handler可以直接发送Message加入MessageQueue,Looper取出Message执行操作,这样就完成了从子线程到主线程的UI更新。但是,如果Looper取出的Message执行的操作是使用handler再发消息(发送的消息和之前一样),这样就形成了死循环,这样handler一直被持有引用,无法释放内存,导致内存泄露。
*****个人经验狭隘,难免有所疏漏,若有问题,恳请斧正!*****
1.什么时候会需要循环handler发消息?
2.如何解决handler循环导致的内存泄露?前面说handler形成死循环,会导致内存泄露。可是大家会想“谁会这么傻,没事弄个死循环?”。记得App里面常见的广告栏吗?会一直循环展示图片,这个时候就可能需要用到handler发送循环的延时消息。这里假设使用ViewPager循环展示图片,大致代码如下:
public class Demo extends AppCompatActivity { private static final int NEXT_PAGE = 1; private ViewPager mViewPager; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == NEXT_PAGE) { nextPage();//收到消息后,调用切换下一页的代码 } } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mViewPager = (ViewPager) findViewById(R.id.viewpager); //其他操作…… nextPage(); } private void nextPage() { //切换至下一页 int currentItem = mViewPager.getCurrentItem(); mViewPager.setCurrentItem(++currentItem); mHandler.sendEmptyMessageDelayed(NEXT_PAGE, 4000);//4秒后切换下一页 } }
可以看到,由于mHandler每次发送的延时4的秒消息,接收到消息后又继续调用了发送延时消息的方法nextPage(),这样就陷入了死循环。mHandler一直被调用,使Activity一直被持有引用,即便是Activity销毁了,也不会被释放掉内存,导致内存泄露。
(一)使用WeakReference,使垃圾回收器倾向于回收该对象
Java中对象有强、软、弱、虚四种引用,垃圾回收器优先回收顺序是虚>弱>软,不会回收持有强引用的对象(一直被持有强引用是导致内存泄露的原因)。平时建立的对象(例如Person person = new Person();),都是强引用,但是一旦使用完后,就不再持有引用,强引用消失,垃圾回收器就会来回收这片内存。而handler的循环使其引用一直不消失,所以不会被垃圾回收器回收,导致内存泄露。解决的代码如下:
private MyHandler mHandler = new MyHandler(DemoActivity.this); private static class MyHandler extends Handler { private final WeakReference<DemoActivity> mActivity; MyHandler(DemoActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { DemoActivity activity = mActivity.get(); if (activity != null) { } } }
使用WeakReference后,该对象的引用变成了弱引用,垃圾回收器会优先回收没有引用的对象,当内存不足时就会回收这种不稳定的弱引用对象,释放出内存。
(二)在Acticity销毁时,移除所有消息
既然handler的所有Message、Callback都被移除了,自然就消除了循环导致的持续引用问题。解决的代码如下:
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null);//移除MessageQueue内所有的Message、Callback }
(三)在Application类中,定义一个全局的handler
Application是一个全局的类,在App启动时启动,只有App关闭时才会销毁,所以完全不会存在内存泄露问题。设置代码如下:
/** * 自定义Application,进行全局初始化 * * @author ALion on 2016/10/13 20:46 */ public class MyApplication extends Application { private static Handler mHandler; @Override public void onCreate() { super.onCreate(); mHandler = new Handler(); } public static Handler getHandler() { return mHandler; } }
——加油!