文档章节

通过源码分析Android 的消息处理机制

Cundong
 Cundong
发布于 2016/05/19 18:12
字数 1556
阅读 378
收藏 18
点赞 2
评论 1

#通过源码分析Android 的消息处理机制

我们知道,Android应用是通过消息来驱动的,每一个进程被fork之后,都会在该进程的UI线程(主线程)中启动一个消息队列,主线程会开启一个死循环来轮训这个队列,处理里面的消息。

通过Android进程的入口 ActivityThread#main 方法可以看到这个逻辑:


    public static void main(String[] args) {

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        Looper.loop();
	
	//只要运行正常,都不会执行到这段代码
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

另外,我们在平时开发中,如果想在子线程中更新UI,必须手动创建一个Looper对象,并且调用它的loop方法:

class TestThread extends Thread {
       public Handler mHandler;
	
        public void run() {

	    // 为当前线程创建Looper对象,同时消息队列MessageQueue也准备好了
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    
		    //这里可以更新UI了

                }
            }; 
  
	    // 开始处理消息队列中的消息
            Looper.loop();
        }
}

在这两个过程中,都看到了Looper的身影,因为Looper就是消息队列的管理者,我们调用他的prepareMainLooper和prepare方法,就会为当前线程创建好消息队列,调用looper方法就会开始消息队列的轮询处理过程。

要理解消息处理机制,除了Looper,我们还要理解其他类:

  • MessageQueue:消息队列
  • Message:消息
  • Handler:消息的处理者

UML类图

此处输入图片的描述

逐个类分析

Looper

先从Looper开始,上面提到的两个静态方法 prepareMainLooper()和prepare()的作用就是为当前线程创建消息队列,loop()调度这个消息队列。

来看Looper#prepareMainLooper:

 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
	    
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

prepareMainLooper是通过调用prepare()方法实现,并且通过myLooper()方法得到了当前线程的Looper,主线程的Looper,就是sMainLooper。

接着看prepare方法:

/**
*  @param uitAllowed 是否允许消息循环退出,在ActivityThread#main 中,我们创建的是主线程的消息循环,肯定不允许退出,其他地方,则可以退出
*/
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

这个方法中为每一个线程new了一个Looper对象,并且设置到sThreadLocal中。sThreadLocal是ThreadLocal类型的变量(线程局部变量),它保证了每一个线程有一个自己独有的Looper对象,

myLooper():

/**
* 从sThreadLocal中获取当前线程的Looper对象
*/
public static Looper myLooper() {
        return sThreadLocal.get();
}

方法很简单,就是返回了当前线程所独有的那个Looper对象。

Looer对象创建好了,同时消息队列也创建好了,接下来就是让整个消息机制跑起来,这就需要通过Looper#loop实现:

    public static void loop() {

	//得到当前线程的Looper
        final Looper me = myLooper();

	//没有Looper对象,直接抛异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

	//得到当前Looper对应的消息队列
        final MessageQueue queue = me.mQueue;
	
	//一个死循环,不停的处理消息队列中的消息,消息的获取是通过MessageQueue的next()方法实现
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
		
	    //调用Message的target变量(也就是Handler了)的dispatchMessage方法来处理消息
            msg.target.dispatchMessage(msg);

            msg.recycleUnchecked();
        }
    }

首先,得到当前线程的Looper,再通过Looper得到当前线程的消息循环,然后轮询这个消息队列,处理每一个消息,下面我就来看消息队列MessageQueue以及消息Message。

MessageQueue

MessageQueue就是Message队列,消息队列这一数据结构是通过一个Message链实现的,Message对象有一个next字段指向它的下一结点。

public final class MessageQueue {
	Message mMessages;

	//消息队列的初始化,销毁,轮询过程,阻塞,唤醒,都是通过本地方法实现的
	private native static long nativeInit();
	private native static void nativeDestroy(long ptr);
	private native static void nativePollOnce(long ptr, int timeoutMillis);
	private native static void nativeWake(long ptr);
	private native static boolean nativeIsIdling(long ptr);


	/**
	*  把消息加入到消息循环中
        *  @param msg 
	*  @param when 是么时候被执行,在消息队列中会按照时间排序
 	*/
	boolean enqueueMessage(Message msg, long when) {}

	/**
	*  把消息加入到消息循环中
        *  @param msg 
	*  @param when 什么时候被执行
 	*/
	boolean enqueueMessage(Message msg, long when) {}
	
        /**移除消息*/
	void removeMessages(Handler h, int what, Object object) {
}

Message对象放入队列通过enqueueMessage()方法实现,消息的依次获取是通过next()方法实现,其中当获取不到可处理Message对象时,该方法会进入等待状态。

Message

Message就是消息的封装对象,它实现了Parcelable接口,因此可以跨进程传输。

public final class Message implements Parcelable {
	
	//消息的标识符
	public int what;

	//两个int形的扩展参数
	public int arg1; 
	public int arg2;

	//一个Object拓展参数
	public Object obj;

	//消息发送者的UID
	public int sendingUid = -1;
	
	//消息带的data
	Bundle data;

	//target对象,也就是处理当前消息的Handler
	Handler target;

	//指向消息队列中下一个消息
	Message next;
	
	//消息同步锁
	private static final Object sPoolSync = new Object();

	//消息池
	private static Message sPool;

	//消息池大小
	private static int sPoolSize = 0;

	//消息池最大消息数量
	private static final int MAX_POOL_SIZE = 50;
}

对消息的管理是通过一个消息池来实现的,因为消息池的存在,我们在需要创建一个Message对象时,最好使用可复用消息对象的方法来创建它。

正确的使用方法是:

Message msg = mHandler.obtainMessage(WHAT);
msg.sendToTarget();

obtainMessage方法中实际上会调用Message#obtain方法来从消息池中取一个对象。

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

错误的使用方法:

Message msg = new Message();
msg.what = WHAT;
mHandler.sendMessage(msg);

Handler

Handler 就是消息循环(MessageQueue)中消息(Message)的真正处理者,通过Looper#loop可以看到:

public static void loop() {

        for (;;) {
            Message msg = queue.next(); // might block

            msg.target.dispatchMessage(msg);
        }
    }

Message的target属性就是一个Handler对象,对消息的处理实际上是通过Handler#dispatchMessage方法来处理的,而dispatchMessag方法会调用Handler#handleMessage方法,我们可以重写handleMessage方法 来真正的处理消息的回调。

© 著作权归作者所有

共有 人打赏支持
Cundong
粉丝 184
博文 28
码字总数 30973
作品 0
海淀
加载中

评论(1)

pennymei
pennymei
OneAPM 详悉 Android Apps 性能信息,协助你快速定位性能瓶颈,快速捕捉性能表现差的应用代码,发现错误并随时向你发送警报,这一切只需下载并安装 OneAPM Android SDK,然后更新你的应用,就可以实现啦~可以在官网注册试用哦~
Android Handler异步通信:深入详解Handler机制源码

前言 在开发的多线程应用场景中,机制十分常用 今天,我将手把手带你深入分析 机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 在多线程的应用场景中,将工作线程中需更新的操作信息 ...

carson_ho ⋅ 05/21 ⋅ 0

安卓自定义View进阶-事件分发机制原理

安卓自定义View进阶-事件分发机制原理 之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部...

猴亮屏 ⋅ 05/22 ⋅ 0

Handler消息处理机制分析

Handler经常用,然后自己总结一下下 一. What、Handler 是什么 Handler 与 Message、MessageQueue、Looper 一起构成了 Android 的消息机制,Android 系统通过大量的消息来与用户进行交互,V...

大二架构师 ⋅ 05/07 ⋅ 0

Android:手把手教你学会使用Google出品的序列化神器Protocol Buffer

前言 习惯用 数据存储格式的你们,相信大多都没听过 其实 是 出品的一种轻量 & 高效的结构化数据存储格式,性能比 真的强!太!多! 由于 出品,我相信已经具备足够的吸引力 今天,我将详细介...

Carson_Ho ⋅ 04/16 ⋅ 0

android蓝牙手柄、仿QQ看房、仿慕课网、数据库二维码框架等源码

Android精选源码 可自定义图片指示器并支持自定义Tab宽度的TabLayout源码(http://www.apkbus.com/thread-599243-1-1.html) android蓝牙控制手柄操作源码(http://www.apkbus.com/thread-59928...

逆鳞龙 ⋅ 05/15 ⋅ 0

10分钟了解Android的Handler机制

Handler机制是Android中相当经典的异步消息机制,在Android发展的历史长河中扮演着很重要的角色,无论是我们直接面对的应用层还是FrameWork层,使用的场景还是相当的多。分析源码一探究竟。从...

codeGoogle ⋅ 05/22 ⋅ 0

BroadcastReceiver的源码分析

android提供了广播机制,通过BroadcastReceiver可以在不同的进程间传递消息。类似于观察者模式,A应用通过注册广播表示A对消息subject感兴趣,当B应用发出subject类型的消息的时候,A应用就能...

JasmineBen ⋅ 05/18 ⋅ 0

内存泄露和LeakCanary的故事

新鲜文章请关注微信公众号: JueCode 今天我们来聊一聊Android中的内存泄露和内存泄露的检测工具LeakCanary。Java有垃圾回收线程为什么还会发生内存泄露?原因就是外部人为持有对象引用,持有...

juexingzhe ⋅ 05/22 ⋅ 0

聊聊为什么在activity启动的时候获取不到View的宽高

周五一大早看到一篇鸿洋推了一篇博客讲onResume中Handler.post(Runnable)为什么获取不到宽高?,细细读了两遍,有些收获,但依然有些不明白的地方,同时对有些细节不太认同。于是自己梳理了一...

Marco黑八 ⋅ 06/03 ⋅ 0

探索Activity启动流程-实现打开插件中的Activity

通过分析Activity的启动流程,探索Android的插件化,下面通过源码分析实现一个简单的插件化 打开一个 未安装apk中的Activity 开始分析 Activity的启动流程从 startActivity开始 然后通过 In...

liwg ⋅ 05/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

内核线程、轻量级进程、用户线程

线程与进程概念 在现代操作系统中,进程支持多线程。 进程是资源管理的最小单元; 线程是程序执行的最小单元。 即线程作为调度和分配的基本单位,进程作为资源分配的基本单位 一个进程的组成...

117 ⋅ 23分钟前 ⋅ 0

elasticsearch2.4.6升级为elasticsearch-5.5.0的经历

将elasticsearch-5.5.0 中的配置 path.data 指向原来的数据路径 即 path.data: /usr/local/src/elasticsearch-2.4.6/data 注意: elasticsearch-5.5.0 需要将jdk版本升级到1.8...

晨猫 ⋅ 24分钟前 ⋅ 1

lvm讲解 磁盘故障小案例

1

oschina130111 ⋅ 28分钟前 ⋅ 0

那些提升开发人员工作效率的在线工具

本文转载自公众号 Hollis 作为一个Java开发人员,经常要和各种各样的工具打交道,除了我们常用的IDE工具以外,其实还有很多工具是我们在日常开发及学习过程中要经常使用到的。 Hollis偏爱使用...

时刻在奔跑 ⋅ 40分钟前 ⋅ 0

restful风格 实现DELETE PUT请求 的web.xml的配置

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframe......

泉天下 ⋅ 45分钟前 ⋅ 0

Shell数组

Shell数组 Shell在编程方面比Windows批处理强大很多,无论是在循环、运算。 bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数...

蜗牛奔跑 ⋅ 55分钟前 ⋅ 0

nmap为了开发方便 可以做简单的修改

因为nmap扫描是默认使用的是nse脚本,但是在开发的过程中需要修改后缀(主要是因为后缀为lua才能显示高亮,所以这里用一个取巧的办法) nse_main.lua文件中我们找到如下代码 local t, path = cn...

超级大黑猫 ⋅ 59分钟前 ⋅ 0

springmvc获取axios数据为null情况

场景:前端用了vue没有用ajax与后台通信,用了axios,但是在代码运行过程中发现axios传递到后台的值接受到数据为null。 问题原因:此处的问题在与axios返回给后台的数据为json类型的,后台接...

王子城 ⋅ 今天 ⋅ 0

hadoop技术入门学习之发行版选择

经常会看到这样的问题:零基础学习hadoop难不难?有的人回答说:零基础学习hadoop,没有想象的那么难,也没有想象的那么容易。看到这样的答案不免觉得有些尴尬,这个问题算是白问了,因为这个...

左手的倒影 ⋅ 今天 ⋅ 0

806. Number of Lines To Write String - LeetCode

Question 806. Number of Lines To Write String Solution 思路:注意一点,如果a长度为4,当前行已经用了98个单元,要另起一行。 Java实现: public int[] numberOfLines(int[] widths, Str...

yysue ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部