1.前言
在学习Android开发的过程中,想必许多小伙伴一开始都有过在子线程中修改UI导致报错的经历。现在我们明白了Android中规定UI的更新操作必须在主线程中完成,这主要由于是Android的UI控件是线程不安全的。相反的,一些耗时I/O操作如:读取文件、访问网络等,必须在子线程中执行,否则就有可能导致ANR。我们在实际开发当中,常常需要在子线程中进行一些耗时操作,当其完成以后再对UI进行一些改变,这就涉及到了线程切换操作,Android的消息机制——Handler就该派上用场了 。
2.Handler的使用
2.1与Handler相关的类
- Message 用于储存消息。
- MessageQueue 消息队列,用于储存Handler发送来的Message。
- Looper 循环器,用于取出MessageQueue中的Message并进行分发,Handler的创建必须依赖于Looper,否则会创建失败。
- ThreadLocal 线程内部的数据存储类,可在指定线程中存储数据,只有该线程可以获取其中数据,其他线程无法获取,在Handler中用于存储一个Looper。
2.2Handler常用的形式
//sendMessage
class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 1:
textView.setText("网络请求成功");//收到消息 更新UI
break;
default:
break;
}
}
}
final Handler myHandler = new MyHandler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);//模拟网络请求等耗时任务
Message message = Message.obtain();//从缓存池中获取Message
message.what = 1;
myHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//sendEmptyMessage
new Thread(new Runnable() {
@Override
public void run() {
myHandler.sendEmptyMessage(1);//内部获取了一个Message并设置了what为1
}
}).start();
//post
final Handler myHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);//模拟网络请求等耗时任务
myHandler.post(new Runnable() {
//这里看似是在子线程中跟新UI,实际上他是在创建handler的线程中调用的,
//通过之后的源码阅读可以发现他调用的是run方法而不是start方法
@Override
public void run() {
textView.setText("网络请求成功");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//postDelayed
myHandler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("网络请求成功");
}
},500);//延迟500ms
主线程中创建Handler十分简单,接下来我们来看如何在子线程中创建Handler
//这段代码会报错的
class MyThread extends Thread{
public Looper mLooper;
@Override
public void run() {
Looper.prepare();//创建一个Looper
mLooper = Looper.myLooper();//得到当前线程中创建的Looper
Looper.loop();//循环读取消息
}
}
MyThread mThread = new MyThread();
mThread.start();
Handler handler = new Handler(mThread.mLooper){//子线程Handler的创建必须依赖于子线程的Looper
public void handleMessage(@NonNull Message msg) {
Log.d("MainActivity",Thread.currentThread().getName());
}
};
handler.sendEmptyMessage(0);
我们可以看到在子线程中创建Handler比主线程要复杂很多,因为主线程中的Looper系统已经为我们创建好了,并启动了loop去循环读取消息,所以我们不需要自己来操作了,而子线程中的Looper需要我们自己来创建。这段代码虽然看似没有问题,但实际上会报空指针错误,原因是我们在创建Handler的时候,子线程的Looper不一定创建完毕,所以得到的mLooper可能为null。而Android为了解决这个问题,提供了HandlerThread类 。
HandlerThread handlerThread = new HandlerThread("Thread1");//传入线程名
handlerThread.start();//启动线程
Handler handler = new Handler(handlerThread.getLooper()){//得到子线程的Looper
public void handleMessage(@NonNull Message msg) {
Log.d("MainActivity",Thread.currentThread().getName());
}
};
handler.sendEmptyMessage(0);//主线程发送消息到子线程
可以看到,handler成功收到了消息,并且handler的所在线程为子线程
D/MainActivity: Thread1
子线程中Handler的使用场景:
class MainHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 1001:
textView.setText("网络请求成功");
break;
default:
break;
}
}
}
final Handler mainHandler = new MainHandler();//主线程的Handler
HandlerThread handlerThread = new HandlerThread("Thread1");
handlerThread.start();//开启子线程
final Handler downloadHandler = new Handler(handlerThread.getLooper()){//子线程的Handler
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 1000:
try {
Thread.sleep(1000);//模拟网络请求
//网络请求成功 子线程发消息给主线程更新UI
mainHandler.sendEmptyMessage(1001);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadHandler.sendEmptyMessage(1000);//主线程发送网络请求消息到子线程
}
});
3.源码解析
3.1Message
public final class Message implements Parcelable {
public int what;//标识
public int arg1;//可以用来存放int内容
public int arg2;//可以用来存放int内容
public Object obj;//可存放任意类型的内容
Handler target;//Message的接收者
Runnable callback;//回调
Message next;//链表的下一个Message
private static Message sPool;//缓存池
private static final int MAX_POOL_SIZE = 50;//缓存池最大容量
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {//缓存池不为null
Message m = sPool;//得到缓存池表头的message
sPool = m.next;//缓存池表头指向下一个meassge(若为空则obtain时将会新建一个)
m.next = null;
m.flags = 0;
sPoolSize--;//缓存池大小减一
return m;
}
}
return new Message();//第一次取的时候缓存池为null,所以会新建一个Message
}
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
//消息不再使用了,回收
recycleUnchecked();
}
@UnsupportedAppUsage
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;//回收后将自己放入缓存池
sPoolSize++;//缓存池大小加一
}
}
}
}
以上是Message类的主要内容,可以看到其内部维持了一个缓存池,当我们调用obtain方法的时候,会将message从缓存池中取出,而message使用完毕的时候会对其进行回收,重新加入缓存池。所以一般不会直接调用message的构造方法,这样会产生很多缓存池,造成资源的浪费。
3.2ThreadLocal
为了更直观的了解ThreadLocal的作用,我们先看一个小例子。
final ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
mThreadLocal.set("MainThread");//主线程设置mThreadLocal的值
Log.d(TAG,"MainThread: "+mThreadLocal.get());
new Thread(new Runnable() {//子线程1
@Override
public void run() {
mThreadLocal.set("Thread1");//子线程1设置mThreadLocal的值
Log.d(TAG,"Thread1: "+mThreadLocal.get());
}
},"Thread1").start();
new Thread(new Runnable() {//子线程2
@Override
public void run() {
Log.d(TAG,"Thread2: "+mThreadLocal.get());
}
},"Thread2").start();
在这里,我们将主线程的mThreadLocal值设置成MainThread,子线程1中的值设置为Thread1,子线程2不设置任何内容。然后我们分别在这三个线程中打印mThreadLocal的值。
D/MainActivity: MainThread: MainThread
D/MainActivity: Thread1: Thread1
D/MainActivity: Thread2: null
可以看出在不同的线程中,虽然调用的是同一个mThreadLocal的get方法,却获得了不同的值,它可以在不同的线程中维护一组数据彼此不受干扰。接下来我们就来探究ThreadLocal的set和get方法,看看它内部是如何实现的。
public void set(T value) {
Thread t = Thread.currentThread();//得到当先线程
ThreadLocalMap map = getMap(t);//得到当线程所对应的map
if (map != null)
map.set(this, value);//设置map的值
else
createMap(t, value);//map为null就创建map
}
在set方法中首先通过getMap来获取当前线程所对应的ThreadLocalMap实例,再通过ThreadLocalMap的set方法把value值添加到map中。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {//找到key对应的Entry
e.value = value;//将value储存在相应的Entry中
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
可以看到最终会把数据储存到一个Entry类(一个ThreadLocalMap的内部类)当中,Entry继承了ThreadLocal的弱引用,并在内部储存一个value值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//得到ThreadLocal(key)对应的Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//取出Entry中的值
return result;
}
}
return setInitialValue();//返回null
}
get方法同样是取出当前线程对应的ThreadLocalMap实例,然后再通过getEntry得到当前ThreadLocal对应的Entry,然后取出Entry中的value值进行返回。如果当前线程没有ThreadLocalMap实例,则会调用setInitialValue方法,最终会返回null。
private T setInitialValue() {
T value = initialValue();//设置value为null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
3.3MessageQueue
MessageQueue中主要包含插入和读取两个操作分别对应着enqueueMessage和next。首先来看enqueueMessage的源码:
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();//标记msg正在被使用
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {//p为null代表MessageQueue没有消息
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;//是否唤醒
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {//从头开始遍历队列,找到消息该插入的位置
prev = p;
p = p.next;
if (p == null || when < p.when) {//对消息的时间进行比较
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //插入到队列中
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);//唤醒
}
}
return true;
}
enqueueMessage主要进行了队列的插入,插入完成后唤醒阻塞中的线程。接下来我们看next的源码:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {//死循环,直到获取到message后才退出
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作(等待enqueueMessage唤醒)
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {//成功获取到Message
mBlocked = false;
if (prevMsg != null) {//从链表中移除
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
...
}
...
}
}
可以看出next方法会出于阻塞状态,直到成功获取到下一条Message才会进行返回。
3.4Looper
Looper是一个循环器,当MessageQueue中有消息在的时候,它就会按顺序一个一个取出。一个线程最多只能有一个Looper,Handler的使用必须要有Looper,若使用Handler时该线程并未创建Looper则会报错。Looper主要有三个重要的方法,创建Looper(prepare)、获取当前线程Looper(myLooper)、开启Looper循环读取消息(loop)。首先我们看一下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方法中对当前线程是否已经有Looper进行了判断,若已有Looper则抛出异常,否则会新建一个Looper并存入ThreadLocal。接下来看myLooper的源码:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
十分简单,就是取出了ThreadLocal中的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;//得到消息队列
...
for (;;) {//死循环
Message msg = queue.next(); // 获取队列中的消息(会阻塞)
if (msg == null) {//取出消息为null,退出死循环
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);//分发消息
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();//消息回收
}
}
可以看到loop方法中首先获取到当前Looper中的消息队列,然后进入一个死循环,唯一跳出死循环的方式是MessageQueue的next方法返回了null,而只有当Looper的quit或quitSafely方法被调用时,会通知消息队列退出,此时会返回null,其他时候一直处于一个死循环中。当成功获取到下一条Message时,会调用msg.target.dispatchMessage(msg)来分发信息,msg.target及目标Handler,而我们的目标Handler是在我们想要接收消息的线程中创建的,这样就达到了线程的切换。dispatchMessage方法将在Handler当中具体讲解。
3.5Handler
首先我们看一下我们调用new Handle()时所用到的构造方法。
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
...
//获得当前线程的Looper
mLooper = Looper.myLooper();
//Looper获取失败则抛出异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//设置消息队列
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到其中调用了Looper的myLooper方法来获取当前线程的Looper。那么问题来了,在Android主线程中,我们不需要自己创建Looper,那么这个Looper是在哪里set到ThreadLocal当中的呢?我们的Handler是在主线中创建的,Android中的主线程其实就对应着ActivityThread,主线程的入口即为main方法。下面是ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();//创建主线程Looper
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();//开启消息循环,并进行分发
....
}
Looper的prepareMainLooper方法
public static void prepareMainLooper() {
prepare(false);//调用prepare方法
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//获取当前Looper
}
}
可以看到prepareMainLooper也时调用到了之前所说的prepare方法,这样就在程序启动的时候自动为我们创建了一个Looper,因此我们不需再手动调用prepare方法去创建Looper。而我们自己新建的子线程中系统并没有为我们创建Looper,所以我们要手动进行创建,并开启Looper循环。
我们再来看看Handler的消息是如何发送的,首先看看sendMessage方法:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//设置msg的target为自己(Handler),用于loop中dispatchMessage
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//将消息放入消息队列
}
再来看看Handler的post方法:
public final boolean post(@NonNull Runnable r) {
//同样是调用了sendMessageDelayed方法
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();//获取一个message
m.callback = r;//设置message的callback
return m;
}
可以看到无论是sendMessage方法还是post方法,都调用了sendMessageDelayed方法。sendMessage是依据传入的message,把他放入消息队列中,而post是重新获取了一个message,并设置其callback,最后把他放入消息队列中。看完了消息是如何发送的,接下来我们来看看loop方法中调用的Handler的dispatchMessage方法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {//msg存在callbcak
handleCallback(msg);//1
} else {
if (mCallback != null) {//handler存在callback
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//2
}
}
private static void handleCallback(Message message) {//1处调用
message.callback.run();
}
public void handleMessage(@NonNull Message msg) {//2处调用
}
这里的2就是我们继承Handler时重写的handleMessage方法,1处调用的就是post方法传入的Runnable的run方法。这样我们的Handler就成功得到了message。
4.Looper阻塞
我们通过阅读Looper和MessageQueue的源码发现,当Looper调用loop方法的时候若没有消息得到会进行阻塞,那么为什么Android的主线程并没有被阻塞呢?
实际上阻塞确实存在,但是Android的所有UI操作都是通过Handler来完成的,一旦收到收到各种点击事件之类的消息,就会把线程进行唤醒,所以主线程就不会有卡顿的现象了。
参考资料《Android开发艺术探索》