参考文章:

http://gityuan.com/2015/12/26/handler-message-framework/#next

参考资料:

Android Framework的源码:

Message.java

MessageQueue.java

Looper.java

Handler.java

ThreadLocal.java

(以上几个类代码都不复杂,自己去打开看一看还是有必要的)

1.前言

在Android开发中,Handler是非常常用的,如果多线程需要切换到主线程更新UI,通常都会使用到Handler。Handler后面的是Android的消息机制。

2.思考

2.1为什么Android需要一套通用的多线程通信的机制?

Android的UI是单线程机制(从效率,实现的复杂程度来说,是最优解),这意味着

我们只能在UI线程(主线程)更新UI,如果在其他线程更新UI,app会报异常(奔溃)

然而多线程是Android开发中非常常见的情景,不可能把所有代码都运行在主线程,如网络请求,文件读写,复杂的运算,这些操作如果运行在主线程,会造成UI线程卡顿,甚至会带来ANR(应用无响应)的问题。

当我们在子线程执行完任务后,需要把结果显示在UI上,就必然涉及到多线程的通信。

多线程通信的实现方式很多,但是,Android 系统提供了一套已经实现的通用的机制,这是最好的选择,同时这对开发者也是方便友好的。

2.2为什么Android采用的消息驱动多线程通信的机制?

由于多线程(我们只考虑同一个进程下的多线程),共享进程的内存空间,所以它们之间的相互操作非常简单

我们可以使用回调监听,或者更粗暴一点,一个A线程直接持有B线程的引用,然后就可直接通知操作B线程

唯一的问题是:通用性

不可能穷尽所有线程之间通信的可能,所以,理想的解决方法是抽象:

把线程之间的通信抽象为A线程发出消息通知,B处理消息

消息Message代表两个线程之间的通信传递的内容

以上,线程之间的通信问题就抽象为由一个线程发出消息Message,另一个线程接收并处理这个Message问题

2.3为什么Android Loop循环不会卡死CPU?

对于一个App来说:

它需要一直运行,直至被退出,结束进程

最简单的方式就是一个死循环(永真循环),注意为了不让这个死循环一直占用CPU,所以必然有一个等待并让出cpu的处理。

对应Android来说,每一个程序的入口是:

ActivityThread中的main方法(我们在学习Java里面非常熟悉的main方法,这是整个App的入口):

  public static void main(String[] args) {

        Looper.prepareMainLooper();
//注意这个方法,这个方法为当前的线程创建了一个属于当前线程的Looper,并赋值给 Looper的静态变量 private static Looper sMainLooper;
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();//这里是一个死循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}

在Looper中的loop()方法:

 public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//ongoing这里可以看到一个Looper里面包含着一个对应的MessageQueue // Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//这就是死循环
for (;;) {
Message msg = queue.next(); // might block,这个方法可能会被阻塞,等待,让出CPU
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} // Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}

3.Java层研读:

3.1理清下面几个类的作用以及它们之间的联系:

首先是它们的整体关系:使用Gityuan大神的图片:



我们使用一个最常见的的场景解析它们的关系:

UI线程A:刷新,显示UI

工作线程B:负责请求网络,获取一个结果,假设结果是一个字符串 "Hello ,thread A,I 'm thread B."

现在我们需要把结果从工作线程B传递到UI线程A,然后由UI线程把这个结果显示到一个Textview的控件上

分为三个步骤:

在主(UI)线程 初始化一个Handler mHandler对象

在工作线程中

通过

Message message=mHandler.obtain();

获取一个Message

这个时候可以把"Hello ,thread A,I 'm thread B." 保存在Message里面

调用mHandler.sendMessage()方法,就会把Message保存到主线程的Looper的 MessageQueue中

主线程的Looper会不断循环从MessageQueue中取出Message,然后通过dispatchMessage方法分发给它对应的Handler处理

3.2Message:

Message代表一个从一个线程传到另外一个线程的消息

Android的消息驱动多线程通信的机制是基于Message的

所以Message需要重点关心的是Message所能包含的信息,也就是这个类的

成员变量

源码:

public final class Message implements Parcelable {
/**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
*/
public int what;//用于区分Message类型的id,作用域是单个Handler,也就是不同的Handler这个值即使重复也没关系 /**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1;//arg1,一个位置,供使用者保存一个整形的值 /**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg2;/arg2,一个位置,供使用者保存一个整形的值 /**
* An arbitrary object to send to the recipient. When using
* {@link Messenger} to send the message across processes this can only
* be non-null if it contains a Parcelable of a framework class (not one
* implemented by the application). For other data transfer use
* {@link #setData}.
*
* <p>Note that Parcelable objects here are not supported prior to
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
*/
public Object obj; /**
* Optional Messenger where replies to this message can be sent. The
* semantics of exactly how this is used are up to the sender and
* receiver.
*/
public Messenger replyTo; /**
* Optional field indicating the uid that sent the message. This is
* only valid for messages posted by a {@link Messenger}; otherwise,
* it will be -1.
*/
public int sendingUid = -1; /** If set message is in use.
* This flag is set when the message is enqueued and remains set while it
* is delivered and afterwards when it is recycled. The flag is only cleared
* when a new message is created or obtained since that is the only time that
* applications are allowed to modify the contents of the message.
*
* It is an error to attempt to enqueue or recycle a message that is already in use.
*/
/*package*/ static final int FLAG_IN_USE = 1 << 0; /** If set message is asynchronous */
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /*package*/ int flags;//状态标记位 /*package*/ long when;//记录当前Message需要被处理的时间 /*package*/ Bundle data;//保存一个类型为Bundle的数据 /*package*/ Handler target;//记录当前的Message的处理者(目的地) /*package*/ Runnable callback;//记录当前的Message的Runnable回调 /*下面的代码使得我们可以用多个Message构建一个单链表
// 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;//为了避免频繁创建销毁Message,设置一个最大size为50的Message对象池 private static boolean gCheckRecycle = true;
}

3.2MessageQueue:

MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,

这个需要结合C++ Native层的NativeMessageQueue来看。

具体请参考Gityuan大神的分析文章:

http://gityuan.com/2015/12/27/handler-message-native/

3.4Looper:

Looper负责从MessageQueue不断取出需要处理的Message,然后把Message分发给它对应的Handler处理

重要的方法有以下三个:

Looper.prepare()

/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
} 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));//这个方法就为当前的线程set一个属于它的Looper,ThreadLocal见另外一篇文章的分析
}

Looper.prepareMainLooper()

这个方法本质也是使用了Looper.prepare(),这个方法只在UI线程被调用,然后

把Ui线程对应的Looper也赋值给了sMainLooper

  /**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

Looper.loop()

这个方法是一个死循环,它负责从MessageQueue中取出需要处理的Message,并分发处理

 public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity(); for (;;) {
Message msg = queue.next(); // might block 可能阻塞,然后会让出CPU给其他线程(进程)
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
} // This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);//把当前的Message分发给对应的Handler处理
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
} // Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
} msg.recycleUnchecked();
}
}

3.5Handler:

Handler是我们最经常使用的:

它有两个职责:

  1. 发送Message
  2. 处理Message

    通常我们会继承并重写它的 handleMessage(Message msg)方法,因为这个方法在父类只是一个空实现:
  /**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}

在一个需要接收处理Message的线程初始化一个Handler

在需要发送Message的线程利用这个Handler发送消息(本质把Message添加到接收线程的Looper的MessageQueue中)

4.代码赏析:

4.1在Message类中,必须考虑一个这样的问题,每一次都new 一个Message实例,有时是非常低效并且消耗资源的。Java虽然有Gc机制,但是这个并不能保证及时回收。

解决问题的方法之一就是对象池:

我们短时间内需要获取很多对象,这些对象又很快使用完毕,可以考虑重用对象。

考虑到对象池的数目大小是不断变化的,插入删除操作比较多,考虑使用单链表的数据结构

private static Message sPool;//静态全局变量,记录链表头
private static int sPoolSize = 0; //静态全局变量,记录链表长度
private static final int MAX_POOL_SIZE = 50;//常量,对象池最大值 /**
* 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;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
} /**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null; synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}

上面的代码最精彩的地方就是并没有单独实现一个对象池的类来保存Message的对象,通过全局静态变量

Message sPool 和一个int sPoolSize 记录一个单链表就完成了一个简单的对象池(抽象意义上的)

  1. 第一次通过 obtain()方法获取Message对象,注意,此时sPool==null,所以直接new 一个 Message实例
  2. 如果sPool==null,调用多少次obtain()方法都会new 多少个Message实例(因为没有对象池是空的)
  3. 注意:一个Message实例被使用后,将会调用recycleUnchecked()方法,这个时候会把Message的值重设

    然后,做判断,如果对象池的数目没有超过最大值,就把这个对象插入到单链表的头部(回收),并且把sPollSzie++

    4.如果sPool !=null(sPool已经初始化) 此时调用obtain方法,
  Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;

会从单链表头部取一个Message 实例,并且把sPollSize--

5.能不能模仿实现一个多线程消息机制

由于这个涉及到Linux的epoll机制,需要理解这个并使用C,C++调用,目前还个人还完成不了。

留给大家一起想一下吧。

具体解析参考:

http://gityuan.com/2015/12/27/handler-message-native/

http://gityuan.com/2015/12/06/linux_epoll/

Android的消息机制简单总结的更多相关文章

  1. Android的消息机制

    一.简介 ①.我们不能在子线程中去访问UI空控件,这是时候只能通过Handler将更新UI的操作放到主线程中去执行 ②.Handler的组成:messageQueue和Looper的支持 ③.Mess ...

  2. 【原创】源码角度分析Android的消息机制系列(一)——Android消息机制概述

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.为什么需要Android的消息机制 因为Android系统不允许在子线程中去访问UI,即Android系统不允许在子线程中更新UI. 为什 ...

  3. Android 基础 十一 Android的消息机制

    Handler是Android消息机制的上层接口,这使得在开发应用过程中我们只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去 ...

  4. 聊一聊Android的消息机制

    聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比 ...

  5. Android开发——Android的消息机制详解

    )子线程默认是没有Looper的,Handler创建前,必须手动创建,否则会报错.通过Looper.prepare()即可为当前线程创建一个Looper,并通过Looper.loop()来开启消息循环 ...

  6. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

  7. 【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻 ...

  8. 【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其 ...

  9. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

随机推荐

  1. UITableViewCell笔记

    默认的四种cell的类型 原网站 还有这个带图的 据我自己试验,只有value2不自带imageview 不同的accessoryType 可以看到,一个tabelviewcell的contentvi ...

  2. Flink学习笔记:Flink Runtime

    本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKhaz ...

  3. typescript项目中import 图片时报错:TS2307: Cannot find module ‘...’

    最近在用typescript写项目时,我用import来加载一个图片,webpack编译文件是会报错如下: 报错: 解决: 如果在js中引入本地静态资源图片时使用import img from './ ...

  4. php post get 繁体、日文、韩文时 自动添加 反斜杠 问题

    做些二次开发项目,数据库.文件编码没法大规模的修改,比如二次开发一个日文系统,编码是JA16SJIS,$_POST或$_GET的信息中如果“申請”,得到的信息就会变成“申\請”,多出一个反斜杠! 先贴 ...

  5. 解析XMl文档和字符串

    //解析xml字符串 txt="<bookstore><book>"; txt=txt+"<title>Everyday Italia ...

  6. sql拼接列字符串

    1.使用函数(sql2000以上) )) ) AS BEGIN ) select @v = isnull(@v + ',','')+code FROM dict WHERE type=@filter ...

  7. web服务的简单介绍及apache服务的安装

    一,web服务的作用:  是指驻留于因特网上某种类型计算机的程序,可以向浏览器等Web客户端提供文档.可以放置网站文件,让全世界浏览:   可以放置数据让全世界下载.目前最主流的三个Web服务器是Ap ...

  8. 小众软件:windows 系统下 exe 文件打包软件

    1. Enigma Virtual Box 单文件打包软件 官网:EnigmaProtection 2. 安装包打包软件 官网:Inno Setup 参考文献: [1] 单文件制作工具Enigma V ...

  9. my.工坊_ZZ

    1.查了下,可以将 考古升上去,但是 还是使用 2级的考古技能,这样比较赚,高级的反而不赚.但是看到有人说 考古升到 3/4 不能再用 洛阳铲 了. 于是有了两种情况,我暂定的做法:先将 考古升到2级 ...

  10. J15W-J45W全铜截止阀厂家,J15W-J45W全铜截止阀价格 - 专题栏目 - 无极资讯网

    无极资讯网 首页 最新资讯 最新图集 最新标签   搜索 J15W-J45W全铜截止阀 无极资讯网精心为您挑选了(J15W-J45W全铜截止阀)信息,其中包含了(J15W-J45W全铜截止阀)厂家,( ...