美文网首页
Android消息机制详解

Android消息机制详解

作者: francis_fanfan | 来源:发表于2018-03-26 16:22 被阅读0次

    Android消息机制

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
    这一定是一个被写烂了的专题吧。那本媛也来凑一个热闹吧。哈哈
    这篇博客将会涉及以下内容:

    • 消息机制概述
    • UML图解消息机制相关类
    • 从在主线程更新UI的方法带你畅游消息机制的源码,更加方便自己理解
    • Handler
    • Looper
    • MessageQueue和Message
    • 消息机制的应用

    消息机制概述

    Android系统在设计的初期就已经考虑到了UI的更新问题,由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:

    1. 加锁会有导致效率底下
    2. 由于可以在多个地方更新UI,开发就必须很小心操作,开发起来就很麻烦,一不小心就出错了。

    基于以上两个缺点,这种方式被抛弃。于是机智如我谷歌爸爸。。。设置一个线程专门处理UI控件的更新,如果其他线程也需要对UI进行更新,不好意思,您把您想做的告诉那个专门处理UI线程的家伙,让它帮你做。大家各有各的任务,井水不犯河水,各司其职,效率才会高,不仅仅对于软件如此,人也是如此,我只管写我的代码,有农民伯伯帮我种吃的、有电脑公司卖给我电脑、有建筑公司给我盖房子(当然了,房子我是万万买不起的... 哼!),这些事儿好像自己也能干,但是都自己干好像就回到了远古时代,人类的进步和发展和社会分工的明确也是离不开的,而且关系很大!
    好像扯的有点远了。

    那么您也看出来了,消息机制其实可以很简单的用一句话概括,就是:其他线程通过给特定线程发送消息,将某项专职的工作,交给这个特定的线程去做。比如说其他线程都把处理UI显示的工作通过发送消息交给UI线程去做。
    实现的原理呢,我是这么理解的,现在要做的主要工作就是切换线程去操作,怎么切换呢?把两条线程看作是两条并行的公路,如果要从一条公路转到另一条公路上,要怎么做呢?是不是只要找到两条公路交叉或者共用某个资源的地方,如果说交叉路口,比如说加油站。当然了,线程是不存在交叉的地方的,那么可以考虑他们公用资源的地方,不同的进程享用不同的内存空间,但是同一个进程的不同线程享用的是同一片内存空间,那让其他线程把要处理的消息放到这个特定的内存空间上,处理消息的线程来这个内存空间上来取消息去处理不就可以了吗。事实正是如此,在Android的消息机制中,扮演这个特定内存空间的对象就是MessageQueue对象,发送和处理的消息就是Message对象。其他的Handler和Looper都是为了配合线程切换用的。
    其实不仅仅是线程之间,不同进程之间进行消息传递(IPC机制),也是这个思路,找到公用的一个资源点,文件系统啊,共享内存啊等等,这个以后再讲吧。

    这样理解起来是不是就是so easy了呢?

    UML图解消息机制相关类

    不知道上面的说法您是否可以对消息机制有了一个基本的认知呢?我曾经在想,怎么通过很简洁直观的方式去把消息机制讲明白(讲给自己,也讲给你)呢,后来我就在想,当初设计者的思路是什么样的呢?我想到了UML图,用类图来对消息机制中涉及到的几个类有一个概括的认识;通过时序图,可以很清晰的观察到整个消息机制的处理过程。
    消息主要设计到下面几个类:

    • Handler:这是消息的发出的地方,也是消息处理的地方。
    • Looper:这是检测消息的地方。
    • MessageQueue:这是存放消息的地方,Handler把消息发到了这里,Looper从这里取出消息交给Handler进行处理
    • Message:呜呜呜...他们发的是我,处理的也是我。
    • Thread:我在这里专门指代的是,处理消息的线程。消息的发送是在别的线程。

    话不多说,先来看一张图(UML忘的差不多了,刚补的,如果有错误,麻烦大家指出)

    消息机制类图

    畅游源码

    图在这里了,怎么看呢,整个消息机制相关的类密密麻麻支撑了一张网,咋个看嘛,,,不急不急,咱们先来思考一下咱们常用的更新UI是怎么一个操作步骤。

    1. 在主线程新建一个Handler对象,在构造方法中传入一个实现Handler.Callback接口的匿名类的对象,实现接口中的handleMessage方法
    2. 在非UI线程使用Handler的sendMessage或者post方法发送一个消息
    3. 然后handleMessage方法会在不久的将来马上执行,实现更新UI的操作。

    那咱们就跟着这个思路来看一看这张图,先看Handler类,你会发现,Handler真的是个相当关键的核心(当然,其他部分也是不可或缺的),他几乎拥有所有其他相关对象的引用。

    • Handler拥有Looper的引用,通过得到Looper对象获得Looper中保存的MessageQueue对象
    • Handler拥有MessageQueue的引用,使Handler得以拥有发送消息(将Message放入MessageQueue)的能力
    • Handler拥有Handler.Callback的引用,使得Handler可以方便的进行消息的处理。

    来思考一个问题:为什么Handler在其他线程发送消息之后,就跑到了主线程的handleMessage方法中去更新UI?

    这个问题暂时先放着,等下回过头再来看。
    我们现在先跟着第1,2,3步看看系统都帮我们做了什么操作呢?这就是在看源码,不要觉得很高深
    下面是鲜活的代码,为了方便您查看,我帮您摘出来了。如果有兴趣,您也可以在AS里点开看看

    
        //这是在主线程中
        Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
    
                switch (msg.what) {
                    case 1:
                        Toast.makeText(mContext, "你真漂亮", Toast.LENGTH_SHORT).show();
                        break;
                    case 2:
                        Toast.makeText(mContext, "你也很帅呢", Toast.LENGTH_SHORT).show();
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
        
        //这是我们在主线程中创建Handler时会使用的构造方法
        public Handler(Callback callback) {
            this(callback, false);//调用了下面的这个构造方法↓
        }
        
        //先不要管第二个参数。跟紧主线,别跟丢了
        public Handler(Callback callback, boolean async) {
            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());
                }
            }
    
              //在这里获取到Looper对象,怎么获取的,稍后再看
            mLooper = Looper.myLooper();
            //如果获取的mLooper为空,直接抛出异常,说你不能在一个没有调用Looper.prepare()方法
            //的线程里创建Handler
            //如此看来,Looper.prepare()方法重要的嘞
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            //通过mLooper对象获取MessageQueue这个消息队列(单链表)
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    到此为止,一个Handler就创建好了,(还有一个问题是Looper.prepare方法很重要,但是我们还没有去考虑他是干嘛的,不急不急,先顺着一条线看,不然看源代码的过程会把你搞死翘翘的)先面就该进行第二步,看看Handler.sendMessage干了啥.代码段又来喽

        //在一个新建的线程里使用创建好的Handler发送一个消息
        new Thread(new Runnable() {
            @Override
            public void run() {
                //在这儿干点你想干的吧,一些耗时的计算或者网络操作啥的
                Message message = new Message();
                message.what = 1;
                handler.sendMessage(message);
            }
        }).start();
        
        //直接调用的是这个函数
        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) {
            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);
        }
        
        //最后的最后来到了这里
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            //target就是Message绑定的Handler,看看类图,上面有这个细节
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //最后的最后,调用了MessageQueue的enqueueMessage方法
            return queue.enqueueMessage(msg, uptimeMillis);
        }
        
        //再看一下MessageQueue的enqueueMessage方法,
        //我把其他一些无关的细节给删掉了,只为了更加容易阅读
        boolean enqueueMessage(Message msg, long when) {
            synchronized (this) {
                Message p = mMessages;
                if (p == null) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                } else {
                    //下面的错误就是遍历单链表,找到链表的尾部,这个没有难度的吧?
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            //找到了尾部,现在的结构是这样的。
                            //A->B->C...->pre(p)->null
                            break;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;//把next插入链表的尾部
                }
            }
            return true;
        } 
    

    到此为止,第二步就结束了,成功把一个消息插入到了MessageQueue的尾部。可是你很快就会发现,第三步好像从这条路探寻不下去了。接下来就等着别人来调用Handler中的方法了,可是是谁调用的,在哪儿调用的?我们现在好像毫无头绪了?怎么办?怎么办?我们刚才不是看到一个Looper.myLooper(),和Looper.prepare()方法,说是很重要但是一直没看吗,既然现在搜寻不下去了,是不是可以回头看看了?还有一点,Looper,看起来是在循环,循环什么玩意儿呢?我们去好好看看Looper类吧。
    一共就三百来行代码,仔细看看,你会发现有一个核心方法:Looper.loop();
    同样的,我把影响阅读的非主线代码剔除了,发现Looper.loop方法就长这样:

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

    清晰可见的是,Looper.loop()方法一直遍历MessqgeQueue,阻塞线程,直到获取到一个Message,然后调用了Message的一个成员变量target(其实就是Handler)的dispatchMessage(msg)方法,嗨,还真的又跟Handler扯上关系了,既然这里扯上关系了,而且还是一个分发消息的方法,可以大胆猜测就是让Handler去处理这个消息的。
    那么我们来看看这个方法:

        /**
         * Handle system messages here.
         * 如果Message中callback对象不为空(这是调用handler.post(Runnable)方法发送的消息),
         * 就调用callback的run方法
         * 否则如果创建Handler的时候如果设置了Callback就调用创建时候的传入的
         * 实现Handler.Callback接口的类的对象的handleMessage方法,看这就是回调方法被调用的地方。
         * 再如果没有mCallback对象,就调用自身的handleMessage方法,为了Handler的子类复写了该方法的时候,方便调用,如,IntentService里的ServiceHandler就是继承自Handler的,并且重写了handleMessage方法。
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
        
        private static void handleCallback(Message message) {
            message.callback.run();
        }
        
        //ServiceHandler继承自Service并且重写了handleMessage方法
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);
                stopSelf(msg.arg1);
            }
        }
        
    

    到了这里,三步走已经看完了,我想消息机制在我们心里已经又清晰了一层,但是不用急,咱们前边提的一个问题不是还没有解决吗,先把他解决掉吧,一起继续来看源码。
    咱们现在已知的是这样的,在主线程创建的Handler发送了一个消息,发送消息的代码运行在其他线程,将代码加入消息队列也是在其他线程(加了线程同步锁)。然后handleMessage发生在主线程,那么调用该方法的dispatchMessage方法也是运行在主线程的,dispatchMessage是在Looper.loop方法中调用的,也就是说loop方法也运行在主线程,那么问题就明朗了,可是loop方法是谁调用的,在哪里调用的呢?当然是系统启动的时候创建主线程之后再主线程的run方法中调用了Looper.prepare和Looper.loop方法,但是这点我还没看,留着以后再看吧。
    然后通过上面的分析,我们是不是可以自己来试着建立这样一个模型:

    1. 创建一个线程A
    2. 在这个线程的run方法中调用Looper.prepare和Looper.loop方法使该线程阻塞,等待消息发过来,然后处理
    3. 在该线程中创建一个Hanlder,用来处理looper发送来的待处理的消息
    4. 创建一些其他的线程a、b、c,做一点操作之后,通过Handler把消息传递出去,让线程A去处理。
    public class MyThread extends Thread {
        private static final String TAG = "MyThread";
    
        Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.i(TAG,"你真漂亮");
                        break;
                    case 2:
                        Log.i(TAG,"你也很帅呢");
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    
    
        public Handler getHandler() {
            return handler;
        }
    
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Looper.loop();
        }
    
        @Override
        public void destroy() {
            super.destroy();
            Looper.myLooper().quit();
        }
    }
    
    //
        private void testMyThread() {
            MyThread thread = new MyThread();
            thread.start();
            final Handler handler = thread.getHandler();
            
            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        sleep(400);
                        Message message = new Message();
                        message.what = 2;
                        handler.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

    试着想一想,如果把线程A看成主线程,在回调方法更新UI,那这不就是Android系统中更新UI使用的套路吗?不错,事实本就如此,工具是工具,用它来更新UI是可以的,那你当然也可以用来做一些其他的工作啊。

    在这里,通过以上的分析,不难得到下面的这个整个消息机制运行过程的时序图:

    [图片上传失败...(image-6a9973-1522052528840)]

    ok啦,源码阅读到此为止。其他的细节,有兴趣的可以再细细研究一下。
    下面我们来对涉及到的类进行一下总结。
    先来说一下我的个人理解,

    Handler

    handler在消息机制中,扮演的是消息的发送方和处理方(通过回调函数)。消息在一个线程通过Handler发送到MessageQueue中。Looper获取到Message之后,根据Message中保存的handler对象调用handler对象的dispatch方法,进行消息的处理。

    Looper

    Looper在这里扮演的是一个轮询消息队列的角色,以为不停地去问MessageQueue要消息,得到消息之后,根据Message中保存的handler对象调用handler对象的dispatch方法,进行消息的处理。

    MessageQueue和Message

    MessageQueue实质上是一个单链表的结构,里面以链表的形式保存着Handler发送过来的消息,当有新消息发来时放在链表的尾部,Looper要取消息的时候从链表的头部取出消息返回给Looper处理。Message对象中保存在要处理的信息,同时也持有消息发送方(Handler)的引用,Looper在得到该Message的时候,可以从Message中拿到消息的发送方,调用发送方的回调方法将消息传递过去交给Handler进行处理。

    消息机制的应用

    在Android中有很多消息机制的应用,如:

    • UI的更新
    • HandlerThread
    • IntentService

    UI的更新

    UI线程持有一个Looper对象,Looper对象的loop方法在UI线程中一直不停的进行死循环,直到有新的消息发来的时候,交给特定的组件进行处理,当然了这个处理也是在主线程运行的(如我们设置的点击事件也是等着被UI线程调用的),正是由于这个原因,我们不能在主线程处理耗时操作。如果我们一个View的点击事件里做了大量耗时的操作,由于这个操作也在主线程中运行,主线程必须等着这个操作操作结束才能去处理其他的消息,这个时候表现的就是系统卡顿甚至报ANR的错误。

    HandlerThread

    HandlerThread继承自Thread,内部保存一个Looper对象。
    这是一个系统帮我们包装好的Thread,这个线程的run方法已经调用了Looper.prepare和Looper.loop(即已经绑定了一个Looper对象,并且可以开始轮询消息),创建该对象之后可以通过获得对象获取到一个Looper对象,将Looper对象传递给Handler,完成Handler和Looper以及MessageQueue的绑定。最后再其他的线程中调用Handler的sendMessage或者post(Runable)方法发送消息,handler中的callback.handleMessage方法会在HandlerThread中运行。即,将消息发送到了特定的线程(此处是HandlerThread)处理。

    IntentService

    IntentService继承自Service,运行时优先级更高,内部使用了HandlerThread作为处理消息的线程。内部有一个私有内部类ServiceHandler继承自Handler,并且会创建一个ServiceHandler对象。
    使用startService()方法启动IntentService时,不会重新创建一个服务,会调用ServiceHandler对象发送包含该Intent的Message对象,该对象通过HandlerThread处理后交给ServiceHandler重写的handleMessage方法进行处理,处理的方式是调用IntentService的onHandleIntent(Intent)方法,所以使用的方式就是创建一个继承自IntentService类的子类,并重写onHandleIntent方法,在该方法中处理startService时传递的Intent。Intent中包含有要交给Service处理的信息。

    相关文章

      网友评论

          本文标题:Android消息机制详解

          本文链接:https://www.haomeiwen.com/subject/axmrcftx.html