Android总结之--消息机制

消息机制

你了解安卓中的消息机制吗;
1、一个线程可以存在多个消息队列吗
2、延迟消息是怎么处理的
3、 View.post(Runnable action) ,runOnUiThread(Runnable action)和Handler.post(Runnable action)区别
4、 为什么主线程不会因为Looper.loop()方法造成阻塞
5、 为什么主线程会ANR而子线程不会呢
6、 HandlerThread原理
7、 IntentService原理

消息机制
我们从常用的Handler.post(Runnable action)去一步步解析消息机制

  • 不管是post(),postDelayed(),sendMessage()等方法最后会调sendMessageDelayed();
public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
}
  • 把runnable或者what都封装成Message
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
}
  • 消息发送。注意SystemClock.uptimeMillis() + delayMillis SystemClock.uptimeMillis() 是本地方法获取当前时间
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  • 加入消息队列。enqueueMessage(queue, msg, uptimeMillis)。注意这里msg.target = this
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}
  • MessageQueue 消息队列怎么来的呢
    我们从Handle构造方法开始找
public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        //这里的有没有遇到过,我们平常new的Thread是没有mLooper的。在子线程里直接new Handle就会抛出这个
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

-消息队列是从Looper里来拿的,那Looper又是怎么来的呢

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

再看看 sThreadLocal.get()
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
       return setInitialValue();
}
通过set(T value)方法再看看looper是怎么加到ThreadLocalMap里的
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
  • Looper又是在哪里实例化并是怎么加到ThreadLocalMap里的呢
    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();
    }
-前面有提到过"消息队列是从Looper里来拿的"然后一个Thread只有有一个Looper,所以一个Thread只能有一个Looper,对应一个消息列队。
  • 前面有提到线程默认是没有Looper的,那么主线程在哪里创建了Looper呢
//ActivityThread的main方法
    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  • 加到消息队列
    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            //这个mMessages是链表的头
            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;
    }

通过上边代码可以看到消息是按when排序好的。在插入消息前会记录消息要发送的时间。这样就可以处理延时消息了

万事俱备,就差牛逼的消息获取以及分发了

  • 死循环一直从MessageQueue中遍历消息
    我们都知道一个线程做完一件事情就没了,但主线程就算没事做也不能结束掉呀。很简单,一个死循环。
 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();

        boolean slowDeliveryDetected = false;

        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
            final Printer logging = me.mLogging;
            if (logging != null) {
                //有没有人用过BlockCanary,原理很简单
                //在事件分发前后做记录,就能分析出在dispatchMessage中执行了耗时操作
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            try {
                msg.target.dispatchMessage(msg);
            } 
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            msg.recycleUnchecked();
        }
    }

关键是两个点

  • 获取消息: Message msg = queue.next(); // might block
 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;
        }
    }

1、拿到消息,判断消息when
1.1、立即返回message去分发
1.2、延时消息计算要分发的时间nextPollTimeoutMillis,会调用本地的方法nativePollOnce(ptr, nextPollTimeoutMillis);
没有消息nextPollTimeoutMillis = -1 会一直阻塞操作,等待新消息进来
延时消息nextPollTimeoutMillis > 0 会一直阻塞操作,到点后或者新消息进来唤醒
1.3、nativePollOnce()方法里本质同步I/O,即读写是阻塞的,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

具体nativePollOnce()的Native层实现,想深入研究可查看资料: Android消息机制2-Handler(Native层)

  • 消息分发:msg.target.dispatchMessage(msg);前面有提到要注意Message加入列表前msg.target = this。所以消息分发就是调的Handle的dispatchMessage(msg)
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  • runOnUiThread(Runnable action)就很简单了,看一下源码
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
  • View.post(Runnable action)复杂一点,我们平时有尝试过拿View的宽高,在onCreate(Bundle savedInstanceState)甚至在onResume() 里都是拿到宽高为0。然后通过View.post(Runnable action)方法在run()里却拿到真正的宽高。
    首先我们看一下post()代码
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

代码以及注释很明显,如果AttachInfo 不为空就走的mHandler.post(action);否则推迟这个Runnable直到知道要在哪个线程去运行。那这个getRunQueue().post(action)应该就是暂时缓存起来了。

    public void post(Runnable action) {
        postDelayed(action, 0);
    }
    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

1、我们先看AttachInfo。mAttachInfo是在View第一次attach到Window时,ViewRoot传给自己的子View的。这个AttachInfo之后,会顺着布局体系一直传递到最底层的View。

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
            }
        }
 void dispatchDetachedFromWindow(){
 ... 
 mAttachInfo = null;
 }
  • HandlerThread 其实HandlerThread 继承Thread,但是它实现了消息队列。特点就是开启一个线程按消息发送顺序串行地处理事务,可以复用,不用重新new Thread。
  • IntentService 继承 Service。内部用了HandlerThread去起一个子线程做耗时操作,最后stopSelf()。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值