1. 我们为什么需要Android的消息机制

我们知道,Android规定访问UI只能在主线程中进行。若在子线程中访问UI,就会抛出异常。这个验证由ViewRootImpl的checkThread方法来完成。

为什么不允许在非主线程访问UI呢,这是因为Android的UI控件不是线程安全的。并发访问会导致控件处于不可预期的状态。

那为什么不对UI访问加上锁机制呢,原因如下:

(1)这显然会让UI访问的逻辑变得极其复杂;

(2)除了效率问题,锁机制还会阻塞某些进程的执行。

但是Android又不建议在主线程进行耗时操作,因为这可能会引起ANR。因此,便出现了Android的消息机制。

本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52120086

2. Android的消息机制结构

Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层MessageQueue和Looper的支撑。

2.1  MessageQueue

MessageQueue采用单链表的数据结构存储消息列表。对外提供插入(enqueueMessage)和读取(next)工作。读取本身附带删除操作。单链表在插入和删除上比较有优势。

enqueueMessage方法根据消息的延迟时间来进行的单链表的插入操作,next方法是一个无限循环,如果消息队列中没有消息,就会阻塞,当有新消息到来时,next方法就返回这条消息并将其从单链表中删除。

2.2  Looper

Looper以无限循环的形式去消息队列查找是否有新消息,如果有,就处理消息,否则就一直阻塞等待。

需要注意的是,Handler在创建时会采用当前线程的Looper来构造消息循环系统,Handler内部是通过ThreadLocal来实现的。

有一种情况是HandlerThread,HandlerThread 继承自Thread,内部已经封装了Looper。并对外提供自己这个Looper对象的get方法,这就是它和普通Thread唯一不同的地方。具体关于HandlerThread的使用以及特性介绍请查看Android开发——HandlerThread以及IntentService详解

ThreadLocal适用于某些数据以线程为作用域并且不同线程具有不同数据副本的场景。

ThreadLocal可以在不同线程中互不干扰地存储并提供数据,通过它可以获取每个线程的Looper。

ThreadLocal的神奇功能介绍请看之前写过的一篇博文,Android开发——ThreadLocal功能介绍

(1)UI线程,即ActivityThread被创建时会初始化Looper,因此在主线程默认可以使用Handler。

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

new Thread(){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();

Looper的loop()方法极其重要。因为它消息循环系统才真正起作用。

loop方法就是一个死循环,跳出死循环的唯一条件是MessageQueue的next返回了null。当Looper的quit方法被调用时,MessageQueue的next就会返回了null。

loop方法会调用MessageQueue的next方法,next是一个阻塞操作,前面也讲过了。只有当next返回了新消息,Looper才会处理这条消息。这样Handler发送的消息最终又交给它的dispatchMessage方法来处理。但是Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的。这样就将逻辑切换到指定线程中去执行了。

Looper也是可以退出的,Looper提供quit和quitSafely来退出一个Looper。唯一的区别是,后者会处理完消息队列中已有的消息后才安全退出,前者直接退出。Looper退出后,通过Handler发送消息会失败,send方法返回false。在子线程中,手动创建的Looper在不需要时应quit退出,否则会此线程会一直处于等待状态。

2.3  Handler

Handler主要工作是消息的发送和接收。

消息的发送可以使用Handler的post的方法(最终还是通过send方法完成)将一个Runnable投递到Looper中去处理,send方法发送一个消息也是同理。

这里我们有必要对这两种方式进行说明。

handler.post(new Runnable(){
@Override
public void run() {
//do something
}});

或者这种用法的变形,用途很广,功能是延迟3秒后从欢迎界面进入主界面。这里并不是开启了一个新的线程。

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}, 3000);

post方法的使用如上所示,我们还知道Hanlder中也有一个handler.sendMessage(Messagemsg)方法,这两个方法有什么区别呢?

看一下handler.post(Runnable callback)方法的源码,很明显最终还是通过send方法完成的。

public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}

再看一下sendMessageDelayed的源码:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

这里面有个关键就是方法getPostMessage(r)这个方法,他将Runnable转成一个Message,他内部到底干了什么呢?看一下他的源码:

private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

这里面就是将Runnable转化成一个Message,其他看他的代码很简单,就是先获取一个空消息Message.obtain(),然后将Message中私有变量callback的值设置成Runnable。

send方法被调用时,会调用MQ的enqueueMessage方法将这个消息放入消息队列中,MQ的next方法会返回这条消息给Looper,Looper发现新消息到来会处理之,最终消息交给Handler处理。dispatchMessage方法被调用,过程如下。

public void dispatchMessage(Message msg){
if(msg.callback!=null){
handleCallback(msg);
}else{
if(mCallback!=null){
if(mCallback.handleMessge(msg)){
return;
}
}
handleMessage(msg);
}
}

首先检查Message的callback不为null,不为null就意味着,callback是一个Runnable对象(实际上就是Handler的post方法所传递的Runnable参数),这就是从post方法来实现的。就通过handleCallback来处理消息。handleCallback逻辑很简单,直接就是msg.callback.run(),执行我们在Runnable方法里重写的run方法。

其次检查mCallback不为空,调用mCallback的handleMessage方法来处理消息。

这是为了处理以下这种Handler的使用情况。CallBack可以用来创建一个Handler的实例但并不需要派生Handler的子类。

Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});

若我们直接返回了true,就不用再处理消息了。返回false,或者mCallback为空,会在最后执行Handler的handleMessage(msg)方法来处理消息。

这样我们就分析完了Android消息机制的整个流程。

Android开发——Android的消息机制详解的更多相关文章

  1. Android开发:文本控件详解——TextView(一)基本属性

    一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...

  2. Android开发:文本控件详解——TextView(二)文字跑马灯效果实现

    一.需要使用的属性: 1.android:ellipsize 作用:若文字过长,控制该控件如何显示. 对于同样的文字“Android开发:文本控件详解——TextView(二)文字跑马灯效果实现”,不 ...

  3. Android开发数据存储之ContentProvider详解

    转载:十二.ContentProvider和Uri详解 一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可 ...

  4. android开发系列之消息机制

    最近接触到一个比较有挑战性的项目,我发现里面使用大量的消息机制,现在这篇博客我想具体分析一下:android里面的消息到底是什么东西,消息机制到底有什么好处呢? 其实说到android消息机制,我们可 ...

  5. 转: Android开发中的MVP架构详解(附加链接比较不错)

    转: http://www.codeceo.com/article/android-mvp-artch.html 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解M ...

  6. Android开发5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  7. Windows消息机制详解

    消息是指什么?      消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用 ...

  8. Android开发——Android 6.0权限管理机制详解

    .Android 6.0运行时主动请求权限 3.1  检测和申请权限 下面的例子介绍上面列出的读写SD卡的使用例子,可以使用以下的方式解决: public boolean isGrantExterna ...

  9. Android事件分发传递回传机制详解

    转载本专栏每一篇博客请注明转载出处地址,尊重原创.此博客转载链接地址:点击打开链接   http://blog.csdn.net/qq_32059827/article/details/5257701 ...

随机推荐

  1. Azure service bus Topic基本用法

    我们在升级一个POS系统的时候,决定使用微软公有云计算平台下的Azure ServiceBus 进行POS客户端与服务器的交互. 本文主要时作者在学习使用 Azure SDK for .NET 操作由 ...

  2. 转 怎样解读10046 trace (tkprof 的结果 )

    set autot on SQL> set autotraceUsage: SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ ...

  3. centOS+uwsgi+nginx 部署flask项目,问题记录

    用flask做的项目想要部署到centOS系统上,填了一些坑,终于成功了,记录一下遇到的问题: 此次部署主要是按照这个博客进行的 https://www.cnblogs.com/Ray-liang/p ...

  4. 单线程单元(STA)线程都应使用泵式等待基元

    CLR 无法从 COM 上下文 0x20ad98 转换为 COM 上下文 0x20af08,这种状态已持续 60 秒.拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows ...

  5. ubuntu下安装无线网卡去驱动Qualcomm-Atheros-QCA9377

    文件(文档和压缩包):http://pan.baidu.com/s/1mhktFT2

  6. Java编程基础-变量

    1.变量的定义. 变量与常量相对应,变量是在程序运行过程中它的值允许改变的量,变量可以通过变量名访问. 2.Java中的三大变量 (1).类变量.又称为静态变量,在类中定义类的属性时,使用static ...

  7. 简单明了理解Java移位运算符

    无须多言: @Test public void intro() { assertThat("应该相等", -1 >> 1, equalTo(-1)); assertTh ...

  8. windows/Linux 常用命令

    windows 文件操作命令 cd 切换文件目录 dir 显示文件目录内容 md 创建文件夹 rd 删除文件夹 copy 拷贝文件 move 移动文件 del 删除文件 replace 替换文件 mk ...

  9. Java生成固定长度的随机字符串(以大小写字母和数字)

    package org.jimmy.autosearch2019.test; import java.util.ArrayList; import java.util.Random; /** * @a ...

  10. BestCoder Round#15 1002-Instruction

    http://acm.hdu.edu.cn/showproblem.php?pid=5083 官方题解——> 1002 Instruction 先考虑编码,首先找到operation对应的编码, ...