android消息处理机制原理解析

在android开发过程中相信屌丝程序员们都用过Handler来处理一些逻辑任务,比如发送延迟消息处理业务等逻辑,我们常用的图片加载缓存库ImageLoader和Picasso等内部也是通过Handler来最终有后台加载线程切换到主线程(UI线程)来更新页面的,今天就趁着离职有点儿时间就抽空的分析了下它的一点源码,在此总结出来。闲言少叙,书归正传!

先来谈谈Looper:

Looper从源码上来看就是一个普通的Java类,它在消息机制中,顾名思义,就是一个消息循环的角色。有时候读源码,我习惯性的会从它的构造器开始读,当然有时候会从一个方法切入,根据情况不同采取不同的方式。下面让我们看看Looper的构造器都做了什么:

//(每个Looper对象的)消息队列,也就是说每个Looper对象都持有自己的消息队列
 final MessageQueue mQueue;
//(每个Looper线程关联的)当前线程
final Thread mThread;
private Looper(boolean quitAllowed) {
        //初始化当前Looper对象的消息队列
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();//获取当前线程
    }   

从上面的代码中我们可以得出如下简单的结论:

a. 每个Looper对象都有自己的消息队列MessageQueue!

b. 每个Looper对象都和当前线程或者说创建Looper的线程相关联。

那么问题来了,当前线程是如何跟Looper对象想关联的呢?如果你读过Looper源码,从代码注释中你可以看到下面一个代码:

  class LooperThread extends Thread {
        public Handler mHandler;
        public void run() {
            //注意是在run方法中调用了prepare
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                }
            };
            Looper.loop();
        }
    }

可以发现里面调用了prepare()这个静态方法,所以直接看看prepare()这个方法做了什么了不起的事儿!

//注意为静态final变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 public static void prepare() {
        prepare(true);
    }
    private static void prepare(boolean quitAllowed) {
        //一个Thread只能关联一个Looper对象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

从代码上不难看出prepare做了两个工作:

a.在当前线程中创建一个Looper对象,放入ThreadLocal中;ThreadLocal作用简单来说就是在每个线程中存储数据,每个线程只能获取到自己存储在ThreadLocal的数据,其他的线程是获取不到自己线程存储在ThreadLocal的数据的。当然既然用ThreadLocal保存一个Looper那么我们肯定可以通过ThreadLocal得到这个Looper对象,方法如下:

//获取当前线程关联的Looper对象
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

b.如果当前的线程已经有一个Looper对象相关联,就会抛出异常,也就是说一个Thread只能关联一个Looper对象

总之一句话prepare()方法就是让线程关联Looper对象用的!

写到此处不难发现Looper已经完成了如下工作:

这样我们线程也绑定Looper了,消息队列也由Looper对象创建好了,所以该是消息队列工作的时候了!!!那怎么才能让一个Looper对象持有的消息队列工作呢?其实笼统的说一句无非就是不停的从消息队列中查看是否有新的消息,有则处理,无则阻塞!这个处理消息的方法其实在上面的例子中也现身过,那就是loop()这个静态方法!

  /**该方法必须在prepare方法之后调用**/
  public static void loop() {
        //获取当前线程锁关联的Looper对象
        final Looper me = myLooper();
        if (me == null) {//在调用loop()之前必须调用looper.prepare()方法
            throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread.");
        }
        //获取当前对象的消息队列
        final MessageQueue queue = me.mQueue;

        ..........省略两行代码......
        for (;;) {//是一个无限循环,来遍历消息队列
            //获取一条消息Message对象,可能会造成阻塞,
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            //该消息的target是一个Handler,来分发消息,具体怎么分发稍后讨论
            msg.target.dispatchMessage(msg);

           .....省略部分代码..

            msg.recycleUnchecked();
        }
    }

可以发现loop()方法其实执行了如下两步工作:

1)获取当前线程关联的Looper对象

2)获取Looper对象的消息队列然后开启无限循环获取消息和处理消息

重点就是第二步了,在无限循环中有且只有一个跳出的入口:那就是消息队列的next方法返回了null!另外需要注意的是next方法是一个阻塞方法,这也意味着当MessQueue没有消息的时候,next方法会阻塞进而使得loop方法也一直阻塞。当然next方法有新的消息的时候就调用 msg.target.dispatchMessage(msg);发送并处理消息!需要注意的消息队列中的Mssage都有自己的target对象来处理,target对象不唯一!。简单的概况下可以用如下流程图简单表示:

通过上面的流程图也可以清晰的知道在获取到一个Messge对象之后,通过Message.target.dispatchMessage进行消息的分发和处理。那么这个target到底值什么鬼呢?还记得上文的例子代码LooperThread么?里面有个handler是什么意思呢会不会就这这个target呢?其实回答这个问题之前或许我们应该考虑:“我们是什么时候向Looper中的消息队列添加一条条消息来供loop循环读取呢?“

这就不得不说本篇博文的另一个主角Handler了,顺便说一句,Message的target你应该能想到其实也是一个Handler对象,不信?后面又说明!

Handler简析

先看看Handler的一个构造器,为了说明问题捡了其中的一个构造函数来说明:

//当前线程关联Looper对象的消息队列
 final MessageQueue mQueue;
 //当前线程关联的Looper对象
  final Looper mLooper;
    public Handler(Callback callback, boolean async) {
        ...省略部分代码..
        //获取当前线程中关联的的Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {/不能在Thread里面创建Hanlder对象,如果没有调用prepare的话)
            throw new RuntimeException(
                "Can‘t create handler inside thread that has not called Looper.prepare()");
        }
        //这个就是初始化关联对象的地方
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

从上面的代码中可以看出一个Handler对象有如下信息:

1)Handler对象持有一个Looper对象的引用mLooper 。

2)Handler对象持有一个消息队列对象的引用mQueue ,并且该引用在构造器中得到了初始化,初始化也很简单就是把looper对象创建的消息队列MessageQueue赋值给mQueue对象。

也就是说Handler关联了Looper对象及Looper对象创建的消息队列!

3)在一个子线程里面是如果没有调用Looper.prepare,不能创建Handler对象!

万事俱备,是时候回答“什么时候向消息队列添加消息”这个问题了!

在使用Handler的时候我们是通过sendMessage方法发送消息的,看看这个方法都做了什么,查看其源码,它的调用脉络是:

sendMessage(Message)–>sendMessageDelayed(Message, long)–>sendMessageAtTime(Message,long);所以直接看sendMessageAtTime这个方法即可:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ****省略了部分代码**
        return enqueueMessage(queue, msg, uptimeMillis);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //初始化了target从这里可以看出来target就是一个Handler
        msg.target = this;
       //调用messageQueue的enqueueMessage插入消息
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最终sendMessageAtTime回调用enqueueMessage这个方法,这个方法可以得到如下结论:

a,上图中loop()循环取的message对象后调用message.target,这个target就是一个Handler!

b.Handler调用sendMessage方法发送消息的过程其实就是向MessageQueue这个消息队列插入一条消息的过程,另外我们到这里可以做出以下断言:在UI线程中(主线程)中创建的Handler对象通过sendMessage发送的Message实际上是添加到了UI线程的消息队列中!Looper的loop()方法通过循环消息队列,通过其next方法获取消息,然后处理之。所以下面就该讨论处理流程了。

很简单就从上图中的dispatchMessage方法说起:

 public void dispatchMessage(Message msg) {
        //如果创建的msg,设置了callback这个Runnable
        if (msg.callback != null) {
            //执行callback方法,其实是执行run方法
            handleCallback(msg);
        } else {//如果创建Handler的时候创建了Callback对象
            if (mCallback != null) {
                //执行callback的handleMessage方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //让handler自己来处理msg
            handleMessage(msg);
        }
    }

 private static void handleCallback(Message message) {
        //只是简单的调用了Runnable的run方法
        message.callback.run();
    }
 //简单的接口,提供了一个handleMessage方法来处理消息
 public interface Callback {
        //返回一个boolean值
        public boolean handleMessage(Message msg);
    }

所以Handler处理消息的过程其实也很简单:

a.检测Message的callback!=null,注意这个callback其实是一个Runnable,调用handleCallback方法其实就是执行这个Runnable的run方法而已。

b.如果创建Handler的时候,初始化了mCallack(这个callback并不是一个Ruannable,其实一个接口,该接口也很简单,就提供了一个handleMessage方法,由客户端决定怎么处理这条Message。

c.最后一步就是调用Handler的handleMessage方法来处理消息了。

通常我自己在使用Handler的时候就是用的定义一个Handler的子类,重写handleMessage方法来处理消息,倒是没有为Handler创建callback!

同时如果你设置了Handler的Callback,并且Callback的handleMessage方法如果返回true,那么Handler的handleMessage方法将不会执行;否则Handler的handleMessage方法也会得到执行!

所以通过上面的讲解,综合起来能得到下面的流程图:

到此位置android的消息机制可以说是解说完毕,不过还有些问题值得思考:我们说了这么多,那么android主线程(UI线程)的工作机制又是怎么样的呢?

UI线程的消息处理

在任何关于activity启动流程解析的资料中我们都会进入ActivityThread的main方法,这里当然不会再分析Activity的启动流程,拿来主义有时候还是不错的(需要注意一点ActivityThread不是一个Thread,只是一个普通的java对象:

 public static void main(String[] args) {
        //创建主线程的Looper对象,prepareMainLooper内部实际上调用了
        //Looper.prepare(false)方法,并把UI线程交给Looper的静态变量             //Looper sMainLooper;持有
        Looper.prepareMainLooper();
        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        //创建一个Handler,这个Handler就是关联了UI消息队列的Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //开启了消息循环
        Looper.loop();
    }
}

结合前面关于Looper的分析,其实ActivityThread的main方法也很简单,其流程如下:

1)调用Looper.prepareMainLooper()使得UI线程也就是主线程与一个Looper对象想关联。prepareMainLooper方法如下:

 //提供一个静态变量来持有UI线程的Looper对象
 private static Looper sMainLooper;
  public static void prepareMainLooper() {
        //为UI线程创建一个Looper对象
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //静态变量来引用这个关联了UI线程的Looper对象
            sMainLooper = myLooper();
        }
    }

既然Looper用一个静态变量来保存关联了UI线程的Looper对象,那么我们可以调用Looper的如下两个方法检测是否是主线程:

//Picasso提供的检测是否是UI线程的方法
 static boolean isMain() {
    return Looper.getMainLooper().getThread() == Thread.currentThread();
  }

而ImageLoader检测是否是UI线程的方法则是如下:

 Looper.myLooper() == Looper.getMainLooper()

2)创建一个Handler对象,根据前面的讲解这个Handler对象同样关联了UI线程的looper对象以及该looper对象的消息队列

3)调用Looper.loop()循环获取和处理消息。

需要注意的是:根据Looper源码的注释,android官方并不希望我们自己主动调用prepareMainLooper()方法。

最后的总结

这样UI线程的处理流程也简单的梳理完毕,那么还有一个最后一个问题:我们知道UI组件的更新是是在UI线程中进行的,也即是如果你在非线程中处理了某个任务后需要更新UI组件,那么在非UI线程线程工作完成后都需要交给UI线程来处理,怎么通知UI线程呢?你应该会知道答案:通过Handler,在UI线程中创建Handler,在非UI线程工作完毕后调用UI线程创建的Handler发送消息到UI消息队列,然后按照上面的流程图处理即可。

前面我既然分析了ImageLoader的源码(详见ImageLoader博客),那么就根据ImageLoader的工作原理把非UI线程和UI线程的工作流程也做个总结吧,如下图所示:

到此为止android消息处理机制就简单的讲解完毕,如有不当的地方欢迎批评指正,共同学习!

其实多写博客还是有所帮助的,比如我在写这篇博客的时候,边分析Looper和Handler的源码,有的时候还需要ImageLoader的工作原理来配合加深理解和体会。如果我之前没有写过ImageLoader博客的话,理解或者体会或许就不那么深了,这也算是坚持写博客对自己的回报吧!

时间: 2024-10-19 09:00:17

android消息处理机制原理解析的相关文章

Android 消息处理机制

1.消息处理机制简介 主要用于进程内线程之间的通信,主线程一般调用looper()进行循环等待处理消息,其它线程向它发消息并指定消息的处理方法. (1)涉及文件包括frameworks中的:Looper.javaHandler.javaMessageQueue.javaMessage.javaandroid_os_MessageQueue.cpp...涉及文件包括system中的:Looper.hLooper.cpp (2)涉及native处理机制使用pipe加epoll机制实现消息队列的睡眠和

[学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起

Android消息处理机制(源码分析)

前言 虽然一直在做应用层开发,但是我们组是核心系统BSP,了解底层了解Android的运行机制还是很有必要的.就应用程序而言,Android系统中的Java应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下: 1. 有一个消息队列,可以往这个消息队列中投递消息. 2. 有一个消息循环,不断从消息队列中取出消息,然后处理 . 为了更深入的理解Android的消息处理机制,这几天空闲时间,我结合<深入理解Android系统>看了Handler.Looper.Message这几

Android动画机制全解析

导论 本文着重讲解Android3.0后推出的属性动画框架Property Animation--Animator. 产生原因 3.0之前已有的动画框架--Animation存在一些局限性, Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApp

Android消息处理机制:源码剖析Handler、Looper,并实现图片异步加载

引言 我们在做 Android 开发时,常常需要实现异步加载图片/网页/其他.事实上,要实现异步加载,就需要实现线程间通信,而在 Android 中结合使用 Handler.Looper.Message 能够让不同的线程通信,完成异步任务.虽然 Android 官方为我们提供了 AsyncTask 类来完成异步任务,但这个类存在许多问题,并不好用,而且,AsyncTask 也是通过 Handler 和 Thread 来实现异步加载的,所以学习这方面的知识是有必要的 本文讲解思路大致如下:绘制 A

Android消息处理机制

文章出处:http://www.cnblogs.com/qingblog/archive/2012/06/27/2566021.html Google参考了Windows的消息处理机制,在Android系统中实现了一套类似的消息处理机制.学习Android的消息处理机制,有几个概念(类)必须了解: 1.       Message 消息,理解为线程间通讯的数据单元.例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程. 2.       Message Q

android消息机制原理详解

android消息机制原理详解 因为之前使用的是CSDN默认的文本编辑器,而且也因为懒得学用MarkDown来写博客,所以排版上有一些问题.就上一篇写的设计模式之抽象工厂模式提出了这个问题(一个android群的群友提出来的,没有在评论里评论),所以以后的文章都用MarkDown来写了. 好了,言归正传,这篇文章我来介绍一下android消息机制的原理 Android消息机制概述 说到Android的消息机制,Android初级工程师(不包括那些初学者)肯定会想到Handler.是的,Andro

【Android 开发】: Android 消息处理机制之一: Handler 与 Message

最近几讲内容,我们学习了Android中关于多线程的一些知识,上一讲我们讲解了异步任务 AsyncTask 的操作,Android中还提供了其他的线程操作,如Handler Message MessageQueue Looper 等模块,这些就是Android中的消息处理机制.这部分内容是Android学习过程中的重点和难点.    现在我们就来学习一下Android的消息处理,以及剖析一下相关类如Handler和Message类的源代码,同时使用他们来更新UI主线程的操作.因为Android的

Android -- 消息处理机制源码分析(Looper,Handler,Message)

android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类.下面一一介绍: Looper Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程.所谓Looper线程就是循环工作的线程.在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Lo