荣成城市规划建设局网站,江宁城乡建设局网站,建设报名系统是正规网站吗,wordpress 主题制作全Handler 现在几乎是 Android 面试的必问知识点#xff0c;大多数 Adnroid 工程师都在项目中使用过 Handler。主要场景是子线程完成耗时操作的过程中#xff0c;通过 Handler 向主线程发送消息 Message#xff0c;用来刷新 UI 界面。
下面我们来了解 Handler 的发送消息和处…Handler 现在几乎是 Android 面试的必问知识点大多数 Adnroid 工程师都在项目中使用过 Handler。主要场景是子线程完成耗时操作的过程中通过 Handler 向主线程发送消息 Message用来刷新 UI 界面。
下面我们来了解 Handler 的发送消息和处理消息的源码实现。分析源码的时候最好找到一个合适的切入点Handler 源码的一个切入点就是它的默认构造器。
从 new Handler 开始
在无参的构造函数里调用重载的方法并分别传入 null 和 false。并且在构造方法中给两个全局变量赋值mLooper 和 mQueue。 mLooper 和 mQueue 都是通过 Looper 来获取具体代码如下 可以看出mQueue 是 Looper 中的一个全局变量类型是 MessageQueue 类
这个 Looper 是什么何时被初始化
Looper 介绍
不知大家在开发的时候有没有思考过这么一个问题呢启动一个 Java 程序的入口函数是 main 方法当 main 方法执行完毕之后此程序停止运行也就是进程会自动终止。但是当打开一个 Activity 之后只要不按下返回键 Activity 会一直显示在屏幕上。也就是 Activity 所在进程会一直处于运行状态。实际上Looper 内部维护一个无限循环保证 App 进程持续进行。
Looper 初始化
ActivityThread.java 的 mian 方法是一个新 app 进程的入口其具体实现如下 图中1处就是初始化当前进程的 Looper 对象图中2处调用 Looper 的 loop() 方法开启无限循环。
prepareMainLooper() 方法如下 图中1处在 prepareMainLooper() 方法中调用 prepare() 方法创建 Looper 对象将 new 出的 Looper 设置到线程本地变量 sThreadLocal 中也就是说创建的 Looper 与当前线程发生了绑定。
Looper 的构造方法如下 可以看出在构造方法中初始化了消息队列 MessageQueue 对象。
Looper 方法执行完之后会在图中3处执行 myLooper() 方法从 sThreadLocal 中取出 Looper 对象并复制给 sMainLooper 对象。图中2处在创建 Looper 对象之前会判断 sThreadLocal 中是否已经绑定过 Looper 对象如果是则抛出异常。这行代码的目的是确保在一个线程中 Looper.prepare() 方法只能被调用1次。比如执行如下代码程序秒崩 打印日志如下 不是说调用2次 prepare 才会抛异常吗为什么 MainActivity 中只调用了1遍就导致程序崩溃
这是因为在 MainActivity 所在进程被创建时Looper 的 prepare 方法已经在 main 方法中调用了1遍这会导致一个非常重要的结果 prepare 方法在一个线程中只能被调用1次 Looper 的构造方法在一个线程中只能被调用1次 最终导致 MessageQueue 在一个线程中只会被初始化1次
也就是说UI 线程中只会存在一个 MessageQueue 对象。后续我们通过 Handler 发送的消息都会发送到这个 MessageQueue 当中。
Looper 负责做什么事情
用一句话总结就是Looper 做的事情是不断从 MessageQueue 中取出 Message然后处理 Message 中指定的任务。
在 ActivityThread 的 main 方法中除了调用 Looper.prepareMainLooper() 初始化 Looper 对象之外还调用了 Looper.loop() 方法开启了无限循环。Looper 的主要功能就是在这个循环中完成的。 很显然loop() 方法执行了一个死循环这也是一个 Android app 进程能够持续运行的原因。图中1处不断调用 MessageQueue 的 next 方法取出 message。如果 message 不为 null则调用图中2处进行后续处理。具体就是从 message 中取出 target 对象然后调用其 dispatchMessage() 方法处理 message 自身。那么这个 target 是谁呢
查看 Message.java 源码可以看出 target 就是 Handler 对象如下所示 Handler 的 dispatchMessage() 方法 可以看出在 dispatchMessage 方法中调用一个空方法 handleMessage()而这个方法正是我们创建 Handler 时需要复写的方法。那么 Handler 是何时将其设置为一个 message 的 target 呢在 Handler 的 enqueueMessage 方法中
Handler 的 sendMessage() 方法
Handler 有几个重载的 sendMessage() 方法但是基本都大同小异代码具体如下 经过几层调用之后sendMessage 最终会调用 enqueueMessage() 方法将 message 插入到消息队列 MessageQueue 当中。这个消息队列就是我们上面分析的在 ActivityThread 的 main 方法中通过 Looper 创建的 MessageQueue。
Handler 的 enqueueMessage 方法 可以看出在图中1处 enqueueMessage 方法中将 Handler 自身设置为 Message 的 target 对象。因此后续 message 会调用此 Handler 的 dispatchMessage 来处理。图中2处会判断如果 message 中的 target 没有被设置则直接抛出异常。图中3处会按照 Message 的时间(when)来有序的插入 MessageQueue 中。可以看出 MessageQueue 实际上是一个有序队列只不过是按照 message 的执行时间来排序的。
至此Handler 的发送消息和消息处理流程已经介绍完毕。
有关 Handler 的面试题
1. Handler 的 post(Runnable) 与 sendMessage() 有什么区别
看一下 post(Runnable) 的实现源码如下 实际上 post(Runnable) 会将 Runnable 复制到 message 的 callback 变量中。那么这个 Runnable 是在什么地方被执行的呢Looper 从 MessageQueue 中取出 Message 之后会调用 dispatchMessage 方法进行处理。再看一下其实现 可以看出 dispatchMessage 分两种情况如果 message 的 callback 不为null一般为通过 Post(Runnable) 方式会直接执行 Runnable 的 run 方法。因此这里的 Runnable 实际上就是一个回调接口和线程 Thread 没有任何关系如果 message 的 callback 为 null这种一般为 sendMessage 方式用 Handler 的 handleMessage() 方法进行处理。
2. Looper.loop() 为什么不会阻塞主线程
上面我们提到Looper 中的 loop 方法实际上是一个死循环但是我们的 UI 线程却并没有被阻塞反而还能够进行各种手势操作这是为什么呢
在 MessageQueue 的 next 方法中有如下一段代码 nativePollOnce 方法是一个 native 方法当调用此 native 方法时主线程会释放 CPU 资源进入休眠状态直到下一条消息到达或者有事务发生通过往 pipe 管道写端写入数据来唤醒主线程工作。
3. Handler 的 sendMessageDelayed 或者 postDelayed 是如何实现的
在向 MessageQueue 队列中插入 Message 时会根据 Message 的执行时间排序而消息的延时处理的核心实现是在获取 Message 的阶段。接下来看一下 MessageQueue 的 next 方法 图中蓝框处表示从 MessageQueue 中取出一个 Message但是当前的系统时间小于 message.when因此会计算一个 TimeOut 。目的是为了实现在 TimeOut 后再将 UI 线程唤醒。因此后续处理 TimeOut 的代码只会在 TimeOut 时间后才会被 CPU 执行。
注意在上述代码中也能看出。如果当前系统时间大于或等于 Message.when那么会返回 Message 给 Looper.loop()但是这个逻辑只能保证在 when 之前消息不被处理不能保证一定在 when 时被处理。 总结 应用启动是从 ActivityThread 的 main 开始的。先是执行了 Looper.prepare()该方法先是 nwe 了一个 Looper 对象又在私有的构造方法中创建了 MessageQueue 作为此 Looper 对象的成员变量。Looper 对象通过 ThreadLocal 绑定在 MainThread 中。 当创建 Handler 子类对象时在构造方法中通过 ThreadLocal 获取绑定的 Looper 对象并获取此 Looper 对象的成员变量 MessageQueue 作为该 Handler 对象的成员变量。 在子线程中调用上一步创建的 Handler 子类对象的 sendMessage(msg)方法时在该方法中将 msg 的 target 属性设置为自己本身同时调用成员变量 MessageQueue 对象的 enqueueMessage() 方法将 msg 放入 MessageQueue 中。 主线程创建好之后会执行 Looper.loop() 方法该方法获取与线程绑定的 Looper 对象继而获取该 Looper 对象的成员变量 MessageQueue 对象。并开启一个会阻塞不占用资源的死循环只要 MessageQueue 中有 msg就会获取该 msg 并执行 msg.target.dispatchMessage(msg) 方法msg.target 即上一步引用的 handler 对象此方法中调用了第二步创建 handler 子类对象时覆写的 handleMessage() 方法。