好记性不如烂笔头,今天来分析一下Handler的源码实现

Handler机制是Android系统的基础,是多线程之间切换的基础。下面我们分析一下Handler的源码实现。

Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message

Handler : 获取消息,发送消息,以及处理消息的类

MessageQueue:消息队列,先进先出

Looper : 消息的循环和分发

Message : 消息实体类,分发消息和处理消息的就是这个类

主要工作原理就是:

Looper 类里面有一个无限循环,不停的从MessageQueue队列中取出消息,然后把消息分发给Handler进行处理

先看看在子线程中发消息,去在主线程中更新,我们就在主线程中打印一句话。

第一步:

在MainActivity中有一个属性uiHandler,如下:

    Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 100){
Log.d("TAG","我是线程1 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
}else if(msg.what == 200){
Log.d("TAG","我是线程2 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
}
}
};

创建一个Handler实例,重写了handleMessage方法。根据message中what的标识来区别不同线程发来的数据并打印

第二步:

在按钮的点击事件中开2个线程,分别在每个线程中使用 uiHandler获取消息,并发送消息。如下


findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//线程1
new Thread(new Runnable() {
@Override
public void run() {
//1 获取消息
Message message = uiHandler.obtainMessage();
message.what = 100;
message.obj = "hello,world"; //2 分发消息
uiHandler.sendMessage(message);
}
}).start(); //线程2
new Thread(new Runnable() {
@Override
public void run() {
//1 获取消息
Message message = uiHandler.obtainMessage();
message.what = 200;
message.obj = "hello,android"; //2 分发消息
uiHandler.sendMessage(message);
}
}).start();
}
});

使用很简单,两步就完成了从子线程把数据发送到主线程并在主线程中处理

我们来先分析Handler的源码

Handler 的源码分析

Handler的构造函数

   public Handler() {
this(null, false);
}

调用了第两个参数的构造函数,如下

 public Handler(Callback callback, boolean async) {
//FIND_POTENTIAL_LEAKS 为 false, 不走这块
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
} mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

主要是下面几句:

mLooper = Looper.myLooper(); 调用Looper的静态方法获取一个Looper

如果 mLooper == null ,就会抛出异常

Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()";

说明我们的线程中如果没有一个looper的话,直接 new Handler() 是会抛出这个异常的。必须首先调用 Looper.prepare(),这个等下讲Looper的源码时就会清楚了。

接下来,把 mLooper中的 mQueue赋值给Handler中的 mQueue,callback是传出来的值,为null

这样我们的Handler里面就保存了一个Looper变量,一个MessageQueue消息队列.

接下来就是 Message message = uiHandler.obtainMessage();

obtainMessage()的源码如下:

  public final Message obtainMessage()
{
//注意传的是一个 this, 其实就是 Handler本身
return Message.obtain(this);
}

又调用了Message.obtain(this);方法,源码如下:

public static Message obtain(Handler h) {
//1 调用obtain()获取一个Message实例m
Message m = obtain(); //2 关键的这句,把 h 赋值给了消息的 target,这个target肯定也是Handler了
m.target = h; //3 返回 m
return m;
}

这样,获取的消息里面就保存了 Handler 的实例。

我们随便看一下 obtain() 方法是如何获取消息的。如下

  public static Message obtain() {
//sPoolSync同步对象用的
synchronized (sPoolSync) {
//sPool是Message类型,静态变量
if (sPool != null) {
//就是个单链表,把表头返回,sPool再指向下一个
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
} //如果sPool为空,则直接 new 一个
return new Message();
}

obtain()获取消息就是个享元设计模式,享元设计模式用大白话说就是:

池中有,就从池中返回一个,如果没有,则新创建一个,放入池中,并返回。

使用这种模式可以节省过多的创建对象。复用空闲的对象,节省内存。

最后一句发送消息uiHandler.sendMessage(message);源码如下:

 public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

sendMessageDelayed(msg, 0) 源码如下

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

又调用了sendMessageAtTime() 源码如下:

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// Handler中的mQueue,就是前面从Looper.get
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

调用enqueueMessage()

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//注意这句,如果我们发送的消息不是 uiHandler.obtainMessage()获取的,而是直接 new Message()的,这个时候target为null
//在这里,又把this 给重新赋值给了target了,保证不管怎么获取的Message,里面的target一定是发送消息的Handler实例
msg.target = this; // mAsynchronous默认为false,不会走这个
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

最后调用queue.enqueueMessage(msg, uptimeMillis)源码如下:

    boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
} synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
} msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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; // invariant: p == prev.next
prev.next = msg;
} // We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

enqueue单词的英文意思就是 排队,入队的意思。所以enqueueMessage()就是把消息进入插入单链表中,上面的源码可以看出,主要是按照时间的顺序把msg插入到由单链表中的第一个位置中,接下来我们就需要从消息队列中取出msg并分了处理了。这时候就调用Looper.loop()方法了。

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; for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
} msg.target.dispatchMessage(msg); msg.recycleUnchecked();
}
}

可以看到,loop()方法就是在无限循环中不停的从queue中拿出下一个消息

然后调用 msg.target.dispatchMessage(msg) , 上文我们分析过,Message的target保存的就是发送的Handler实例,这我们的这个demo中,就是uiHandler对象。

说白了就是不停的从消息队列中拿出一个消息,然后发分给Handler的dispatchMessage()方法处理。

Handler的dispatchMessage()方法源码如下:

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

可以看到,一个消息分发给dispatchMessage()之后

1 首先看看消息的callback是否为null,如果不为null,就交给消息的handleCallback()方法处理,如果为null

2 再看看Handler自己的mCallback是否为null,如果不为null,就交给mCallback.handleMessage(msg)进行处理,并且如果返回true,消息就不往下分发了,如果返回false

3 就交给Handler的handleMessage()方法进行处理。

有三层拦截,注意,有好多插件化在拦截替换activity的时候,就是通过反射,把自己实例的Handler实例赋值通过hook赋值给了ActivityThread相关的变量中,并且mCallback不为空,返回了false,这样不影响系统正常的流程,也能达到拦截的目的。说多了。

前面分析了handler处理消息的机制,也提到了Looper类的作用,下面我们看看Looper的源码分析

Looper源码分析

我们知道,APP进程的也就是我们应用的入口是ActivityThread.main()函数。

对这块不熟悉的需要自己私下补课了。

ActivityThread.main()的源码同样经过简化,如下:

文件位于 /frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
//1 创建一个looper
Looper.prepareMainLooper(); //2 创建一个ActivityThread实例并调用attach()方法
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq); //3 消息循环
Looper.loop();
}

可以看到,主线程中第一句就是创建一个looper,并调用了Looper.loop()进行消息循环,因为线程只有有了一个looper,才能消息循环,才能不停的从消息队列中取出消息,分发消息,并处理消息。没有消息的时候就阻塞在那,等待消息的到来并处理,这样的我们的app就是通过这种消息驱动的方式运行起来了。

我们来看下 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();
}
}

第一句,调用了prepare(false)

源码如下:

    private static void prepare(boolean quitAllowed) {
//1 查看当前线程中是否有looper存在,有就抛个异常
//这表明,一个线程只能有一个looper存在
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
} //2 创建一个Looper并存放在sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}

sThreadLocal的定义如下

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

是一个静态的变量。整个APP进程中只有一个sThreadLocal,sThreadLocal是线程独有的,每个线程都调用sThreadLocal保存,关于sThreadLocal的原理,其实就是类似HashMap(当然和HashMap是有区别的),也是key,value保存数据,只不过key就是sThreadLocal本身 ,但是映射的数组却是每个线程中独有的,这样就保证了sThreadLocal保存的数据每个线程独有一份,关于ThreadLocal的源码分析,后面几章会讲。

既然Looper和线程有关,那么我们来看下Looper类的定义,源码如下:

/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
public final class Looper {
.......
}

我们看上面的注释

  * <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>

这就是经典的Looper的用法 ,可以在一个线程中开始处调用 Looper.prepare();

然后在最后调用 Looper.loop();进行消息循环,可以把其它线程中的Handler实传进来,这样,一个Looper线程就有了,可以很方便的切换线程了。

下章节我们来自己设计一个Looper线程,做一些后台任务。

Handler的消息机制源码就分析到这了

Android Handler消息机制源码解析的更多相关文章

  1. 史上最详细的Android消息机制源码解析

    本人只是Android菜鸡一个,写技术文章只是为了总结自己最近学习到的知识,从来不敢为人师,如果里面有不正确的地方请大家尽情指出,谢谢! 606页Android最新面试题含答案,有兴趣可以点击获取. ...

  2. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

  3. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  4. [Android]简略的Android消息机制源码分析

    相关源码 framework/base/core/java/andorid/os/Handler.java framework/base/core/java/andorid/os/Looper.jav ...

  5. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  6. 【Android】IntentService & HandlerThread源码解析

    一.前言 在学习Service的时候,我们一定会知道IntentService:官方文档不止一次强调,Service本身是运行在主线程中的(详见:[Android]Service),而主线程中是不适合 ...

  7. Git8.3k星,十万字Android主流开源框架源码解析,必须盘

    为什么读源码 很多人一定和我一样的感受:源码在工作中有用吗?用处大吗?很长一段时间内我也有这样的疑问,认为哪些有事没事扯源码的人就是在装,只是为了提高他们的逼格而已. 那为什么我还要读源码呢?一刚开始 ...

  8. Android View 事件分发机制 源码解析 (上)

    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...

  9. Android消息机制源码分析

    本篇主要介绍Android中的消息机制,即Looper.Handler是如何协同工作的: Looper:主要用来管理当前线程的消息队列,每个线程只能有一个Looper Handler:用来将消息(Me ...

随机推荐

  1. setState 是异步的

    1.解决 setState 异步问题 // 查询 handleSearch(e){ // 禁止默认行为 e.preventDefault(); // 获取 form 表单的值 this.setStat ...

  2. Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制

    转自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315) 今天这篇文章主要分析的是Android的事件分发机制, ...

  3. IO模型:同步、异步、阻塞、非阻塞

    前言: 在Linux的网络编程中,同步IO(synchronous IO).异步IO(asynchronous IO).阻塞IO(blocking IO).非阻塞IO(non-blocking IO) ...

  4. HDU 1028 Ignatius and the Princess III (母函数或者dp,找规律,)

    Ignatius and the Princess III Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K ...

  5. Kafka知识点汇总

    整体结构 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZXJpY19zdW5haA==/font/5a6L5L2T/fontsize/400/fill/I ...

  6. mvc4中 @Url.Action 如何读取javascript变量的值

    js中  function Search()  {     var businessDate = GetSelectedBusinessDate("#businessYear",  ...

  7. dubbo配置指南

    dubbo配置指南 SLA配置在此完成!Service Layer Agreement ApplicationConfig 应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者. Regist ...

  8. monitor and move the log content to our big data system

    Apache Flume HDFS Sink Tutorial | HowToProgram https://howtoprogram.xyz/2016/08/01/apache-flume-hdfs ...

  9. crontab -e 定时任务中的 脚本文件 路径

    crontab -l 57 */1 * * * python /home/data/crontab_chk_url/personas/trunk/plugins/spider/chk_url_stat ...

  10. 蒟蒻的HNOI2017滚粗记

    蒟蒻的第一次省选,然而并没有RP爆发... Day 1: 8:00开考,(然而密码错误是什么鬼).跌跌撞撞,8:40终于拿到纸质试题. { T1:作为一名没有学过Splay的蒟蒻,考场上真的被出题人感 ...