先贴一下前一篇文章的传送门:
正文:
在上篇文章中,我们大体了解了Android的handler体系,弄明白了发送一个消息后,这个消息怎么就能来到handleMessage方法中从而被处理,但同时也留下了这样几个问题:一个线程中能不能有多个looper? 子线程中能不能直接使用handler?handler造成的内存泄漏该如何处理? 本篇文章我们便来一一进行说明
1.一个线程中能不能有多个looper?
先说结论:不能
那么为什么呢?要弄清楚这个问题,就必须要提到prepare()这个方法
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
这个方法的作用,简单来说就是为某个线程准备其对应的MessageQueue与Looper,可以看到,prepare()方法本身只是一个入口,真正逻辑都交给了prepare(boolean quitAllowed)这个方法来处理
而在这个方法中,首先会进行一个逻辑判断:如果sThreadLocal.get() 的值为空才进行设置,否则直接抛出一个异常,从异常信息我们也可以看出来,一个线程只能对应一个Looper
那么这个sThreadLocal是个什么玩意儿呢?简单来说就是一个ThreadLocal对象,用来进行线程间的数据隔离作用,让每个线程只能访问自身的数据,至于具体的原理和android的关系不大,这里便不做展开
2.子线程中能不能直接使用handler?
结论:不能
因为子线程是不会自带MessageQueue与Looper的,如果想要让子线程也拥有自己的handler,则必须先调用Looper.prepare()方法来创建Looper与MessageQueue
如果直接在子线程中使用handler,则会抛出如下异常
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
可以看见,如果queue对象为空,则会直接抛出一个运行时异常,根据异常信息就可以发现,想要使用handler,就必须先拥有自身的Looper与MessageQueue
3.handler造成的内存泄漏该如何处理?
如果直接在Activity中使用匿名类的形式来重写handleMessage方法可能会导致内存泄漏问题,这是因为在调用sendMessage方法后,会将Message的target属性设置为当前handler(具体流程可以参考我的上一篇文章),而当前handler又持有了当前Activity的引用,这就导致了Message间接持有了Activity的引用,而Message在被处理完成之后并不会立即被回收,而是会被存放到一个名为"消息池"(sPool)的地方 (我们获取消息时,调用的Message.obtain()方法,便是优先从消息池里去拿消息),这就可能让Activity实例无法被正常回收,从而导致内存泄漏
要解决这个问题,我们一般是新建一个类去继承Handler类,然后在这个类里面利用弱引用获取Activity对象的实例,再进行业务操作,参考代码如下:
public class SaveHandler extends Handler { private WeakReference<MainActivity> myActivity; public SaveHandler(MainActivity activity){ myActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg){ MainActivity mainActivity = myActivity.get(); /* * 执行业务逻辑 * * */ } }
这样便可解决内存泄漏的问题,具体原理参考《深入理解Java虚拟机》一书
全部评论
(0) 回帖