先来一个自己画的Handler机制整体流程图,本文不会带着你走一遍源码,只会对重点需要注意的地方以及一些细节的处理做出解释,让你更好的了解Handler机制整体的运作
。
- Handler通过sendMessage()发送Message到MessageQueue队列;
- Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
- 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
- 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;
- 如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。
下边放几个需要注意的Handler知识点:
- Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
- 在创建 Handler 之前一定需要先创建 Looper;
- Looper 有退出的功能,但是主线程的 Looper 不允许退出;
- 异步线程的 Looper 需要自己调用 Looper.quit(); 退出;
- Runnable 被封装进了 Message,可以说是一个特殊的 Message;
- Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,
也可以说成 Looper 所在的线程,并不是创建 Handler 的线程,Handler新建时持有的Looper在哪个线程,最后Handler.handleMessage()就在哪个线程执行
; - 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;
为什么需要使用Handler
因为Android系统不允许在非UI线程更新UI
,因为如果多个线程同时改变View的状态会造成最终View状态的不确定性,如果给每个View的操作都上锁的话那么势必会造成性能的损耗,所以干脆规定只能在UI线程去更新UI,而Handler就是用来进行线程切换操作的。
使用方法
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定义消息处理逻辑.
}
};
Looper.loop();
}
}
主线程中可以使用Handler的原因是在ActivityThread中程序的入口main方法中调用了Looper.prepare();和Looper.loop();
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.prepare() Looper.prepare()在每个线程只允许执行一次,该方法给当前线程通过TL绑定一个线程所属的唯一一个实例。
private static void prepare(boolean quitAllowed) {
//看当前线程是否已通过TL绑定对应的实例,有的话抛异常,所以prepare方法只允许调用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并通过TL建立与线程的绑定关系
sThreadLocal.set(new Looper(quitAllowed));
}
- ThreadLocal 我们看一下ThreadLocal.set方法
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
if (map != null)
map.set(this, value); //以当前ThreadLocal作为键,Looper作为值建立绑定关系
else
createMap(t, value);
}
}
ThreadLocal.get方法
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//通过当前ThreadLocal作为键取出对应的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
为什么要选择ThreadLocal建立绑定关系?
因为我们是要让每一个线程都有且只有一个唯一的Looper实例,这时就可以使用ThreadLocal给每个线程绑定一个唯一实例的特性很方便的建立绑定关系。如果不采用ThreadLocal去实现,那么只能使用一个LooperManager管理类然后通过其中的Map去统一管理,那么这样无疑是很麻烦的
。ThreadLocal只是作为主键,如果是Thread作为主键,那么很显然一个线程只能与一个对应的对象建立绑定关系,这显然是非常不合理的。
-
Looper.loop() loop()进入循环模式,主要进行了
如下几点
:
- 获取当前线程的Looper实例
- 通过Looper获取MessageQueue实例
- 开启死循环并在其中调用MessageQueue的next方法不断轮询MessageQueue的头结点
public static void loop() {
final Looper me = myLooper(); //获取TLS存储的Looper对象 -->sThreadLocal.get()
final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列
Binder.clearCallingIdentity();
//确保在权限检查时基于本地进程,而不是调用进程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //进入loop的主循环方法
Message msg = queue.next(); //可能会阻塞
if (msg == null) { //没有消息则退出循环,调用Looper.quit()方法后返回空的message,随即退出
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg); //用于分发Message
if (logging !=