上文本身一个Handler机制对 Handler 机制进行了一番探索和思考,对 Handler 的设计意图有了本身的认识和思考,那么这篇文章咱们将从 android 系统 Handler 机制的源码出发,来看一看 android 系统 Handler 机制的设计,对比一下上文中本身写的 Handler 机制。html
阐述逻辑以下:java
知道为何 Activity 的 onCreate() / onStart() 等生命周期函数都是运行在主线程当中吗?在下面的主线程 Handler 的建立和消息处理过程分析中,这个问题将会获得解答。android
主线程的建立和使用方式和自定义一个Handler机制文章中的 Handler 机制使用方式同样,首先建立 Looper ,而后向 Looper 中的消息容器发送消息,最后 Looper 开启死循环获取消息/处理消息。c++
//ActivityThread.java
public static void main(String[] args) {
......
// 建立 Looper
Looper.prepareMainLooper();
// App 启动流程入口,会发送向 Looper 的消息队列中添加一系列的消息以启动 App。
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 获取 Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 启动 looper,开启死循环处理消息
Looper.loop();
......
}
复制代码
下面就直接看 looper 的建立过程,这里的重点有两处。git
// Looper.java
// 给当前线程建立 Looper,为何说给当前线程?
public static void prepareMainLooper() {
// 建立 Looper ,false 表示该 Looper 不容许开发者调用退出函数退出 looper 死循环。
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
// 若是该线程中已经有一个 Looper 就不容许建立多个 Looper。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 建立一个 Looper 实例,传入是否容许该 Looper 退出死循环的参数,并添加到该线程的 ThreadLocal 中
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 构造函数
private Looper(boolean quitAllowed) {
// 初始化 MessageQueue ,传入是否容许退出函数
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
// 获取该线程的 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
复制代码
loop() 方法中也有几个重点。面试
public static void loop() {
// 死循环
for (;;) {
// 重点!!!!重点!!!!重点!!!!!!该 queue 即为 Looper 构造函数中的 MessageQueue
// 该 next() 方法表示从 MessageQueue 中获取消息,若是没有消息在该函数中会调用 Linux 的
// epoll 机制陷入沉睡状态,等待有消息加入 MessageQueue 时被唤醒。
Message msg = queue.next(); // might block
if (msg == null) {
// 从 MessageQueue 中获取不到消息就表示退出死循环,为空的具体的能够看 MessageQueue.quit() 函数。
return;
}
// 消息被执行前输出消息,能够在 FPS 监控中经常使用
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
......
// 重点!!! target 为向 MessageQueue 发送消息的 Handler 实例。
// 处理 msg 消息。
msg.target.dispatchMessage(msg);
......
// 消息执行完成后输出信息,对应以前的 println()
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
复制代码
next() 方法中的重点有几个。安全
epoll 机制。 关于 epoll 机制,这个主要是 Linux 方面的知识,我也仍是只知其一;不知其二,这里推荐系列文章,写的能够,值得一看。markdown
如何处理延时 Message 消息和按顺序执行?app
这里分为两步处理的,第一步是在向 MessageQueue 中会根据消息加入时间和延时时间进行排序,加入时间在前和延时时间短的 Message 排在队列的前面。第二步是在 MessageQueue 获取到要执行的 Message 以后,会判断其执行时间是不是当前,若不是,则会计算时间差,使用该时间差调用 epoll 机制进入定时睡眠。框架
同步屏障的概念以及同步消息处理。
为了优先执行异步 Message ,如:渲染 UI 、响应用户操做等消息所以设计了同步障碍这个机制。当有异步 Message 须要优先执行时,先向 MessageQueue 的头部插入一条 target(即处理消息的Handler) 为空的消息(设置同步障碍),并将须要执行的 Message 的是不是异步消息的标志设置为 true(isAsynchronous()返回值为 true)。在后续的 next() 方法中,当碰到同步障碍时就会忽略同步消息,选择性的优先执行异步消息。
处理 looper 退出。
退出的处理也是在 MessageQueue 中处理,由 MQ 的退出触发 Looper 的退出。主要流程是移除队列中全部 Msg。由是否移除即将处理的 msg 为去区别点,分两种方法进行消息移除,一种是保留即将执行的 msg,消息执行完毕以后退出,一种是直接移除全部 msg,直接退出。
IdleHandler 的使用。
当 MessageQueue 中后续没有消息执行时,若是有 IdlerHander 就是执行 IdlerHander,即当 Handler 空闲时执行。
另外咱们要注意,同一 IderHander 会被触发屡次,咱们须要处理这种状况。这里举例一种,实现 IderHandler 接口,queueIdle() 返回 false ,待执行完毕后,即会移除该 IderHandler。
// MessageQueue.java
Message next() {
// mPtr 为 c++ 层指针,当 MessageQueue 须要退出时,会将该值赋为 0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// IdleHandler 数量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 利用 epoll 机制沉睡的时间
int nextPollTimeoutMillis = 0;
for (;;) {
// 利用 epoll 机制进入沉睡,
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 尝试获取 Message
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 判断当前 Message 是不是同步障碍,标志就是 msg.target 为空
if (msg != null && msg.target == null) {
// 寻找同步消息执行,标志是 msg.isAsynchronous()
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// msg.when 会在消息被发送时进行赋值
if (now < msg.when) {
// 当前消息是否到了执行时刻,没到将 epoll 沉睡时间计算出来
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 到了?返回目标 msg ,同时安排好下一个 msg。
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 {
// 没有更多的 msg 将该值赋值为-1,将会在 nativePollOnce() 函数中陷入沉睡等待唤醒。
nextPollTimeoutMillis = -1;
}
// 若是启动调用了退出函数(主线程不容许退出),则处理退出流程,返回 Null 值,在 Looper 中会获得 null 值会直接退出 loop() 中的死循环,该线程若无其它操做,运行完毕后将会天然退出。
if (mQuitting) {
dispose();
return null;
}
// 注意!!!能运行到这里的时间点,当 MQ 中没有更多消息时才会运行到这里
// 计算 IdleHandler 的数量,准备触发
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有 IdleHandler 执行,进行下一次循环。
mBlocked = true;
continue;
}
...
}
// 循环执行已添加的 IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
...
// 判断是否移除该 IdleHandler
keep = idler.queueIdle();
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
...
}
}
复制代码
在上面咱们已经说明了什么是同步障碍、何为添加同步障碍。而设计同步障碍的缘由就是为了优先执行某些消息,这里的表明就是系统中更新 UI ,这里能够去看ViewRootImpl.scheduleTraversals()
函数(View三大步骤的地方哦)和 UI 更新相关机制,这里就不作展开。这里咱们来看 MessageQueue.postSyncBarrier()
函数,这里是添加同步障碍的地方,同时结合上一步骤处理 msg 的逻辑来看。
另外一个设置异步消息就十分简单了,调用 Message.setAsynchronous()
函数,传入 true 便可(处理逻辑见上一步骤分析)。
// MessageQueue.java
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
/// 重点!!!这里的 msg 没有 traget,在处理逻辑中有体现。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 后面的操做就是将承担同步障碍做用的 msg 添加到队列头
return token;
}
}
复制代码
首先明确该类被设计出来的做用是什么,解决了怎样的问题。
ThreadLocal 类出如今 jdk 1.2 中,此时 java 的 synchronized 关键字提供的锁机制还未优化(没有锁升级,直接重量级锁)。当程序中某些变量只能被一个线程使用,若使用 synchronized 关键提供线程安全会增长系统开销,而 ThreadLocal 就提供了在不加锁的状况下,保证某个变量只能被一个线程访问的机制。使用简便,只要 set() / get() 一番便可。
关于使用内存泄露的问题
该问题是面试中常问。因为 ThreadLocal.ThreadLocalMap 保存数据构建 Entry 时 key 值(TheadLocal)使用了弱引用,看成为 key 的 ThreadLocal 被回收后,当时持有 ThreadLocal 还未中止,这时存在这 Thread.threadLocals 指向 value 的一条引用,致使 key 已经为空的 value 对象实例没法回收,出现内存泄露问题。
内存泄露问题推荐阅读 > www.cnblogs.com/aspirant/p/…
Thradlocal 详细分析 > www.cnblogs.com/fsmly/p/110…
在平常开发工做当中,可能是使用主线程的 Handler 作事情,不多会在其余线程中使用。固然,想要使用也很简单,只须要三步。
Looper.prepare()
函数建立 Looper / MessageQueue,使用该方法建立的 Looper 是能够退出地。退出
直接调用 Looper.myLooper().quit()
或者 Looper.myLooper().quitSafely()
方法便可,关于二者的区别在上文中有阐述。
主线程 Handler 为何不能退出?
不是不能退出,是开发者不能调用主线程 Looper 退出的方法,调用会抛出异常。
缘由在于,Looper 的死循环的做用不仅仅是处理消息,同时还保证了主线程无外力做用下永远在运做的机制,iOS / Windows 等开发框架都有类似的机制。
Looper 死循环的退出即表示 App 主线程退出,App 将退出运行。
该类继承自 Thread,内部建立了一个使用使用了 Handler 机制,start() 以后开启了 Looper 的死循环处理消息,Looper 能够退出,Message 消息再该线程中处理。能够利用其执行有序的任务!
继承自 Service 拥有 Serivce 的特性,同时内部使用率 HandlerThread,当 onStart() 函数触发时,向 HandlerThread 中的 MessageQueue 中添加一条 Message ,自建的 Handler 处理完毕以后当即调用 stopSelf() 函数中止 Service 的运行。能够利用其在后台执行任务,执行完成以后不须要管理,自动中止。固然,记得和通常 Service 同样清单文件注册。
Only the original thread that created a view hierarchy can touch its views.
当咱们在子线程操做 UI 时系统会抛出这样一个错误。首先明确,这个错误并非说不能在主线程更新 UI ,而是只能在建立了 ViewRootImpl 实例的线程中更新 UI。抛出这个错误的是 ViewRootImpl 中的 checkThread()
函数,目的是检测当前请求更新 UI 的线程是否是建立 ViewRootImpl 的线程,为何须要检测呢?缘由在于在调用 requestlayout()
函数后,会向当前线程的 Looper 中添加一条同步屏障,若当前更新 UI 的线程不是建立 ViewRootImpl 的线程,那么该同步屏障不会起做用,那么后续极有可能不回第一时间响应用户操做更新 UI 。
其实子线程是能够更改 UI 的,在 onCreate() / onResume() 函数中都是能够的。具体缘由,能够去看 ViewRootImpl 建立时机和 setContentView() 过程。
推荐文章:关于 TextView 子线程更新文字问题 / 为何要 checkThread()
为何主线程不能执行耗时操做?从设计上来考虑,单线程更新 UI ,若在 UI 线程执行耗时操做,会致使 UI 更新延迟。所以在 UI 线程(主线程)执行操做时,系统会添加耗时检测机制,UI 线程超过必定时间无响应即会弹出咱们常见的 ANR 警告弹框。
关于这篇问题,强烈推荐 gityuan 大佬的系列文章。
Handler 存在内存泄露的问题主要是 Java 语言中,非静态内部类和匿名内部类会持有外部类的引用,而这个外部类一般就是 Activity 了。当存在延时 Message 还未触发或者有子线程运行耗时任务又持有 Handler 引用,就极有可能引起内存泄露。
这里主要当 Message 建立频繁时,使用优化手段进行优化。也就是 java 语言中优化对象的几种方式。
Message 中的 obtain() 函数已经提供了对象池供咱们优化使用。
这个就推荐文章了,我也还在研究学习中,若是有了本身的理解,会有文章出来说讲的。
以前有讲过哦,不记得了????
滑上去再喽一眼吧!
首先,Looper 和 MessageQueue 中都是使用率 synchronized
来保证线程安全。
线程切换?线程之间共享资源,向目标线程中的 MessageQuque 中添加 Message ,目标线程进行处理就完成了线程切换。
经过 Looper 获取到 MessageQueue 添加 IdleHander 便可。
该机制是当 MessageQueue 中没有 Message 了(即空闲时)或者处在一个延时消息的执行时,就会调用 IdleHander 执行。注意!这个执行时机是比较模糊的。
当 IdleHander 的 queueIdle()
返回 true 时表示保留该 IdlerHander ,下次继续执行,为 false 则移除。
系统在以下几个地方使用了
推荐阅读 www.wanandroid.com/wenda/show/…
Handler 机制的设计目的是提供一种单线程消息处理机制,而 UI 框架的单线程更新机制又使得 Handler 自然的能完成能承担这个任务,这个呢是 Handler 的重点,是 Handler 的本质。清楚了本质,那么诸如其余的同步屏障、异步消息和 ThreadLocal 等,为了实现这个本质中所采起的方法,也能很快的分而治之的弄明白。
总之,对于 adnroid frmework 的学习之路,我仍是推荐本身思考、依靠源码、踩在巨人的肩膀上的方法路线进行学习。