安卓消息处理机制 【安卓似懂非懂的小知识点】首篇

来源:互联网 发布:大数据 课程 编辑:程序博客网 时间:2024/06/05 18:28

前言:

因为Handler机制在网上很多,大家也分析的很透彻。但是还是有朋友会问我一些Handler/Looper的东西,那么本篇就以解惑为主,讲了一些平常我们可能没接触过的细节小知识点。后续大家也可以继续留言提问讨论。


首先简单概括一下Hanler机制的概况,我们通常怎么用,Hanler机制是怎么运作的,如图:



        通常我们会先定义并初始化一个handler对象,然后在子线程做完一些操作需要通知Handler线程(大家熟悉的是在主线程,当然也可以是其他线程)操作时生成Message, handler.sendMessage ,就大功告成了。

我们来看看机制在做什么:

1、sendMessage (当然还有其他发送消息的形式)

  handler调用此方法会将此message与自己绑定:msg.target = this; 标记这个message将来就是要执行此handler的处理。(对于这里,不一定是处理handleMessage,下面会有解释)

2、sendMessage最终调用MessageQueue.enqueueMessage

  Handler有一个属于自己的MessageQueue(下面解释),MessageQueue将所有消息用一个链表维护,enqueueMessage会将消息按照执行时间等条件(除了执行时间还会有其他条件,下面解释)将消息放入链表等待Looper来取。

3、怎么执行到的handleMessage呢

  Looper是在Looper所在线程中无限循环的,循环做什么呢?只有两件事,取message:queue.next(),执行message对应的操作msg.target.dispatchMessage(msg); 这个target就是前边说的message绑定的handler,也就是发送自己的handler,执行它的dispatchMessage里就是回调的咱们熟悉的handleMessage。


大致情况介绍完了,我们来详细分解一下:

首先大家疑惑较多的就是线程问题。我们什么都没有做Handler怎么就运行在主线程了?那我们就扩展开来,先看看Looper是怎么回事:

Looper:

首先贴一个demo:

    @Override    public void run() {        mTid = Process.myTid();        Looper.prepare();        synchronized (this) {            mLooper = Looper.myLooper();            notifyAll();        }        Process.setThreadPriority(mPriority);        onLooperPrepared();        Looper.loop();        mTid = -1;    }


        这个是我粘贴的HanlderThread的run方法,其实这就是我们通常使用Lopper的正确姿势。所以说,HanlderThread就是一个为我们封装好的包含有Looper的Thread。

简单说就是三部 

        1 Looper.prepare();

        2 做一些loop前的操作,因为一旦loop就没有机会再执行线程中后续的代码了(除非调用quit),线程就进入了取消息,执行消息的无线循环中。

          // 这个动作对应在HanlderThread里就是onLooperPrepared();这个回调方法,使用者override次方法,在这里做loop前的准备工作。

        3 Looper.loop();


 这里是Looper的初始化

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    public static void prepare() {        prepare(true);    }    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

        可以看到,prepare时new Looper放在ThreadLocal里,每次prepare时先判断ThreadLocal里有没有Looper,保证了Looper的唯一性,同时使用ThreadLocal又保证了线程之间不收干扰。

        也就是说每一个线程都可以创建自己的Looper,且线程中唯一。

 除此之外,Looper还提供了主线程标识:

    private static Looper sMainLooper;  // guarded by Looper.class    public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }

    public static Looper getMainLooper() {        synchronized (Looper.class) {            return sMainLooper;        }    }

        主线程是通过prepareMainLooper初始化的,我们可以通过getMainLooper方便的获取主线程Looper。prepareMainLooper不提供开发者使用,因为主线程Looper一早就被系统初始化了,轮不到我们插手。。。

        这是ActivityThread的main方法,在new ActivityThread  thread.attach(我们的activity的初始化以及生命周期都在attach之后了)之前就已经prepareMainLooper了。

        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        Looper.loop();

        顺便插一句,Activity的生命周期就靠这个Looper了,系统会在合适的时机发送消息,ActivityThread里维护有一个Handler,他根据消息执行各种动作,生命周期就在其中。

        再回过头看一下Looper的构造,保存了一个MessageQueue和当前的Thread。从这里可以看出,Looper和MessageQueue是一一对应的,Handler是通过和Looper的绑定获取的Loop的MessageQueue。

然后就是让Looper动起来,loop方法:

    public static void loop() {        ...        for (;;) {            Message msg = queue.next(); // might block            ...                msg.target.dispatchMessage(msg);            ...            msg.recycleUnchecked();

捡两句贴一下,就是循环起来,取消息,执行消息,回收消息。

---------------------------------------------------------------------------------

然后再来看一些细节的东西,上面提到了几个地方说要下面讲,好的,面下好了,开讲:

1、消息以及消息发送

        首先说一下消息怎么生成。

        两种方法:1、直接new; 2、Message的obtain方法;3、Handler的obtainMessage(其实也调用的Message.obtain)

        那么就重点说obtain

    public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }

        Message里维护有一个消息池,sPool,obtain方法就是从这里取消息的,能够提高效率合理利用已有资源,建议大家这样使用。

回收消息 recycle:




取出消息 obtain




2、handler的回调处理动作,handleMessage,是Looper取到消息后通过消息message调用的handler的dispatchMessage回调的。

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

这里调用了三个回调,从代码逻辑可以看出:

        1、handleCallback(msg)优先,有了他就屏蔽其他处理;

        public static Message obtain(Handler h, Runnable callback) 

        Message提供了这个获取方式,生成消息时传入一个Runnable赋值给成员callback,开发者也可以自己直接赋值进去。有了这个callback,消息就唯一执行此callback的run方法,屏蔽其他handleMessage。(handleCallback是直接调用callback的run方法,并没有start启动线程)

        另外,Handler的post方法也是生成了带有callback的message发送出去的。

        2、其次是mCallback的handleMessage,根据其返回值决定是否执行handler的handleMessage;

        Handler也提供了可以传入Callback的构造方法,这个Callback是Handler内部的interface,开发者需自行override其handleMessage,这个handleMessage返回值为true则屏蔽handler的handleMessage,返回false则执行完之后还会执行handler的handleMessage

        3、最后才是handler的handleMessage。


3、Handler并不是只能运行在主线程中,那他到底运行在哪里,我们怎么做? 应用程序里有多少Looper有多少MessageQueue,他们怎么跟Handler联系起来的

        前面讲Looper的时候说到了,Looper与MessageQueue一一对应,Handler是与Looper建立联系后才能拿到MessageQueue的。Handler是根据与自己建立联系的Looper去确定自己要运行在什么线程的,因为Handler的回调handleMessage是Looper取执行的嘛。

        前面讲Looper也讲到,Looper与线程相关,也就是每个线程都可以创建自己的Looper且唯一,MessageQueue与Looper一一对应。Handler和Looper可以直接建立联系,从而跟MessageQueue联系起来,具体做法:Handler的构造可以传入Looper,这里传入的是哪个Looper他就运行在对应的线程里,不传入Looper则默认是当前创建handler对象的线程的Looper。


4、MessageQueue是如何分配Message的。


下面详解MessageQueue:

消息入队列的方法(消息队列实际不是数据结构的队列,而是根据消息需要执行的时间排序的有序链表):

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


首先判断没有target的不要,当前标记为正在使用的不要

加锁,跟next方法取消息动作互斥。

然后判断mQuitting,当Looper quit时会置true,说明handler对应的Looper已经退出了,就不能再入队了。

紧接着标记此条消息正在使用中markInUse,不可做回收、由其他队列入队等操作。

下面就是正式寻找入队位置:


从队列中获取可执行的message:

    Message next() {        // Return here if the message loop has already quit and been disposed.        // This can happen if the application tries to restart a looper after quit        // which is not supported.        final long ptr = mPtr;        if (ptr == 0) {            return null;        }        int pendingIdleHandlerCount = -1; // -1 only during first iteration        int nextPollTimeoutMillis = 0;        for (;;) {            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                // Try to retrieve the next message.  Return if found.                final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages;                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                if (msg != null) {                    if (now < msg.when) {                        // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        // Got a message.                        mBlocked = false;                        if (prevMsg != null) {                            prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);                        msg.markInUse();                        return msg;                    }                } else {                    // No more messages.                    nextPollTimeoutMillis = -1;                }                // Process the quit message now that all pending messages have been handled.                if (mQuitting) {                    dispose();                    return null;                }                // If first time idle, then get the number of idlers to run.                // Idle handles only run if the queue is empty or if the first message                // in the queue (possibly a barrier) is due to be handled in the future.                if (pendingIdleHandlerCount < 0                        && (mMessages == null || now < mMessages.when)) {                    pendingIdleHandlerCount = mIdleHandlers.size();                }                if (pendingIdleHandlerCount <= 0) {                    // No idle handlers to run.  Loop and wait some more.                    mBlocked = true;                    continue;                }                if (mPendingIdleHandlers == null) {                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];                }                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);            }            // Run the idle handlers.            // We only ever reach this code block during the first iteration.            for (int i = 0; i < pendingIdleHandlerCount; i++) {                final IdleHandler idler = mPendingIdleHandlers[i];                mPendingIdleHandlers[i] = null; // release the reference to the handler                boolean keep = false;                try {                    keep = idler.queueIdle();                } catch (Throwable t) {                    Log.wtf(TAG, "IdleHandler threw exception", t);                }                if (!keep) {                    synchronized (this) {                        mIdleHandlers.remove(idler);                    }                }            }            // Reset the idle handler count to 0 so we do not run them again.            pendingIdleHandlerCount = 0;            // While calling an idle handler, a new message could have been delivered            // so go back and look again for a pending message without waiting.            nextPollTimeoutMillis = 0;        }    }

这里插一段looper取消息的代码

        for (;;) {            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }
 
        MessageQueue里没消息Looper怎么办难道是返回null跳出loop循环吗,不是的。没有消息时MessageQueue不是返回null而是阻塞等待消息。代码中可以看出来,MessageQueue返回null是在Looper退出时。

                // Process the quit message now that all pending messages have been handled.                if (mQuitting) {                    dispose();                    return null;                }

mQuitting在Looper调用quit时通过调用MessageQueue的quit赋值为true的。

        没有消息可处理时是通过 nativePollOnce(ptr, nextPollTimeoutMillis); 这里阻塞等待的。nextPollTimeoutMillis是当前最靠前的消息距离现在的时间。

下面说一下取消息的过程:

                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }

        首先一开始有这么一个判断。判断当遇到了一个没有target的message,这个message是一个标志,他标记着队列开始处理异步消息。if中的do-while可以看出是在循环查找下一个异步消息,而且会先执行do,是要跳过取出这个标志message,也就是说一旦打了这个标志将永久存在。

这里说一下异步消息:

        首先,异步消息通过一个没有target的消息做标记,当插入这个消息时,标志这从这个消息后开始处理异步消息,从代码可以看出来,每次循环都会先判断标志并找异步消息,找到就执行,没找到就走else直接跳过了,说明有了次标志就只处理异步消息,跳过其他消息了。


        那么什么是异步消息呢,他通过FLAG_ASYNCHRONOUS标记为标记。

        如何发送一个异步消息呢:

        Message有setAsynchronous方法,另外,Handler的构造有将其置为异步的构造,这种异步的handler发送消息时会自动给消息加上异步消息标签。

        如何打异步消息标记呢:

        postSyncBarrier打标记    removeSyncBarrier去除标记

        代码不再粘了,postSyncBarrier就是根据时间找链表位置插入,removeSyncBarrier就是找到这个开始异步消息标志并删除他,最后要执行nativeWake唤醒nativePollOnce,因为一直没处理非异步消息,当前要找一下是不是有非异步消息要处理了。


        然后就是取消息动作,没有什么好说的,就是取表头执行就是了。

        继续往下看代码,会发现如果本次循环没有取到消息,后边还会有动作,我们会取出IdleHandler循环执行,执行后移除:

                // If first time idle, then get the number of idlers to run.                // Idle handles only run if the queue is empty or if the first message                // in the queue (possibly a barrier) is due to be handled in the future.                if (pendingIdleHandlerCount < 0                        && (mMessages == null || now < mMessages.when)) {                    pendingIdleHandlerCount = mIdleHandlers.size();                }                if (pendingIdleHandlerCount <= 0) {                    // No idle handlers to run.  Loop and wait some more.                    mBlocked = true;                    continue;                }                if (mPendingIdleHandlers == null) {                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];                }                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);


            for (int i = 0; i < pendingIdleHandlerCount; i++) {                final IdleHandleridler = mPendingIdleHandlers[i];                mPendingIdleHandlers[i] = null; // release the reference to the handler                boolean keep = false;                try {                    keep = idler.queueIdle();                } catch (Throwable t) {                    Log.wtf(TAG, "IdleHandler threw exception", t);                }                if (!keep) {                    synchronized (this) {                        mIdleHandlers.remove(idler);                    }                }            }


        每次取message当发现没有消息了,其实我们还有事情做,执行闲时消息。当IdleHandler都没有了,就会在下次循环时给nativePollOnce设置唤醒时间,阻塞等待。


        这个IdleHandler通过MessageQueue的addIdleHandler添加,removeIdleHandler移除。


讲到这里吧,写累了,哈哈。

大家再讨论吧。





2 0
原创粉丝点击