源码分析-Android中的消息机制详解

来源:互联网 发布:域名免费的 编辑:程序博客网 时间:2024/04/28 02:17

本人原创,转载请注明出处哈:http://blog.csdn.net/tyhj_sf/article/details/51105453

1 引言

个人认为,不亲自阅读和熟悉Android的源码,不了解Android源码背后的实现机理,就不能真正成为Android开发的高手。前两天为了了解android的消息处理机制,我阅读和分析了Looper,Handler,MessageQueue,Message这几个类的源码,不得不对Google大牛们膜拜下,现在把学习的笔记拿出来特与大家分享。

2 消息机制的基本原理

从大的方面讲,不光是Android平台,各种平台的消息机制的原理基本上都是相近的,其中用到的主要概念大概有:

1)消息发送者;
2)消息队列;
3)消息处理循环。

示意图如下:
这里写图片描述
图中表达的基本意思是,消息发送者通过某种方式,将消息发送到某个消息队列里,同时还有一个消息处理循环,不断从消息队列里摘取消息,并进一步解析处理。

在Android平台上,把上图的右边部分包装成了一个Looper类,这个类的内部具有对应的消息队列(MessageQueue mQueue)和loop函数。
但是Looper只是个简单的类而已,它虽然提供了循环处理方面的成员函数loop(),却不能自己凭空地运行起来,而只能寄身于某个真实的线程。而且,每个线程最多只能运作一个Looper对象,这一点应该很容易理解。
Android平台上另一个关键类是Handler。当消息循环在其寄身的线程里正式运作后,外界就是通过Handler向消息循环发出事件的。我们再画一张示意图如下:
这里写图片描述

在Android应用中为线程创建消息处理程序的常规用法如下示例代码:

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();      }  }

3 Android消息机制相关的核心类

android的消息处理有四个核心类:Looper,Handler和MessageQueue(消息队列),Message。其中MessageQueue被封装到Looper里面了,我们不会直接与MessageQueue打交道。下面一一介绍:

3.1 Looper类

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread {    @Override    public void run() {        // 将当前线程初始化为Looper线程        Looper.prepare();        // ...其他处理,如实例化handler        // 开始循环处理消息队列        Looper.loop();    }}

让我们看看与Looper相关的这两行代码各自做了什么。

1)Looper.prepare()的源码:

public class Looper {    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象    private static final ThreadLocal sThreadLocal = new ThreadLocal();    // Looper内的消息队列    final MessageQueue mQueue;    // 当前线程    Thread mThread;    // 。。。其他属性    // 每个Looper对象中有它的消息队列,和它所属的线程    private Looper() {        mQueue = new MessageQueue();        mRun = true;        mThread = Thread.currentThread();    }    // 我们调用该方法会在调用线程的TLS中创建Looper对象    public static final void prepare() {        if (sThreadLocal.get() != null) {            // 试图在有Looper的线程中再次创建Looper将抛出异常            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper());    }    // 其他方法}

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象保存到ThreadLocal中去。如果你还不清楚什么是ThreadLocal,可以去看看它源码,这里不对它进行细说,我们只需要知道是每一个线程对象都有一个ThreadLocal类型的成员变量,而这段源码里的sThreadLocal就是当前线程的ThreadLocal类型的成员变量的引用。

2)Looper.loop()方法的源码:

当调用loop方法后,Looper线程就开始真正工作了,loop()方法内部的For(;;)死循环不断从自己的MessageQueue中取出队头的消息(也叫任务)执行。其源码分析如下:

/**     * Run the message queue in this thread. Be sure to call     * {@link #quit()} to end the 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;        // 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            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            Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            msg.target.dispatchMessage(msg);            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.recycle();        }    }

除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如:

prepareMainLooper(),如果是主线程,调用此方法为主线程准备一个looper,主线程的looper有应用程序初始化;
Looper.myLooper()得到当前线程looper对象;
getThread()得到looper对象所属线程;
quit()方法结束looper循环。

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且最多只能有一个Looper对象,它被添加到ThreadLocal中去。每个线程都有一个ThreadLocal类型成员变量。

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

3.2 Message类

Message类中含有一个Message类型的成员变量next。学过数据结构就可立刻知道,这是链表节点的结构,因此MessageQueue实际上维护的是一个Message链表。Message部分源码:

public final class Message implements Parcelable {    public int what;    public int arg1;     public int arg2;    public Object obj;    public Messenger replyTo;    /*package*/ static final int FLAG_IN_USE = 1 << 0;    /*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;        /*package*/ Bundle data;        /*package*/ Handler target;             /*package*/ Runnable callback;       // sometimes we store linked lists of these things    /*package*/ Message next;····································

在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点做开发时需要注意(有待补充):

1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

3.擅用message.what来标识信息,以便用不同方式处理message。

3.3 Handler类

3.3.1 什么是handler?

handler扮演了往MessageQueue上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以通过它的几个有参构造方法来指定looper的。

下面我们就可以为之前的LooperThread类加入Handler:

public class LooperThread extends Thread {    private Handler handler1;    private Handler handler2;    @Override    public void run() {        // 将当前线程初始化为Looper线程        Looper.prepare();        // 实例化两个handler        handler1 = new Handler();        handler2 = new Handler();        // 开始循环处理消息队列        Looper.loop();    }}

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

3.3.2 Handler发送消息

有了handler之后,我们就可以使用
post(Runnable),
postAtTime(Runnable, long),
postDelayed(Runnable, long),
sendEmptyMessage(int),
sendMessage(Message),
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)
这些方法向消息队列上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:

 // 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行    public final boolean post(Runnable r)    {       // 注意getPostMessage(r)将runnable封装成message       return  sendMessageDelayed(getPostMessage(r), 0);    }    private final Message getPostMessage(Runnable r) {        Message m = Message.obtain();  //得到空的message        m.callback = r;  //将runnable设为message的callback,        return m;    }    public boolean sendMessageAtTime(Message msg, long uptimeMillis)    {        boolean sent = false;        MessageQueue queue = mQueue;        if (queue != null) {            msg.target = this;  // message的target必须设为该handler!            sent = queue.enqueueMessage(msg, uptimeMillis);        }        else {            RuntimeException e = new RuntimeException(                this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);        }        return sent;    }

其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象

3.3.3 Handler处理消息

消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码:

// 处理消息,该方法由looper调用    public void dispatchMessage(Message msg) {        if (msg.callback != null) {            // 如果message设置了callback,即runnable消息,处理callback!            handleCallback(msg);        } else {            // 如果handler本身设置了callback,则执行callback            if (mCallback != null) {                 /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */                if (mCallback.handleMessage(msg)) {                    return;                }            }            // 如果message没有callback,则调用handler的钩子方法handleMessage            handleMessage(msg);        }    }    // 处理runnable消息    private final void handleCallback(Message message) {        message.callback.run();  //直接调用run方法!    }    // 由子类实现的钩子方法    public void handleMessage(Message msg) {    }

可以看到,除了handleMessage(Message msg)和Runnable对象的run( )方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!

3.3.4 Handler的作用

1.只要线程A的handler传进另一个线程B中,就可以在线程B中向线程B发送消息,这些消息会被添加到关联的MessageQueue上。
这里写图片描述

2.handler是在它关联的线程中处理消息的。

handler创建时,成员变量包含了一个looper,而looper关联到一个线程的。因此可以判断handler关联到哪个线程。

这就解决了android不能在其他非主线程中更新UI的问题。

Activity是在主线程运行的,在Activity中创建handler,那么这个handler就属于主线程,我们可以将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知Activity更新UI。
过程如下图所示:
这里写图片描述

下面给出一个在worker thread通过发消息更新UI的例子(代码仅供参考):

public class TestDriverActivity extends Activity {    private TextView textview;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        textview = (TextView) findViewById(R.id.textview);        // 创建并启动工作线程        Thread workerThread = new Thread(new SampleTask(new MyHandler()));        workerThread.start();    }    public void appendText(String msg) {        textview.setText(textview.getText() + "\n" + msg);    }    class MyHandler extends Handler {        @Override        public void handleMessage(Message msg) {            String result = msg.getData().getString("message");            // 更新UI            appendText(result);        }    }}
public class SampleTask implements Runnable {    private static final String TAG = SampleTask.class.getSimpleName();    Handler handler;    public SampleTask(Handler handler) {        super();        this.handler = handler;    }    @Override    public void run() {        try {  // 模拟执行某项任务,下载等            Thread.sleep(5000);            // 任务完成后通知activity更新UI            Message msg = prepareMessage("task completed!");            // message将被添加到主线程的MQ中            handler.sendMessage(msg);        } catch (InterruptedException e) {            Log.d(TAG, "interrupted!");        }    }    private Message prepareMessage(String str) {        Message result = handler.obtainMessage();        Bundle data = new Bundle();        data.putString("message", str);        result.setData(data);        return result;    }}

4 分析源码后对几个疑惑的回答

  1. 问:消息机制相关的类几个类?
    答:Thread、 Handler、Looper、MessageQueue、Message
  2. 问:Thread如何获得Looper?
    答:对于Main Thread,调用Looper.getMainLooper( ),因为Looper已由应用创建。对于worker Thread,调用Looper.prepare( )创建一个关联的looper。
  3. 问:handler如何从消息队列中取消息并处理?
    答:(1)handler持有Looper类型变量mLooper,handler在初始化时,将相关联的线程的Looper赋值给了mLooper。(2)handler持有MessageQueue类型变量mQueue,而mQueue通过赋值语句(mQueue=Looper.mQueue;)实例化。
  4. 问:Message怎样被传递给Handler.handleMessage(msg)方法的?
    答:在Looper.loop()方法中,for死循环中调用了Handler.dispatchMessage(msg)方法(该方法中调用了handleMessage(msg)钩子方法)直到消息队列为空退出循环。
  5. 问:一个线程的handler怎么知道另外的线程发送了msg消息并从消息队列取消息处理呢?
    答:在调用handler.sendMessage(msg)发送了msg消息时,sendMessage(msg)方法内部调用了enqueueMessage()方法,enqueueMessage()内部则调用了C/C++代码接口方法nativeWake( ),在这里我们不在向下研究C++层的代码。通过查资料得知:我们在构造Looper对象时,其内部创建了一个监听机制来监听消息入队,一旦有消息入消息队列进进行消息处理。
0 0
原创粉丝点击