本以为这些东西是老生常谈,并且作为Android开发的基础入门知识,是必须熟练掌握的,但是发现每每提起Handler及Looper,ThreadLocal的原理,总是要去翻阅资料,或者查看源码再捋一遍,干脆花一点时间整理一下,等下次再忘记拿出来稍稍翻阅一下就ok了。
分析一件事情往往需要带着问题去分析,下面我们将从以下几个问题进行分析:
相信你如果能够很详尽的回答这三个问题,那么本篇文章可能对你来说只是一个查漏补缺的过程,不过如果你对这三个问题回答模棱两可甚至回答不出来,那么请务必好好阅读此篇文章。
首先我们先看下怎么在主线程中创建Handler
private void initMainThreadHandler() {
mMainThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "mainThread \n" msg.toString());
}
};
}
如果同样按照这种方式在子线程中创建Handler如下
private void initOtherThreadHandler() {
new Thread(new Runnable() {
@Override
public void run() {
mOtherThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "otherThread \n" msg.toString());
}
};
}
}).start();
}
那么此时,调用initOtherThreadHandler
一定会报异常,报的异常为:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
那为什么会报这个异常呢,我们可以追溯到handler源码中
public Handler(Callback callback, boolean async) {
//省略部分代码
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//注意这里有个mQueue的赋值,是Handler的成员变量,后面会用到
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到Looper.myLooper()
得到的对象是null,所以就抛出了一个异常,那么我们此时就要保证这个方法得到的对象不能为空,至于为什么为空呢?后面将ThreadLocal源码会讲到,优化后的子线程初始化handler如下:
private void initOtherThreadHandler() {
new Thread(new Runnable() {
@Override
public void run() {
//准备该线程对应的looper
Looper.prepare();
mOtherThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "otherThread \n" msg.toString());
}
};
//循环遍历messageQueue来读取消息队列中的消息,从而能够处理消息并保证线程在一直执行
Looper.loop();
}
}).start();
}
那有的同学可能会问了,为什么主线程不会抛异常? 那是因为在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面创建一个Looper对象:Looper.prepareMainLooper(),然后调用Looper.loop();完成Looper对象的创建。实际上Looper.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。
至此,我们解决了第一个问题,下面解决第二个问题。
上面我们创建了handler,那么怎么发消息呢:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.test1:
//给主线程发消息
Message obtain = Message.obtain();
obtain.obj = "主线程收到消息";
mMainThreadHandler.sendMessage(obtain);
break;
case R.id.test2:
//给子线程发消息
Message obtain2 = Message.obtain();
obtain2.obj = "子线程收到消息";
mOtherThreadHandler.sendMessage(obtain2);
break;
}
}
分别点击两个按钮,打印日志:
11-20 13:52:04.196 31131-31131/com.huli.hulitestapplication D/HandlerActivity: mainThread
{ when=-1ms what=0 obj=主线程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$1 }
11-20 13:52:14.166 31131-31163/com.huli.hulitestapplication D/HandlerActivity: otherThread
{ when=-1ms what=0 obj=子线程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$2$1 }
我们可以看到,给对应线程发消息,就是使用对应线程里面创建的handler发消息,给主线程发消息,就是使用mMainThreadHandler
,给子线程发消息,就是使用mOtherThreadHandler
,那这个消息怎么进到对应线程并处理,我们来看发消息的源码:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//这个mQueue是Handler的成员变量,是Looper的成员变量赋值过来的
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//这里将消息的target属性赋值为handler,后续会用这个target来处理消息
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//然后将消息塞进队列中
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,对应的发消息,其实就是将message的target指向当前发消息的handler,然后将message塞入消息队列中,这个消息队列是通过Looper.loop()
来进行遍历的,我们继续看源码:
public static void loop() {
final Looper me = myLooper();
//这个队列跟发消息的Handler里面的队列是一个对象,Handler里面队列添加了消息,这里就能取到消息
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
try {
//关键代码 使用调用msg.target.dispatchMessage方法处理message
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
}
}
由上面代码,我们可以看到,最终就转到了发消息的Handler来处理
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//由于上面都为空,所以走到这里,调用下面的方法
handleMessage(msg);
}
}
//这个方法就是我们重写的方法,最终打印出了日志
public void handleMessage(Message msg) { }
所以我们了解到,消息是由handler进行发出,塞进对应线程的messageQueue中,对应线程的Looper又进行了遍历,取出消息,交由对应发消息的handler来处理,进行了消息的发送和处理,那么新的问题又来了,这个对应线程是怎么来的?怎么根据线程来获得对应的Looper和messageQueue?这就是接下来我们要分析的第三个问题了。
我们会发现我们是通过Looper.prepare()
来进行对应线程Looper的初始化的,后面又通过Looper.myLooper()
进行对应线程的Looper的获取的,这里面肯定有猫腻,这就涉及到了ThreadLocal的原理了,接下来我们看源码:
//Looper类
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//从这也可以看出,每个线程Looper.prepare()只能调用一次,否则会抛异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
从上面就看到ThreadLocal
的身影了,我们先解释下ThreadLocal:
由于内容比较多,这里贴一篇详细的文章,我下面做简单的总结:
其实每个线程Thread
类里面都有一个成员变量ThreadLocal.ThreadLocalMap threadLocals
,该map的key为ThreadLocal
,value为要创建的副本,当我们第一次调用Looper.prepare()
的时候,首先会调用sThreadLocal.get
方法,进到get
方法里面得到当前线程的map,然后获取key为sThreadLocal
的value值,得到的为null, 然后调用 sThreadLocal.set(new Looper(quitAllowed));
, 进到set
方法,同样的获取当前线程的map,然后给当前map设置key为sThreadLocal
,value为新创建的Looper,此时当前线程就存在Looper了
接下来我们创建Handler的时候,就会调用Looper.myLooer()
获取当前线程的Looper,就是通过Looper的sThreadLocal.get
得到的
后面进行消息遍历的时候,也是通过Looper.myLooer()
获取当前线程的Looper,即通过Looper的sThreadLocal.get
得到的
所以这样我们就回答了第3个问题,Looper能够做到每个线程都有一个Looper副本,就是通过Looper的静态成员变量sThreadLocal
做到的,它是通过获取当前线程的ThreadLocalMap来判断对应线程是否有副本,有就能取到当前线程的Looper副本,所以我们并没有手动给Handler赋值Looper,它就能正常取到其值,就是通过ThreadLocal来做的。
如果在开发过程中你也有不同线程都存在一个副本的需求,就可以尝试使用ThreadLocal来实现。
至此,大功告成。
本文由博客一文多发平台 OpenWrite 发布!