看完本篇文章,你将搞清楚以下所有的关于Handler相关的任何问题。如果你以后在面试中碰到Handler相关的问题,相信你会给面试官眼前一亮的感觉。
a.最常用的方式
我们最常用的是在我们的Activity或者什么组件里直接通过匿名内部类的方式使用Handler刷新UI
1 private void doLogic() {
2 new Thread(()->{
3 // TODO 在异步的线程中请求数据或者其他耗时操作,请求完数据通知主线程刷新UI
4 myHandler.sendEmptyMessage(1);
5 }).start();
6 }
7
8 Handler myHandler = new Handler() {
9 @Override
10 public void handleMessage(Message msg) {
11 super.handleMessage(msg);
12 // TODO 数据请求到了,在这里刷新数据吧,一般会在Message中将数据带过来
13 }
14 };
上面这样做是可以实现的,但是匿名内部类会持有外部类的引用,比如如果你的外部是一个Activity,如果myHandler有一个延迟的10分钟的消息发送到MainLooper的消息队列中,在消息还未执行之前外部的Activity调用finish结束自己时,此时由于主线程Looper持有myHandler的引用,而myHandler又持有外部类Activity的引用导致Activity的内存无法释放,就会出现内存泄漏。所以推荐使用静态内部类的方式实现以避免内存泄漏的可能如下:
1 Handler mHandler = new MyHandler();
2 private void doLogic() {
3 new Thread(()->{
4 // TODO 在异步的线程中请求数据或者其他耗时操作,请求完数据通知主线程刷新UI
5 mHandler.sendEmptyMessage(1);
6 }).start();
7 }
8
9 private static class MyHandler extends Handler{
10 @Override
11 public void handleMessage(Message msg) {
12 super.handleMessage(msg);
13 // TODO 数据请求到了,在这里刷新数据吧,一般会在Message中将数据带过来
14 }
15 }
b.在子线程中执行handMessage
上面的是在UI线程(主线程)中执行我们的消息处理,那是否可以在子线程中执行消息处理呢?当然是可以的,如下:
1 private void doLogic() {
2 new Thread(()->{
3 Looper.prepare();
4 Handler lHandler = new MyHandler(Looper.myLooper());
5 Looper.loop();
6 lHandler.sendEmptyMessage(1);
7 }).start();
8 }
9
10 private static class MyHandler extends Handler{
11 MyHandler(Looper looper) {
12 super(looper);
13 }
14 @Override
15 public void handleMessage(Message msg) {
16 super.handleMessage(msg);
17 // TODO 此时该方法将在子线程中执行
18 }
19 }
Handler里面有一个重要的成员变量Looper,Looper里面维护了一个MessageQueue(消息队列),当我们使用handler.post或者sendMessage相关的方法都是将消息Message放入到消息队列中。每一个线程都将拥有一个自己的Looper,是通过:
1static final ThreadLocal sThreadLocal
实现的,顾名思义ThreadLocal是和线程绑定的。当我们有一个线程A使用sThreadLocal.set(Looper a),线程B使用sThreadLocal.set(Looper b)的方式存储,如果我们在线程B中使用sThreadLocal.get()将会得到Looper b的实例。所以我们每个线程可以拥有独立的Looper,Looper里面维护了一个消息队列,也就是每一个线程维护自己的消息队列。
当在主线程中时,在你的应用启动时系统便给我们创建了一个MainLooper存入了sThreadLocal中,所以平时我们使用Handler时,如果是在主线程中创建的,我们是不需再去创建一个Looper给Handler的,因为系统已经做了,所以当我们new Handler时,系统便将之前存入的Looper通过sThreadLoca中get出来,然后再去从对应的消息队列中读取执行。
而当我们在子线程中创建Handler时,如果直接new Handler运行时肯定会报错的,提示我们必须先调用Looper.prepare()方法,为什么呢?因为我们没有创建子线程对应的Looper放入sThreadLocal当中,而prepare方法就是new了一个Looper的实例通过sThreadLocal.set设置到当前线程的。整个建立过程类似于下图:
也就是说,Handler创建的时候肯定会在一个线程当中(主线程或者子线程),并且创建一个Looper实例与此线程绑定(无论是系统帮我们创建或者通过prepare自己绑定),在Looper中维护一个消息队列,然后looper循环的从消息队列中读取消息执行(在消息队列所在线程执行)。这就是整个Handler的运行机制了。
通过Handler有很多种发送消息的方式:
其实无论是通过post的方式或者send的方式,最后都是通过:
1public final boolean sendMessageDelayed(Message msg, long delayMillis)
2
我们使用post传入的Runnable实例,也是通过:
1 private static Message getPostMessage(Runnable r) {
2 Message m = Message.obtain();
3 m.callback = r;
4 return m;
5 }
通过上面的方法,将Runnable的实例转换为Message的实例,然后调用通用的方法发送到消息队列中,最终会通过下面的方式放入队列:
1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2 msg.target = this;
3 if (mAsynchronous) {
4 msg.setAsynchronous(true);
5 }
6 return queue.enqueueMessage(msg, uptimeMillis);
7 }
由于时间和篇幅有限,上述文章讲的还是比较粗糙,也就是给大家一个抛砖引玉的作用,如果想要真正的探究Handler实现的整个细节,大家不妨多看看他们的源码,真个Handler、Looper、ThreadLocal的源码和注释全部加起来也就1900行左右,还是蛮小的。值得大家研究一下。
好了,今天就到这了。写的不好,仅供参考。
如果对你又一点点的帮助,请关注微信公众号:IT烂笔头 回复【职场】获得作者微信好友位,一起交流