Android应用程序消息机制

133 阅读4分钟

概述

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的交互过程

image.png

线程的消息队列创建

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在主线程中被调用