Android源码分析之Message
准备开始写点东西,算是对自己阅读源码的一个记录/笔记,也希望能对同样感兴趣的人有所帮助,希望能坚持下去,加油。
在Android的开发中,我们经常用到Handler.postXXX方法,或者View.postXXX方法,用来在下一次looper到来时执行。
我是那样的人,什么事情最好能够知道下内部实现机理是什么,否则我在用它的时候可能会觉得不爽,或者说不自然,不太愿意去用。
典型例子就是我始终不太愿意用Android引入的SparseArray<E>,而是一直坚持Java的HashMap<Key, Value>,直到我自己读了
SparseArray<E>的源码,才开始放心大胆的使用(后面会写一篇文章专门分析它)。
首先来看下面的代码:
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
// do something...
removeCallbacks(this);
}
};
postDelayed(mRunnable, SOME_DELAY_IN_MILLIMS);
第一直觉告诉我run方法里的removeCallbacks(this);调用显然是多余的。通读代码发现的确是如此,因为任何Message
(即使post的是Runnable也会被包装到Message里)在被处理之前都已经从MessageQueue里取出来了(delete掉了,所以客户端
代码大可不必有这样的代码)。这里顺便提下慎用View.removeCallbacks的返回值,看源码:
/**
* <p>Removes the specified Runnable from the message queue.</p>
*
* @param action The Runnable to remove from the message handling queue
*
* @return true if this view could ask the Handler to remove the Runnable,
* false otherwise. When the returned value is true, the Runnable
* may or may not have been actually removed from the message queue
* (for instance, if the Runnable was not in the queue already.)
*
* @see #post
* @see #postDelayed
* @see #postOnAnimation
* @see #postOnAnimationDelayed
*/
public boolean removeCallbacks(Runnable action) {
if (action != null) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mHandler.removeCallbacks(action);
attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Assume that post will succeed later
ViewRootImpl.getRunQueue().removeCallbacks(action);
}
}
return true;
}
我们可以看到这个方法always返回true,所以不要基于它的返回值做任何事情,还有它的返回值的意义也需要格外留意下。
我在第一次看到这个方法时就自以为然的觉得返回值肯定代表了Runnable action有没有成功地从MessageQueue中移除,true代表成功
移除了,false代表移除失败,呵呵,你想错了。仔细看看方法的doc,人家说的是return true表示这个view可以让它的Handler
去处理这件事情,并没提及处理的结果,而且即使返回true的时候也不能说明Runnable就已经从MessageQueue中移除了,
比如说此时Runnable已经不在MessageQueue中了;其他情况都是返回false。这里顺便看眼View.postDelayed方法:
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
return true;
}
这里不论是postDelayed还是removeCallbacks方法都首先检查了自己的mAttachInfo,如果非空才delegate给attachInfo的Handler
处理,所以你尽量不要过早(mAttachInfo还没初始化完毕)的调用这些方法。比如在早期版本的Android中如果你过早的调用post,
runnable不会被执行,参考这个问题:
http://stackoverflow.com/questions/4083787/runnable-is-posted-successfully-but-not-run;
感兴趣的同学可以在google中搜索view post runnable not run或者自行验证。我现在分析的是Android4.4的源码,依现在的代码来看
即使mAttachInfo是null,也会执行ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); 所以可能在较新的平台上不是
问题(有待考证)。经测试验证确实没问题,即使在Activity.onCreate中调用View.postXXX方法,runnable还是会被执行。
好了说了一大堆了,开始正题。作为开始我今天挑了一个最简单的开始分析,那就是Message.java文件。
其实说白了,Message就是一个数据类,持有data的。基本的数据字段我就不介绍了,都能望文生义。看下几个我觉得有必要的字段:
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ 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;
target是消息的处理者,在以后Looper.loop()方法中Message被从MessageQueue取出来后会调用msg.target.dispatchMessage(msg);
callback是消息要执行的动作action。这里提前插播下Handler的dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们可以看到Handler在分发消息的时候,Message自身的callback优先级高,先被调用如果非空的话(callback的run方法直接被调用)。
next表示消息队列中的下一个Message,类似单链表的概念。
剩下的pool相关的字段都是Message引入的重用(reuse)所要用到的变量,sPoolSync是对象锁,因为Message.obtain方法会在任意
线程调用;sPool代表接下来要被重用的Message对象;sPoolSize表示有多少个可以被重用的对象;MAX_POOL_SIZE显然是pool的上限,
这里hardcode是50。这里我要分析的就2个方法,
obtain和recycle,代码如下:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
} /**
* Return a Message instance to the global pool. You MUST NOT touch
* the Message after calling this function -- it has effectively been
* freed.
*/
public void recycle() {
clearForRecycle(); synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
首先我们来看obtain方法,第一次调用也就是说没有什么东西可以重用,这时sPool是null,直接new一个Message对象返回,等到
Message对象使用完毕(在Looper.loop方法最后有msg.recycle();这样的代码),它的recycle会被调用,在recycle里首先会调用
clearForRecycle方法,它只是把各个字段置空或清零。接下来sPoolSize没到上限,next保存下sPool的旧值(也就是在当前Message
回收利用之前上一个要被回收利用的对象),然后sPool被更新成新值,即当前Message,sPoolSize加1,表示又多了一个可以重用的
Message对象。之后在等到obtain被调用的时候就不是直接return一个new Message了,因为我们已经有可以重用的Message对象了。
将sPool的值设置给我们要返回的Message m对象,接着sPool被更新成上一个要被重用的Message对象(相比recycle是反向过程),
最后设置m的next字段为空(m.next会在重新入队列的时候被设置成合适的值),相应的sPoolSize减1,表示可重用的对象少了一个,
最后返回重用的对象m。
基于有这么个回收再利用的机制,Android建议我们调用Message的obtain方法来获取一个Message对象,而不是调用ctor,因为很
可能会省掉分配一个新对象的开销。
到目前为止,我们好像忽略了Message的一个(重要)方面,即异步性,代码如下:
/**
* Sets whether the message is asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with represent to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param async True if the message is asynchronous.
*
* @see #isAsynchronous()
* @see MessageQueue#enqueueSyncBarrier(long)
* @see MessageQueue#removeSyncBarrier(int)
*
* @hide
*/
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
看方法的doc,我们知道异步的Message表示那些不需要全局顺序的中断或事件(相比同步Message来说),这里的全局顺序是指
MessageQueue中的先后顺序,而且异步消息的处理不受MessageQueue中引入的enqueueSyncBarrier(long)方法的影响;
具体见MessageQueue的next()方法中有段这样的代码:
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
也就是说当我们在队列中遇到一个sync barrier的时候,紧接着的同步Message的处理就会被本次循环忽略,而是直奔下一个
异步的消息。sync barrier是通过MessageQueue中的enqueueSyncBarrier(long when)、removeSyncBarrier(int token)
调用来实现添加、删除的;它们是在android.view.ViewRootImpl执行Traversals相关的代码时被调到的,代码如下:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); // 添加一个sync barrier
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
scheduleConsumeBatchedInput();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); // 移除对应的sync barrier
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); // 移除对应的sync barrier
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
Message类的分析就到这了,以后会陆续分析下常见于Android开发中的类。。。(由于本人水平有限,欢迎批评指正)
Android源码分析之Message的更多相关文章
- Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关
一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...
- Android源码分析(十)-----关机菜单中如何添加飞行模式选项
一:关机菜单添加飞行模式选项 源码路径:frameworks/base/core/res/res/values/config.xml 增加<item>airplane</item&g ...
- Android源码分析-全面理解Context
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- Android源码分析(六)-----蓝牙Bluetooth源码目录分析
一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...
- Android源码分析(十七)----init.rc文件添加脚本代码
一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...
- Android源码分析(十六)----adb shell 命令进行OTA升级
一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...
- Android源码分析(十五)----GPS冷启动实现原理分析
一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...
- Android源码分析(十四)----如何使用SharedPreferencce保存数据
一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...
- Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果
一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...
随机推荐
- 七牛portal可用性测试记
引言:2013年年底应七牛公司朋友的邀请,给他们的Portal进行可用性测试.七牛(http://www.qiniu.com/)一直专注于云存储基础服务,在业内颇有声誉.七牛云存储的后台选用并不常用的 ...
- Web程序员开发App系列 - 认识HBuilder
Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...
- [Node.js] OAuth 2 和 passport框架
原文地址:http://www.moye.me/?p=592 OAuth是什么 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列 ...
- Java基础——事务
一.事务 简单点说,事务就是一件事情.所有与事务相关的内容都是围绕这一件事情展开的. 二.事务的特性:ACID A:Atomicity(原子性),事务必须是一个不可分割的整体. C:Consisten ...
- UnityShader快速上手指南(四)
简介 由于其他项目中断了几天更新,继续~~ 这一篇主要是讲光照的(包含漫反射和高光以及多光源的处理) 还是先来看看具体效果(多光源后面单独展示) 有了基本的光照处理之后越来越有立体感了有不有 ╮(╯▽ ...
- Winform开发框架之权限管理系统功能介绍
权限管理系统的重要特性总结: 1) 高度集成的权限系统.独立模块,能快速整合使用.2) 符合权限的国际通用标准,基于RBAC(基于角色的访问控制)的角色权限控制.3) 多数据库架构支持,内置支持Sql ...
- C#创建服务及使用程序自动安装服务,.NET创建一个即是可执行程序又是Windows服务的exe
不得不说,.NET中安装服务很麻烦,即要创建Service,又要创建ServiceInstall,最后还要弄一堆命令来安装和卸载. 今天给大家提供一种方式,直接使用我们的程序来安装/卸载服务,并且可以 ...
- Java集合Iterator迭代器的实现
一.迭代器概述 1.什么是迭代器? 在Java中,有很多的数据容器,对于这些的操作有很多的共性.Java采用了迭代器来为各种容器提供了公共的操作接口.这样使得对容器的遍历操作与其具体的底层实现相隔离, ...
- Netty学习之服务器端创建
一.服务器端开发时序图 图片来源:Netty权威指南(第2版) 二.Netty服务器端开发步骤 使用Netty进行服务器端开发主要有以下几个步骤: 1.创建ServerBootstrap实例 Serv ...
- 【循序渐进学Python】10.模块和包
1.导入模块 任何Python程序都可以作为模块导入,只要Python解释器能找到我们定义的模块所在位置即可,一般来讲,在一个模块被导入时,Python解释器会按照下面的步骤进行搜索: 在当前所在目录 ...