1 为什么以这一个点为开头?
因为面试的时候被问到ThreadLocal完全不懂,前几天发现Looper内正好使用了ThreadLocal,那么从哪里跌倒就从哪里爬起来。
2 什么是Looper
首先看/sdk/docs/reference/android/os/Looper.html内的定义
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Looper类用于在一个线程中运行一个消息循环。默认的线程不包含消息循环。
如果需要创建一个Looper,在Thread的run函数中调用Looper.prepare()
接着调用Thread.loop()函数,直到消息队列停止。
简单的说,Looper就是一个消息循环,在一个线程中不停的去消息队列里poll新消息出来给Handler处理,
3 如何创建Looper
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends ActionBarActivity {
private static final String TAG = "MainActivity";
private class LooperThread extends Thread
{
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg)
{
Log.i(TAG,msg.toString());
}
};
Looper.loop();
}
}
private LooperThread mLooperThread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLooperThread = new LooperThread();
mLooperThread.start();
}
}
这是reference中给出的子线程中Looper的构建方法
a 创建新线程,重写run函数
b 调用Looper.prepare()
c 创建Handler
d 调用Looper.loop()
我们下面分4部分解读源代码
3.1 Looper概览
- 成员变量
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
}
Looper拥有6个成员变量,其中mLogging和Tag先忽略
sMainLooper、ThreadLocal为static成员
mQueue、mThread为实例的成员变量
- 成员函数
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void | dump( Printer pw, String prefix) | ||||||||||
static Looper |
getMainLooper()
Returns the application's main looper, which lives in the main thread of the application.
| ||||||||||
Thread |
getThread()
Return the Thread associated with this Looper.
| ||||||||||
static void |
loop()
Run the message queue in this thread.
| ||||||||||
static Looper |
myLooper()
Return the Looper object associated with the current thread.
| ||||||||||
static MessageQueue |
myQueue()
Return the
MessageQueue object associated with the current thread.
| ||||||||||
static void |
prepare()
Initialize the current thread as a looper.
| ||||||||||
static void |
prepareMainLooper()
Initialize the current thread as a looper, marking it as an application's main looper.
| ||||||||||
void |
quit()
Quits the looper.
| ||||||||||
void |
quitSafely()
Quits the looper safely.
| ||||||||||
void |
setMessageLogging(
Printer printer)
Control logging of messages as they are processed by this Looper.
| ||||||||||
String |
toString()
Returns a string containing a concise, human-readable description of this object.
|
3.2 调用Looper.prepare()
我们先看看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));
}
从静态的prepare函数可以每一个Thread只能对应一个Looper,不然会报RuntimeException
而prepare函数所做的事情只是在全局的sThreadLocal中存放了个新的Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
new Looper时赋值了mQueue和mThread
既然sThreadLocal是个全局的静态变量,那么所有的Looper类都共享同一个sThreadLocal
sThreadLocal.set操作定义如下
java.lang.ThreadLocal.java
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
即从当前调用线程中取出values对象,然后往这个values对象存放这个Looper
需要注意的是每个Thread中都有一个values对象,
这个values对象再按照ThreadLocal<Looper> sThreadLocal对象在当前线程的values哈希表中找出对应的Looper
那么这个Looper就对应为当前线程的Looper
那么使用ThreadLocal有什么好处呢?
好处是显而易见的,如果用全局的HashMap管理一个Thread对应一个Looper,
那么增删改某个Looper对象时就需要进行同步操作,这大大增加了系统开销
而如果有一个ThreadLocal.Values对象存放在Thread里,需要用到时就直接获取,不与其他线程的数据进行交互,
那么就避免了同步带来的低效率问题,所以这个ThreadLocal正好被应用到了一个Thread对应一个Looper中
3.3 创建Handler
android.os.Handler.java
public Handler() {this(null, false);}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
因篇(lan)幅(de)限(xie)制(le),Handler只提到1个函数,在创建Handler时无参数代入,即调用到Handler(null,false)
在下面一个函数中handler进行了一些初始化的赋值,最重要的一步是赋值了mLooper和mQueue
Looper.myLooper()函数定义如下
public static Looper myLooper() {
return sThreadLocal.get();
}
即从sThreadLocal中获取了当前Thread中得looper,赋值给了Handler,
这也就解释了为什么需要先Looper.prepare()
再new Handler的原因了
3.4 调用Looper.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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
因为是静态函数,需要先获得当前线程的looper实例,
接着调用Binder.clearCallingIdentity(),这个函数返回的是当前进程的pid、uid等信息混合后的唯一标示
接着一个for不断循环获取消息队列中得下一条消息,
如果调用的queue.next返回的msg为null则立即结束此线程
如果不为null则调用msg.target.dispatchMessage(此target为handler实例,即在当前线程调用到了handler的handlerMessage)
接着再获取一次Binder.clearCallingIdentity(),判断前后两次的唯一标示是否相同
我很奇怪既然在同一个线程为什么会需要这样的判断,后来查看Log.wtf定义发现注释如下:
What a Terrible Failure: Report a condition that should never happen.
也就是永远不会发生的情况发生了做一个记录,
因为uid是安装完app就已经生成了,pid是在程序跑起来时也生成了,如果这两个其中一个改变,
说明app被kill后重启或者重新安装,既然被kill后就不应该存在这个handler,
所以理论上应该不会发生这个情况
最后调用msg.recycle()对msg进行一个重置回收
我们来看看
android.os.Message.java的recycle函数实现
public void recycle() {
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
正如代码中所示,首先把所有消息内容清空,然后放入对象池提供给其他对象使用
4 最佳实践
既然上面的Message使用了对象池,
那么我们在使用Message msg = new Message()就太瞎了
在需要使用新的Message时,使用Message.obtain()或者Handler.obtainMessage()是个不错的选择!
5 总结
本文只是简单的叙述了Looper的构建过程,但其实内部中mQueue.next()是夸线程调用的最重要体现,mQueue是如何把其他线程上的msg dispatch给当前线程的Handler,我将在下一讲中给出解释,顺便具体学习下MessageQueue,也一定会讲到MessageQueue cpp层的一部分源代码。