概述
Android应用程序与传统的PC应用程序一样,都是消息驱动的。也就是说,在Android应用程序主线程中,所有函数都是在一个消息循环中执行的。Android应用程序其它线程,也可以像主线程一样,拥有消息循环。Android应用程序主线程是一个特殊的线程,因为它同时也是UI线程以及触摸屏、键盘等输入事件处理线程。主线程对消息循环很敏感,一旦发生阻塞,就会影响UI的流畅度,甚至发生ANR问题。
主要讲Android应用程序线程消息循环原理,主要涉及到Handler和Looper两个类,以及根据消息循环的不同使用场景,总结出三种线程使用模型。掌握Android应用程序消息处理机制,有助于我们熟练地使用同步和异步编程,提高程序的运行性能。
- 线程与消息的关系
- 线程的消息队列创建
- 线程的消息循环
- 线程的消息发送
- 线程的消息处理
- 消息在异步任务的应用
线程与消息的关系
Android应用程序有两种类型的线程
-
带有消息队列,用来执行循环性任务
- 有消息时就处理
- 没有消息时就睡眠
- 例子:主线程、android.os.HandlerThread
-
没有消息队列,用来执行一次性任务
- 任务一旦执行完成便退出
- 例子:java.lang.Thread
带有消息队列的线程四要素
- Message(消息)
- MessageQueue(消息队列)
- Looper(消息循环)
- Handler(消息发送和处理)
Message、 MessageQueue、 Looper和Handler的交互过程
线程的消息队列创建
MessageQueue与Looper的关系
例1:ServerThread
例2:ActivityThread
Looper.prepare/prepareMainLooper
new Looper – 创建Java层的Looper
new MessageQueue
nativeInit
new NativeMessageQueue
new Looper – 创建C++层的Looper
pipe
- 一种进程/线程间通信机制
- 包含一个写端文件描述符和一个读端文件描述符
- Looper通过读端文件描述符等待新消息的到来
- Handler通过写端文件描述符通知Looper新消息的到来
epoll
- 一种I/O多路复用技术,select/poll加强版
- epoll_create:创建一个epoll句柄
- epoll_ctl:设置要监控的文件描述符
- epoll_wait:等待监控的文件描述符发生IO事件
- Looper利用epoll来监控消息队列是否有新的消息,也就是监控消息管道的读端文件描述符
为什么要用epoll
Looper除了监控消息管道之外,还需要监控其它文件描述符,例如,用来接收键盘/触摸屏事件的文件描述符
线程的消息循环
例1:ServerThread
例2:ActivityThread
Looper.loop – Java层的Looper
MessageQueue.next
nativePollOnce
NativeMessageQueue.pollOnce
Looper.pollOnce – C++层的Looper
Looper.pollInner
Looper.awoken
线程的消息发送
常用的消息发送接口
- Handler.sendMessage
带一个Message参数,用来描述消息的内容 - Handler.post
带一个Runnable参数,会被转换为一个Message参数
Message
Handler.sendMessage/post
Handler.sendMessageDelayed
Handler.sendMessageAtTime
Handler.enqueueMessage
MessageQueue.enqueueMessage
备注:
mBlocked:Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
nativeWake
NativeMessageQueue.wake
Looper.wake
回顾消息循环过程
Handler.dispatchMessage
消息在异步任务的应用
在主线程为什么要用异步任务?
-
主线程任务繁重
- 执行组件生命周期函数
- 执行业务逻辑
- 执行用户交互
- 执行UI渲染
-
主线程处理某一个消息时间过长时会产生ANR
- Service生命周期函数 – 20s
- Broadcast Receiver接收前台优先级广播函数 –10s
- Broadcast Receiver接收后台优先级广播函数 – 60s
- 影响输入事件处理的函数 – 5s
- 影响进程启动的函数 – 10s
- 影响Activity切换的函数– 2s
备注:
一个进程如果正在接收一个前台优先级广播,那么它所在的进程调度组就为Process.THREAD_GROUP_DEFAULT ,如果是正在接收后台优先级广播,那么它所在的进程调度组就为Process.THREAD_GROUP_BG_NONINTERACTIVE。发送广播时,通过Intent.FLAG_RECEIVER_FOREGROUND标志来指定是前台优先级还是后台优先级
基于消息的异步任务接口
- android.os.HandlerThread
适合用来处于不需要更新UI的后台任务 - android.os.AyncTask
适合用来处于需要更新UI的后台任务
android.os.HandlerThread
启动HandlerThread
HandlerThread handlerThread = new HandlerThread("Handler Thread");
handlerThread.start();
向HandlerThread分配任务
ThreadTask threadTask = new ThreadTask();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(threadTask);
退出HandlerThread
handlerThread.quit();
android.os.AyncTask
- 在进程内维护一个线程池来执行任务
- 任务在开始执行、执行过程以及结束执行时均可以与主线程进行交互
- 任务是通过一个Handler向主线程发送消息以达到交互的目的
例子
AysnTask的相关成员变量定义
用来向主线程发送消息的InternalHandler
任务执行过程或者结果数据--AsyncTaskResult
创建任务
执行任务
触发AsyncTask.doInBackground在工作线程中被调用
更新任务进度
MESSAGE_POST_PROGRESS通过sHandler发送到主线程的消息队列
触发AsyncTask.onProgressUpdate在主线程中被
任务结束时,MESSAGE_POST_RESULT通过sHandler发送到主线程的消息队列,触发AsyncTask.finish在主线程中被调用
进一步触发AsyncTask.onPostExecute在主线程中被调用
任务中途取消时,MESSAGE_POST_CANCEL通过sHandler发送到主线程的消息队列,触发AsyncTask.onCancel在主线程中被调用