该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽可能按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其余的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能须要有必定Android开发基础和项目经验的同窗才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。java
上一篇咱们介绍了LeakCanary工具用来分析内存泄漏以及谈了下几种常见内存泄漏的表现和解决方法。本篇内容咱们来分析Android的消息机制。咱们为何要介绍Android的消息机制呢,由于Android系统本质上来讲就是一个消息驱动的系统。咱们在开发中何时会用到Handler呢,工做年限较长的开发工程师应该对这个Handler很熟悉了,由于在早期的开发中,不管是网络请求刷新UI仍是子线程耗时任务的通知的应用场景都能看到Handler的身影。如今Handler在咱们的平常开发中少了一些,由于咱们有了RxJava、Retrofit等对Handler进行了很精美的封装。可是理解Android的消息机制对于理解Android系统的运做包括那些开源框架的原理都有很大帮助。android
关于Android的消息机制网上也有好多文章,我本人也看了好多。可是不只没有让我更清晰明了,反而让我陷入更深的迷惑。本篇的目的在于以一种相对更容易理解的方式来解释。网络
咱们先来模拟一个场景,在Activity中执行了耗时操做,耗时操做完成以后显示一个Toast。这种应用场景仍是比较常见的。咱们来模拟代码。多线程
public class MessageActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_message); new Thread(new Runnable() { @Override public void run() { try { //模拟耗时操做 Thread.sleep(3* 60 * 1000); //耗时操做完成以后显示一个通知 Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
咱们来运行上面的代码。呦呵,崩溃了,咱们查看日志获得如下信息。框架
关于上面的崩溃咱们稍后分析。async
既然讨论Android 消息机制,若是咱们全部的操做都能在一个线程中完成貌似就不须要这个消息处理机制了,,但这又是不现实的,正是由于咱们不能在一个线程中把全部的工做(网络请求、耗时操做、更新UI)在一个线程中完成,咱们才有了多线程,多线程的互相协做才造就了咱们这个Android欣欣向荣的世界。由此咱们不得不说到咱们Android App中的主线程(UI)线程,关于这个线程的叫法有不少。读者只须要知道不能在这个线程以外的线程直接对UI进行操做就好了。Android 4.0 以上甚至不能在主线程中(UI线程)中进行网络操做。不然的话会报android.os.NetworkOnMainThreadException,这个错误你们应该都见过把。那咱们就从这个主线程(UI线程提及)。ide
public static void main(String[] args) { ...... //1 建立Looper 和 MessageQueue,原本该线程也是一个普通的线程,可是建立了Looper以及结合后文的Looper.loop()方法,使这个线程成为了Looper线程(读者能够简单的理解为拥有Looper的线程,而这个Looper就是Android消息处理机制的一部分)。 Looper.prepareMainLooper(); //2 创建与AMS的通讯 ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ...... //3 无限循环 Looper.loop(); //能够看出来主线程也是在无限的循环的,异常退出循环的时候会报错. throw new RuntimeException("Main thread loop unexpectedly exited"); }
public final class Looper { ...... public static void prepare() { prepare(true); } //prepare 函数 private static void prepare(boolean quitAllowed) { //判断sThreadLocal.get()是否为空,若是不为空说明已经为该线程设置了Looper,不能重复设置。 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //若是sThreadLocal.get()为空,说明尚未为该线程设置Looper,那么建立Looper并设置 sThreadLocal.set(new Looper(quitAllowed)); } //ActivityThread 调用Looper.prepareMainLooper();该函数调用prepare(false); public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } ...... }
在这里呢有个静态变量sThreadLocal,它的定义以下函数
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
那么咱们就得来说解ThreadLocal这个类:工具
线程本地存储区(Thread Local Storage,简称为TLS),每一个线程都有本身的私有的本地存储区域,不一样线程之间彼此不能访问对方的TLS区域。这里线程本身的本地存储区域存放是线程本身的Looper。简单的来讲就是经过ThreadLocal来进行Looper的统一存储和读取。那么接着来看被ThreadLocal存储的对象Looper的构造函数。oop
//Looper的构造函数 private Looper(boolean quitAllowed) { //这里建立了MessageQueue mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
这里建立了MessageQueue为后续的步骤作准备,MessageQueue能够简单理解为一个“队列”(其底层其实是一个单向链表),之因此是打上引号的“队列”,是由于其并非严格意义上的队列,而是一个单项链表,使用者能够根据节点的优先级等等插入该链表。链表上的节点就是Message。第①步 整个的结构图以下所示
关于这一部份内容必须得对Android Binder知识有相关了解才能更好的理解。咱们下一篇就会讲解Android Binder,到时候咱们在回来这里。
在上面的工做中咱们已经准备好Looper和MessageQueue,下面就有了两个问题,① Message从何而来,② Message如何处理。
咱们在讨论Message的来源以及如何处理以前,先来看一下Message类
public class Message{ //消息码 public int what; //handler Handler target; //下一级节点 Message next; //消息发送的时间 long when; }
上面的代码也从侧面证实了咱们的MessageQueue是一个由Message组成的单向链表
咱们先来看Message如何处理,至于为何,固然是保证由于咱们的思路不被打断,咱们先分析ActivityThread的最后Looper.loop()函数作了什么。
咱们来到了ActivityThread的最后一步Looper.loop()
ActivityThread.java
public static void loop() { //获得Looper final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //获得MessageQueue final MessageQueue queue = me.mQueue; ...... for (;;) {//无限循环 Message msg = queue.next(); // 取下一个Message 可能阻塞在这里 if (msg == null) { //若是队列为空直接return直接结束了该方法,即循环结束 return; } ...... try { //分发message到指定的target handler msg.target.dispatchMessage(msg); ...... } finally { } ...... } }
主线程由此进入无限循环等待消息,有人看到这里就由疑问了,执行到for循环时,不就“卡死”在这个无限循环内了吗?其余的操做没法获得CPU怎么执行呢?关键点就在于queue.next()方法。
为了更好的理解这个方法咱们先来说一下关于线程阻塞与唤醒的知识
什么是阻塞呢?好比某个时候你在等快递,可是你不知道快递何时过来,并且你没有别的事能够干(或者说接下来的事要等快递来了才能作);那么你能够去睡觉了,由于你知道快递把货送来时必定会给你打个电话(假定必定能叫醒你)。结合咱们上面的代码。咱们的代码运行Message msg = queue.next();这一句时,主线程可能一直阻塞在这里等待消息的到来(它去睡觉去了,也就是说咱们的主线程,竟然是大部分时间都在睡觉,心真大啊)。
注:线程阻塞跟线程忙循环轮询是有本质区别的,不要听到线程阻塞就觉得是CPU一直在无限循环轮询状态啊。线程阻塞是不占用CPU资源的,可是线程忙循环轮询就不同了,将几乎占满CPU资源。什么是CPU资源,简单的来讲CPU资源就是分配给程序的执行时间。
要想把主线程活动起来通常有两种方式:一种是系统唤醒主线程,而且将点击事件传递给主线程;第二种是其余线程使用Handler向MessageQueue中存放了一条消息,致使loop被唤醒继续执行。在下面的Message从何而来中咱们这里使用了hander向MessageQueue中存放了一条消息,致使loop被唤醒继续执行。
public class MessageActivity extends AppCompatActivity { private Handler mHandler= new Handler(){ //处理消息 @Override public void handleMessage(Message msg) { handleMsg(msg); } }; private void handleMsg(Message msg) { switch (msg.what){ case 0: Toast.makeText(this,"成功",Toast.LENGTH_SHORT).show(); break; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_message); new Thread(new Runnable() { @Override public void run() { try { //模拟耗时操做 Thread.sleep(3*1000); //发送消息 mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
咱们常用上面的代码来作耗时操做,那么这里这里咱们的猪脚就出场了,mHandler是Handler的对象。咱们来看一下Handler类
public class Handler { //handler类有个Looper final Looper mLooper; //handler类有个MessageQueue final MessageQueue mQueue; //handler类有个Callback final Callback mCallback; public Handler() {//咱们使用的是这一个 this(null, false); } public Handler(Callback callback) { this(callback, false); } public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback) { this(looper, callback, false); } public Handler(boolean async) { this(null, async); } public Handler(Callback callback, boolean async) { //这里获取主线程的Looper,Handler的mLooper指向ThreadLocal内的Looper对象 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //这里获取主线程的Looper的MessageQueue,Handler的mQueue指向ThreadLocal内Looper对象内的MessageQueue对象 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } }
建立Handler 以后就调用 mHandler.sendEmptyMessage(0);发送消息(Handler的发送消息的方式有好多种,但这不是咱们的重点),最终调用到Handler enqueueMessage 方法
Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 设置msg.target 为当前Handler对象 msg.target = this; ...... //调用MessageQueue的enqueueMessage()方法 return queue.enqueueMessage(msg, uptimeMillis); }
咱们再来看一下MessageQueue的enqueueMessage()
MessageQueue.java
boolean enqueueMessage(Message msg, long when) { ...... synchronized (this) { ...... msg.when = when; Message p = mMessages; //检测当前头指针是否为空(队列为空)或者没有设置when 或者设置的when比头指针的when要前 if (p == null || when == 0 || when < p.when) { //插入队列头部,而且唤醒线程处理msg msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 几种状况要唤醒线程处理消息:1)队列是堵塞的 2)barrier,头部结点无target 3)当前msg是堵塞的 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // 将当前msg插入第一个比其when值大的结点前。 prev.next = msg; } //调用Native方法进行底层操做,在这里把那个沉睡的主线程唤醒 if (needWake) { nativeWake(mPtr); } } return true; }
咱们的Handler在发送消息的时候把自身设置给了msg.target,发送消息并唤醒Looper,Looper被唤醒后便使用queue.next()取出Message,并根据msg.target进行派发。Handler总体过程以下图
咱们再稍微看下Handler的dispatchMessage方法
Handler.java
public void dispatchMessage(Message msg) { if (msg.callback != null) {//判断有没有为Message设置callback(这里的callback是个Runnable接口,咱们在为Message设置callback的时候须要本身实现run方法),若是设置了,那么调用Runnable实例的run方法 handleCallback(msg); } else { if (mCallback != null) {//判断Handler的mCallback是否为空(这里的Handler是个Callback接口,咱们在为Handler设置mCallback的时候须要本身实现handleMessage方法),若是设置了,那么调用Callback实例的handleMessage方法 if (mCallback.handleMessage(msg)) { return; } } //调用handleMessage方法 handleMessage(msg); } }
咱们在建立Handler使用的是没法构造函数,并重写了handleMessage方法,因此咱们的重写的handleMessage获得调用,弹出了Toast
本篇比较详细的介绍了Android的消息机制,不过有一部份内容须要其余的知识做为基础才能更好的理解。不过这不影响咱们分析Android的消息机制的整个流程。咱们在这里再梳理一下。
1. 主线程准备Looper和MessageQueue
2. 建立一个线程(由于下面咱们进入死循环了,因此在这以前建立一个线程用来处理,这是个Binder线程)
3. 主线程进入无限循环等待并处理消息。(这个消息多是系统自己的消息,也有多是咱们本身的消息。在本例中分析的是咱们本身建立的Handler发送的消息。)
咱们再上个整图
这里呢咱们呢是使用Activity的建立做为分析,由于这是Activity的起点。在注释第2步中的代码sendMessage(H.LAUNCH_ACTIVITY, r);与咱们例子中 mHandler.sendEmptyMessage(0);并无什么大的不一样。
如今也是揭晓咱们文章开头的那个崩溃的秘密的时候了,相信读者也有答案了。没错,是由于咱们在非UI线程中更新了UI,致使了异常。缘由是咱们在子线程没有Looper啊。你能够作出以下更改就不会有异常了。
public class MessageActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_message); new Thread(new Runnable() { @Override public void run() { try { //模拟耗时操做 Thread.sleep(3* 60 * 1000); //在子线程中更新UI以前,先准备一个Looper,与主线程相同 if (Looper.myLooper() != null){ Looper.prepare(); } //耗时操做完成以后显示一个通知 Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show(); //无限循环 Looper.loop(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
好了,咱们下一篇介绍Android的Binder,Binder是个大工程哈。。
此致,敬礼