先来一个本身画的Handler机制总体流程图,本文不会带着你走一遍源码,只会对重点须要注意的地方以及一些细节的处理作出解释,让你更好的了解Handler机制总体的运做
。 bash
也能够说成 Looper 所在的线程,并非建立 Handler 的线程,Handler新建时持有的Looper在哪一个线程,最后Handler.handleMessage()就在哪一个线程执行
;由于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();
复制代码
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));
}
复制代码
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方法async
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();
}
复制代码
由于咱们是要让每个线程都有且只有一个惟一的Looper实例,这时就可使用ThreadLocal给每一个线程绑定一个惟一实例的特性很方便的创建绑定关系。若是不采用ThreadLocal去实现,那么只能使用一个LooperManager管理类而后经过其中的Map去统一管理,那么这样无疑是很麻烦的
。ThreadLocal只是做为主键,若是是Thread做为主键,那么很显然一个线程只能与一个对应的对象创建绑定关系,这显然是很是不合理的。ide
以下几点
:
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 != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}
复制代码
用于终止loop循环,主线程中不容许调用(能调用的话至关于主程序退出了,应用就直接挂掉了),子线程中要退出时须要主动调用,不然会形成子线程中一直处于死循环状态没法退出
。调用后会改变MessageQueue中的mQuitting标志位,next方法中若是检测到mQuitting为true则直接返回null,loop方法中检测到message是null则直接return终止死循环从而结束逻辑使得线程能够退出。public void quit() {
mQueue.quit(false); //所有消息移除
}
public void quitSafely() {
mQueue.quit(true); //只移除没有执行的消息
}
复制代码
会根据Message发送时的时间戳肯定Message在MessageQueue中的位置。函数
msg.when
这个时间戳进行顺序的排序,若是非延迟消息则msg.when为系统当前时间,延迟消息则为系统当前时间+延迟时间(如延迟发送3秒则为SystemClock.uptimeMillis() + 3000)当咱们发送消息的时候其实最后都会调用到sendMessageAtTime
这个方法,这个方法其实最终会把你的Handler对象赋值给Message实体,咱们最终发送消息都是发送的Message实体,而后调用MessageQueue的enqueueMessage方法oop
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码
boolean enqueueMessage(Message msg, long when) {
// 每个普通Message必须有一个target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) { //正在退出时,回收msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { //p为null(表明MessageQueue没有消息) 或者msg的触发时间是队列中最先的, 则进入该分支并将加入的message放入头结点
msg.next = p;
mMessages = msg;
needWake = mBlocked; //当阻塞时须要唤醒
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //开启死循环遍历message
prev = p;
p = p.next;
if (p == null || when < p.when) { //退出条件为当前message的下一个节点为null或者当前节点的message执行时间大于你放入message的执行时间
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //进行赋值
prev.next = msg;
}
//消息没有退出,咱们认为此时mPtr != 0
if (needWake) {
nativeWake(mPtr); //往管道中写数据,唤醒阻塞,nativePollOnce方法阻塞解除
}
}
return true;
}
复制代码
Message的入列和出列实际上是一个很典型的**生产者-消费者模型**,其中使用了epoll机制,当没有消息的时候会进行阻塞释放CPU时间片避免死循环形成性能的浪费。
虽然是不断循环取出头结点的Message进行分发处理可是若是没有消息时它是阻塞在 nativePollOnce这个native方法中的
,当咱们enqueue插入Message时会触发nativeWake这个方法去唤醒
,从而nativePollOnce阻塞解除继续遍历MessageQueue取出头结点去处理。因此在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,无论在哪一个线程,这个对象都是同一个
,由于Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操做(如增长的同时正在轮询获取Message,有可能形成MessageQueue中最终结果的不肯定性)。Message next() {
final long ptr = mPtr;
if (ptr == 0) { //当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操做,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
//nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
//nextPollTimeoutMillis 为0,不阻塞
//nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
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 {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//当消息
复制代码
当遍历出Message后Message会获取其中的Handler并调用Handler的dispatchMessage进行分发,这时也会有三个优先级。post
一般咱们能够利用 Callback 这个拦截机制来拦截 Handler 的消息,场景如:Hook ActivityThread.mH,在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎全部的插件化框架都使用了这个方法。)
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //callback是一个runnable对象
handleCallback(msg);
} else {
if (mCallback != null) { //mCallback是新建Handler时传进去的Callback接口
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //默认空实现,通常咱们会本身复写实现这个方法
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
复制代码
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
复制代码
Handler的实质其实就是共享内存
,咱们看一个例子。性能
public class Demo {
List mList= new ArrayList()<Message>;
public static void main(String[] args) {
//子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000l);
mList.add(new Message());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//主线程开启死循环不断遍历list取头结点
for (;;) {
//主线程中处理
Message message = mList.get(0);
if (message != null) {
//处理
}
}
}
}
复制代码
咱们为了将数据最终从子线程切换到主线程中去其实只要拿到mList这个实例便可,这个mList对应的其实就是MessageQueue,而咱们要获取MessageQueue只要获取对应的Looper便可,当咱们Handler新建的时会根据Handler所在线程获取到其线程正在轮询消息的Looper对象,Handler中的mQueue指向的是其所在线程的Looper中的mQueue(固然也能够手动指定一个其余线程的Looper,不指定的话默认为当前线程的Looper),由此即可在发送Message时将任务放到Looper所在线程中处理。
ui
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); //threadLocal.get获取线程对应的Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //经过Looper获取MessageQueue
mCallback = callback;
mAsynchronous = async;
}
复制代码
首先咱们来看形成ANR的缘由:
咱们再来看一下APP的入口ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码
咱们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,若是它中止了,应用也就中止了。真正的阻塞是由于轮询出message后在处理message消息的时候因为执行了耗时操做致使了ANR,而不是死循环致使的阻塞,没有消息处理的时候消息队列是阻塞在nativePollOnce方法中的,**这个方法使用的是epoll管道机制,Linux底层执行后会释放CPU避免不断死循环形成的CPU浪费。**
当咱们用Handler发送延时消息时,若是在延时期间用户关闭了 Activity,那么该 Activity 会泄露。
这个泄露是由于 Message 会持有 Handler,而又由于 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就致使 Activity 泄露。 解决该问题的最有效的方法是:将 Handler 定义成静态的内部类(静态内部类不会持有外部类引用,可是静态内部类调用不到外部类的非静态属性和方法,因此咱们须要在内部类中使用弱引用持有Activity,使用弱引用调用到Activity中的方法),并及时移除全部消息。 泄漏时的引用链 Activity->Handler->Message->MessageQueue ,延迟消息会一直在MessageQueue中等待处理,在等待的过程当中有可能会形成内存泄漏。
示例代码以下:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
复制代码
而且再在 Activity.onDestroy() 前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
复制代码
这样双重保障,就能彻底避免内存泄露了。 注意:单纯的在 onDestroy 移除消息并不保险,由于 onDestroy 并不必定执行(如报异常)。
从下边MessageQueue的源码可知道,IdleHandler即在MessageQueue应该被阻塞以前去调用(固然前提是你要讲自定义的IdleHandler加入到集合中)
。 IdleHandler接口表示当MessageQueue发现当前没有更多消息能够处理的时候,
则顺便干点别的事情的callback函数(即若是发现idle了, 那就找点别的事干). callback函数有个boolean的返回值, 表示是否keep. 若是返回false, 则它会在调用完毕以后从mIdleHandlers中移除. IdleHandler 能够用来提高提高性能,主要用在咱们但愿可以在当前线程消息队列空闲时作些事情(譬如UI线程在显示完成后,若是线程空闲咱们就能够提早准备其余内容)的状况下,不过最好不要作耗时操做
。
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操做,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
//nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
//nextPollTimeoutMillis 为0,不阻塞
//nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
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 {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//若是当前MessageQueue头结点为空(没有消息要处理了)或者当前系统时间<消息触发时间
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
//看是否加入了idleHandler
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//没有idle handlers 须要运行,则循环并等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //去掉handler的引用
boolean keep = false;
try {
//若是queueIdle()返回false则当前idlehandler只能运行一次
keep = idler.queueIdle(); //idle时执行的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
//queueIdle返回false,移除idlehandler
mIdleHandlers.remove(idler);
}
}
}
//重置idle handler个数为0,以保证不会再次重复运行
pendingIdleHandlerCount = 0;
//当调用一个空闲handler时,一个新message可以被分发,所以无需等待能够直接查询pending message.
nextPollTimeoutMillis = 0;
}
}
复制代码
同步屏障是由系统发送,通常用于刷新UI(如16ms刷新一次界面)。当设置了同步屏障以后,next函数将会忽略全部的同步消息,返回异步消息。换句话说就是,设置了同步屏障以后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增长了一种简单的优先级机制,异步消息的优先级要高于同步消息。
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部经过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
复制代码