Android消息机制解析

来源:互联网 发布:网络安全工程师 英文 编辑:程序博客网 时间:2024/06/05 04:27

        我写这篇日志的初衷,真心地想让每个初学Android应用开发的同学们(高手就飘过吧~~)弄明白Android消息机制的原理和底层实现方式,至于读者能否对Android消息机制彻底弄明白,纯粹是对我个人表达能力的一次巨大挑战。因为Android事件机制和代码实现,对已经具备JavaC++语言语法基础的人来讲,其实并不难理解。

       在笔者看来,消息机制有2大应用场合,一是GUI程序设计,比如我们用鼠标在一个程序界面上实施鼠标点击,拖拽,键盘输入等动作,程序一般便会执行预期的效果。二是后台服务程序,没有程序界面的那种。比如说一些银行金融产品软件,电信设备的操作维护平台软件等。我们一次网上银行汇款,一次拨打电话行为,都会激发大量的系统内部消息和消息处理动作,这类系统往往还会添加分布式组件模块,屏蔽消息跨平台的发送和接收机制,但其核心设计思想仍基于GUI程序的消息处理模型。

       既然如此,我就从GUI程序说起吧。在界面上一个看似简单的操作背后,其实隐藏了GUI程序设计核心的三要素:线程,消息队列和消息响应函数。当然了,还有些具体的细节过程,比如如何发送消息,取得消息,执行消息响应函数,消息循环的停止等。

       为便于更好的说明这三要素,我把线程,消息队列和消息响应函数,变通成一个邮局的工作流,可以把邮递员看成是线程,邮件看成是消息,那么邮筒就是一个消息队列了,邮递员把邮件投递到邮件接收者的过程看成消息的响应函数。

       邮局的工作流程大概如下图:


       简单说明:头天路人甲,乙,丙分别向邮局的邮筒里投递了3封邮件:mail 1mail 2mail 3,拼命三郎今早上班时和往常一样从邮筒中取邮件时发现了这3封邮件,然后骑上自备的电炉乐呵乐呵的按照mail 1mail 2mail3上的地址送到接收人手里,然后再返回邮件取新的邮件,运气不错,拼命三郎没取到新的邮件,可以好好歇歇了^_^

       但我们还不能歇息,继续探究拼命三郎取送邮件的过程,和Android消息机制的关联性,探究完了我们也就明白Android消息机制的来龙去脉了。下面掌声有请Android消息机制的三大部件隆重登场(鼓掌~~~

Thread:大家好,我是线程,关于我的身世我在这里没什么好讲的,有兴趣的同学可以参阅操作系统的原理方面的书籍和资料,里面会有我前世今生的详细介绍。平常,大家没少跟我打交道。诸位最愿意干的事情就是在我的Run方法里写一个带while循环的函数,让我不停地在这个函数里面兜圈。老实讲,虽然这样让我很累,和感觉些许的无聊,但这是我的职责所在——毫无怨言的执行Run方法的代码指令,我的这个特点和上图中的整天不停取,送邮件的拼命三郎很像吧。

Looper:各位同学好,我的名字大伙都认识的,大家猜猜我是什么循环者?

xx: 等等,这个听上去很熟啊,Thread不是经常在Run方法里做循环的嘛?!你能说讲你和Thread是什么关系?

Looper继续道:xx同学将来一定是块写程序的料,思维很有想象力嘛。在我内部,确实有一个叫loop的消息循环函数和一个消息队列。我再透露一点,在我的一

个实例产生时,它就被上天安排和一个Thread男绑定在一起的时候,可说是真正意义上的执子之手白头到老了。从那刻起,我便拥有了Thread生,Thread对我也是不离不

弃。为什么?一个线程永远只能拥有一个Looper对象,同时Threadrun方法中会进入我的loop消息循环。不管白天和黑夜,Thread做的事情只有一件:永不停歇地从我的

消息队列里获取消息,然后执行消息的target定义的行为——这就是传说中的消息循环,直到程序主动退出循环。执行消息的target定义的行为,这点和拼命三郎,根据邮件

的收信人的地址送信一样啊,不同的接收人地址,决定了送邮件的路径和路程是不一样的。同样的,不同的消息其处理方式也是不一样的。

Handler:大家辛苦了,久等了,我接着刚才Looper说到的—消息的target说起,消息的target实际的扮演者就是我啦。从我的名字也显现了我的身份——处理者,即消息响 应处理者。还有一点:大家知道Looper中消息队列中是怎么来的吗?嗯,对,答案是通过我投递的。那我是怎么拿到Looper的消息队列的呢?其实,在new一个我的实例的时候,会有个Looper实例也跟我绑定一下,这样通过我发送的消息就自动添加到对应Looper的消息队列了,通过上面Looper的自我介绍,也就决定了这条消息是发个那个线程的,是吧^_^

       通过上面三大部件的一段真情对白,相信大家对Android事件机制已经有了清晰的感性认识吧。下面我们再从代码实现的角度来剖析:

       首先,看看一个Looper对象如何跟一个Thread对象绑定的,这里我拿HandlerThread作为解剖标本,它的类是Android SDK中自带Looper的线程子类,重点就在其run方法中:

public class HandlerThread extends Thread {

    private int mPriority;

    private int mTid = -1;

    private Looper mLooper;

    public HandlerThread(String name) {

        super(name);

        mPriority = Process.THREAD_PRIORITY_DEFAULT;

    }

    public void run() {

        mTid = Process.myTid();

        Looper.prepare();

        synchronized (this) {

            mLooper = Looper.myLooper();

            notifyAll();

        }

        Process.setThreadPriority(mPriority);

        onLooperPrepared();

        Looper.loop();

        mTid = -1;

}

}

1. run方法中第一行获取线程所在进程的id

2. 第2行有点陌生,其效果就是使当前线程绑定一个Looper对象啦。

F3跟踪展开Looper.prepare()函数如下:

    public static final void prepare() {

        if (sThreadLocal.get() != null) {

            throw new RuntimeException("Only one Looper may be created per thread");

        }

        sThreadLocal.set(new Looper());

}

在线程的本地存储区域中设置了一个Looper对象实例。在设置之前作一次检测,如果当前线程已经绑定一个Looper实例,程序则抛出异常。这就是前面为什么说到的Thread对一个Looper不离不弃,生死相依的原因。

F3继续深入,看看在Looper对象的构造中能否挖掘到消息队列影子~~

    private Looper() {

        mQueue = new MessageQueue();

        mRun = true;

        mThread = Thread.currentThread();

}

Okay,在Looper的私有构造函数中第1行,一个消息队列MessageQueue终于诞生了。至于MessageQueue长成什么样子,大家可以再继续按F3跟踪,这里它以不是重点。

3. 我们展开第4mLooper = Looper.myLooper();源码如下

    public static final Looper myLooper() {

        return (Looper)sThreadLocal.get();

}

把当前线程绑定的Looper对象赋值给mLooper

4. 8onLooperPrepared();是在进入消息循环之前留给用户自定义的场合了,在HandlerThread类中其实现为空。

5. 关键的第9Looper.loop();相信大家都能猜到这行干的事情了——消息循环。还等什么,赶紧跟进去一趟究竟吧~~~

    public static final void loop() {

        Looper me = myLooper();     // 得到当前线程的绑定的Looper对象

        MessageQueue queue = me.mQueue;  // Looper对象上获取消息队列对象

        while (true) {                     // 呵呵,折腾线程的while循环

            Message msg = queue.next(); // might block 从消息队列中获取到一条消息,队列为空时阻塞

            //if (!me.mRun) {

            //    break;

            //}

            if (msg != null) {

                if (msg.target == null) {

                    // No target is a magic identifier for the quit message.

                    return; // 如果消息的target为空,while循环退出哦,这也是终止消息循环的方法

                }

                if (me.mLogging!= null) me.mLogging.println(

                        ">>>>> Dispatching to " + msg.target + " "

                        + msg.callback + ": " + msg.what

                        );

                msg.target.dispatchMessage(msg); 

// msg.target就是Handler对象,所以消息的响应函数就是HandlerdispatchMessage方法,在dispatchMessage方法中又会调用到handleMessage()方法,所以我们只须定义handleMessage()就能实现对消息的处理。

                if (me.mLogging!= null) me.mLogging.println(

                        "<<<<< Finished to    " + msg.target + " "

                        + msg.callback);

                msg.recycle();

            }

        }

    }

       Okay,通过上面代码的剖析,我们看到了HandlerThread类中run方法如何创建Looper对象,MessageQueue对象,线程进入消息循环的过程,以及消息循环里面具体干啥子事儿。

       现在离大功告成还差最后一步了,那就是消息是如何投递到消息队列中去的?前面说到是由Handler对象投递的,那么Handler应该先拿到Looper的消息队列,然后通过消息队列的某一成员方法将消息添加到消息队列中即可。事实是否如我们推测的这样呢,老方法跟踪源码。

       第一步,Handler必须要拿到一个消息队列的引用。

看看HandlerHandler(Looper looper)构造函数,很明显,HandlermQueue对象拿到了指定looper对象的mQueue

    public Handler(Looper looper) {

        mLooper = looper;

        mQueue = looper.mQueue;

        mCallback = null;

    }

Handler的构造函数还有另一个版本——没有指定Looper,这里则以当前线程绑定的Looper对象的消息队列赋值给HandlermQueue

    public Handler() {

        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 that has not called Looper.prepare()");

        }

        mQueue = mLooper.mQueue;

        mCallback = null;

    }

       第二步,通过Handler投递消息。

    public final boolean sendMessage(Message msg)

    {

        return sendMessageDelayed(msg, 0);

}

       继续跟踪:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)

    {

        if (delayMillis < 0) {

            delayMillis = 0;

        }

        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

    }

       继续跟踪:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)

    {

        boolean sent = false;

        MessageQueue queue = mQueue;

        if (queue != null) {

            msg.target = this;

            sent = queue.enqueueMessage(msg, uptimeMillis);

// 这里通过queueenqueueMessage方法把msg加入到消息队列中去了

        }

        else {

            RuntimeException e = new RuntimeException(

                this + " sendMessageAtTime() called with no mQueue");

            Log.w("Looper", e.getMessage(), e);

        }

        return sent;

    }

好吧,如果诸位有耐心看到这里,应该对Android的消息机制有新的认识和自己的看法了。再结合示例代码调试稍加练习,一定会有所收获。如果问题欢迎拍砖交流O(_)O


原创粉丝点击