您的位置:首页 > 移动开发 > Android开发

【Android源码分析】Threadlocal与looper+handler

2018-01-12 23:37 309 查看

前言

为什么会有这么一篇网上有很多种解说版本的博客?因为我看懂了很多次,都没有把自己的想法记下来,然后就忘了。那样不仅浪费时间、而且还有点伤积极性。

从一个异常出发开始

在《第一行代码》中看到了关于异步处理消息的用法时,有没有想过可以在子线程中去new一个Handler?现在就开始着手,从一个子线程中去new一个Handler,看看会有什么发生。
new Thread(new Runnable() {
@Override
public void run() {
new Handler();
}
}).start();
结果就出现了RuntimeException异常,仔细看它的信息说明。
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
那么可知,在每个线程
new Handler()
时,都必须先调用
Looper.prepare()
或者调用一个能够达到相同效果的函数。那么在主线程中可以
new Handler()
的原因,想必就是已经调用过了。以下代码位于AcitivityThread.java中,是一段初始化主线程的内容。
Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(
new LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
其它的都忽略,就看Looper相关的。
Looper.prepareMainLooper()
想必是达到了相同的效果吧。那么,这个效果到底是什么呢?让我们慢慢拨开云雾。

寻找那个异常

于是,我们很自然地在子线程中加入了
Looper.prepare()
,并随手按着Ctrl,左键点击鼠标,进入了
prepare()
函数中。
/*
Initialize the current thread as a looper. This gives you a chance to create handlers that then reference this looper, before actually starting the loop. Be sure to call {@link #loop()} after calling this method, and end it by calling{@link #quit()}.
*/
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));
}
...
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
当初的那个异常不就在眼前?但是,这
sThreadLocal
又是什么?它是什么暂时抛开,这时我们知道了我们的线程中已经有了一个
Looper
,并且为这个Looper设置好了一个MessageQueue。因为一个线程只能有一个
Looper
,所以一个Looper也就只能拥有一个MessageQueue。但是AcitivityThread中,经过
Looper.loop()
后就再也没有下文了?所以,这个
loop()
又是干啥的呢?

繁忙的
loop()

/** Return the Looper object associated with the current thread.  Returns null if the calling thread is not associated with a Looper.*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
因为其中一个大大的死循环,所以调用了
loop()
之后,其后就没有实际代码了。这个死循环就是用来处理Message,不断地从队列中取,然后不断地进行分发到相应的Handler,进行处理。此时,这个
for(;;)
所处的线程,就是你调用Looper.loop()时所在的线程。
因此,它分发msg给了相应的Handler的handleMessage之后,还是在此线程中执行。然后,在想想,发送Message时所处在的线程,就焕然大悟这个异步操作了。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handleMessage(msg)
不正是我们创建Handler时候,所覆盖的方法吗?进一步思考,如果我只在主线程中new Handler,那么Looper就是主线程,所有的msg都会在主线程中被处理;那如果我想让msg在子线程中被处理呢?当然可以
Looper.prepare()
巴拉巴拉,然后
Looper.loop()
。但是Android还为我们提供了一个更为便捷的封装。那就是
HandlerThread

子线程处理msg的封装HandlerThread

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
源代码比较短。它继承自Thread,并在run方法中初始化好了Looper,可以通过其
getThreadHandler()
方法,获取到与该Looper所绑定的Handler,然后sendMessage(),最后在该线程中处理msg。

小结

因此,一个Thread可以有一个Looper和一个MessageQueue,一个Looper却可以与多个Handler绑定,但是一个Handler只能与一个Looper绑定。原因可以从Handler的构造方法中寻找的。

ThreadLocal是什么

回过头,想想ThreadLocal实现了什么样的功能。举个例子,当不同的线程都去执行同样一个语句以获得当前线程的Looper时,要怎么实现?或许吧,ThreadLocal就实现了这样一个功能。在Looper中,申明了一个如下的静态变量,说明只有一个。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
然后在Looper中调用myLooper()时,都执行了
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
顺手打开,看到了这个。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在每个Thread中,有个
ThreadLocal.ThreadLocalMap
。获取到当前线程的map之后,又将
sThreadLocal
自身作为一个Key,去获取到相应的结果。再看
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
相同的套路,同样是先获取到当前线程的map,然后用ThreadLocal自身作为Key,存进去。

小结

因此对于ThreadLocal,可以这样理解:它自身就是一个Key,然后可以以这个Key在不同的线程中存储/获取一个值,值得类型就是声明时,尖括号里面的类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  异步消息处理
相关文章推荐