Android 的消息机制

作者: 墨染书 | 来源:发表于2018-07-26 01:41 被阅读62次

    为了更好的理解 Looper 的工作原理,我们需要对 ThreadLocal 进行了解,如果对 ThreadLocal 没有了解的童鞋,可以参看 ThreadLocal 原理

    概述

    Handler 作为日常开发的必备,不可避免就要涉及这方面的知识。从开发者角度来说,Handler 是 Android 消息机制的上层接口,使得开发的时只需与 Handler 交互即可。Handler 使用也很简单,能够轻松将一个任务切换到 Handler 所在的线程中执行

    很多人认为Handler的作用就是更新UI,的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景。具体来说,就是有时候需要在子线程做一些耗时操作,比如说访问网络或者耗时的I/O操作,当这些耗时操作完成时,程序的UI进行相应的改变。由于安卓开发规范的限制,我们不能在子线程中访问UI控件,因为UI的控件是线程非安全的,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。

    Android 的消息机制主要是指 Handler 的运行机制。事实上,Handler,Looper,MessageQueue 是一套运行体制而出现的,MessageQueue 是一个消息队列,以队列形式提供插入和删除,主要用于消息的存储,内部实现是采用单链表的形式来组织 Message。Looper 用于处理消息,Looper 内部会以无限循环去查是否有新的 Message ,有则处理,没有就等待。需要特殊说明的是,Looper 内部是使用 ThreadLocal 实现的,由于ThreadLocal 可以在每一个线程中互不干扰的存取数据,所以通过ThreadLocal 就可以轻松获取每个线程的 Looper。

    Message :android.os.Message是定义一个Messge包含必要的描述和属性数据,并且此对象可以被发送给android.os.Handler处理。属性字段:arg1、arg2、what、obj、replyTo等;其中arg1和arg2是用来存放整型数据的;what是用来保存消息标示的;obj是Object类型的任意对象;replyTo是消息管理器,会关联到一个handler,handler就是处理其中的消息。通常对Message对象不是直接new出来的,只要调用handler中的obtainMessage方法来直接获得Message对象。

    需要特殊说明的是,线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建Looper,但是APP 的主线程中,即 ActivityThread ,在主线程被创建的时候就会初始化 Looper。另外,在没有Looper 的线程创建 Handler 也会失败。

    Handler 工作过程
    Hnadler 的工作流程

    使用案例

    话不多说,上例子:

    public class MainActivity extends AppCompatActivity {
    
        private TextView textView;
        private String TAG = "MainActivity";
        private int i = 0;
    
        Handler mHandler = new Handler(){
            /**
             * handleMessage接收消息后进行相应的处理
             * @param msg
             */
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what==1){
                    textView.setText(msg.arg1+"");
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView = (TextView) findViewById(R.id.textView);
        }
        public void onClick(View v){
            ++i;
            //创建新的线程
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    doSendMsg();
                }
            }.start();
        }
    
        /**
         * 在子线程中做耗时操作,完成之后,通知Handler更新UI
         */
        private void doSendMsg(){
            try {
                Thread.sleep(1000);//模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message message = Message.obtain();
            message.arg1 = i;
            message.what = 1;
            mHandler.sendMessage(message);
        }
    
    }
    

    原理分析

    代码版本 : Android API25

    Message

    生成一个 Message

    前面讲过 Message 通常不是 new 出来的,而是通过调用 Handler 的obtainMessage() 得到一个新的 Message:

     public final Message obtainMessage()
        {
            return Message.obtain(this);
        }
    

    而 Handler 就调用 Message 的 obtain(Handler h) 方法:

    public final class Message implements Parcelable {
       
        public int what;  // 让接收者知道这是什么
        public int arg1;  // 存储 int 用于传递过去
        public int arg2;  
        public Object obj;  // 传递一个对象过去
        public Messenger replyTo;  // 可以发送对此消息的回复。具体如何使用取决于发送者和接收者
        public int sendingUid = -1;
    
        /*package*/ static final int FLAG_IN_USE = 1 << 0;  // 标记消息被使用
        /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; // 标记消息是异步的
        /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;  
        /*package*/ int flags;
        /*package*/ long when;     // Message 的执行时间 重要!!!
        /*package*/ Bundle data;    
        /*package*/ Handler target;   // target 负责处理该消息
        /*package*/ Runnable callback;  // Runnable 类型的 callback
        /*package*/ Message next;     // 下一条消息,因为消息队列是链式存储的
    
        private static final Object sPoolSync = new Object();  // 控制并发访问
        private static Message sPool;   // 回收池的头结点
        private static int sPoolSize = 0;
        private static final int MAX_POOL_SIZE = 50;
    
        private static boolean gCheckRecycle = true;
    
        /**
         * Return a new Message instance from the global pool. Allows us to
         * avoid allocating new objects in many cases.
         */
        public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;  // 将头结点赋值给Message
                    sPool = m.next;        // 将头结点更新为下一个节点
                    m.next = null;     // 断掉以前头节点与当前节点联系
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;      // 将回收池的数量减一
                    return m;
                }
            } 
            return new Message();
        }
    
        public static Message obtain(Handler h) {
            Message m = obtain();
            m.target = h;     // 注意,这里将 Handler 保存在了 target 中,后面会调用target来处理这个Message
            return m;
        }
    
    // ...
    }
    

    到了最后就是Message 的 obtain() 方法,从 global pool 返回一个新的Message实例。 允许我们在许多情况下避免分配新对象。而这个 global pool 呢,其实就是一个单链表,从头结点取一个 Message ,如果没有就 new 一个 Message。而这个单链表呢,就是讲无用需要回收的 Message 组织起来的。the global pool 其实就是使用静态常量组织了一些无用了的 Message,组织的数据结构就是单链表。

    看源码就知道,重载了多个obtain 方法,其实就是把上述可选参数配置一下,然后调用 obtain() 得到一个Message。

    回收 Message

    可能看了生成有点懵,那我们提前看一下回收,就好一点了, Message 的 源码:

        public void recycle() {
            if (isInUse()) {           // isInUse() return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;   // 在用就不回收
            }
            recycleUnchecked();  // 直接回收
        }
    
        /**
         * Recycles a Message that may be in-use.
         * Used internally by the MessageQueue and Looper when disposing of queued Messages.
         */
        void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;    // 设置为没有使用
            what = 0;             // 抹去数据
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;  // 将当前 Message 的下一个设置为以前的头结点
                    sPool = this;   // 更新头结点为当前节点
                    sPoolSize++;
                } // 如果到达MAX_POOL_SIZE数量,这个Message就会因为没有与引用链相连而被GC回收
            }
        }
    
    

    回收还是很简单的,就是先检验是否在使用,如果不是,抹去上面的数据,将 Message 加到我称为 回收池 的链表里。需要注意的是,这里如果回收池数量到了上限,这个Message就会因为没有与引用链相连而被GC回收。

    MessageQueue

    MassageQueue 叫做消息队列,通过一个单链表的数据结构来维护消息列表,而且单链表在插入和删除上比较有优势。MessageQueue 主要包含两个操作:插入 和 读取。插入读取对应的方法分别是enqueueMessage(Message msg, long when) 和 next()。

    工作流程

    这里的工作流程先是讲一下例子里面的工作流程,其次补充一些必要的流程,如回收等。

    UI线程 Looper 的创建

    我们知道 UI 线程是在 ActivityThread 中创建的,这个函数就是整个 APP 的入口。接下来就是 ActivityThread 中的 main:

        public static void main(String[] args) {
            ... 
            Looper.prepareMainLooper();  // 1. 创建UI线程的 Looper
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                // UI 线程的Handle
                // getHandler() 得到的是 ActivityThread.H (extends Handler)
                sMainThreadHandler = thread.getHandler(); 
            }
    
            // End of event ActivityThreadMain.
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            Looper.loop();    //2. 执行消息循环
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    看到没有,通过1,2步的配置,这时候 UI 线程中 Looper 其实已经跑起来了,在程序中就已经可以使用 Handler 了。而子线程却默认没有配置

    需要注意的 UI 线程使用的 prepareMainLooper() 来准备 Looper,但是这个方法虽然是 public 的,但是这是专门为 UI 线程量身定做的,我们绝对不可以使用,我们准备Looper可以使用 Looper 的 prepare()就好。

    prepareMainLooper()

    接下来我们来一步一步分析 (Looper 源码:)

        public static void prepareMainLooper() {
            prepare(false);  // new 一个 Looper 放到该线程的ThreadLocalMap中
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                // 将 UI 线程的 Looper 放到静态常量 sMainLooper 中,那么随时随地都可以new 出主线程的 Handler
                sMainLooper = myLooper();  // myLooper() return sThreadLocal.get();
            }
        }
    
        public static void prepare() {
            prepare(true);  // 子线程中默认是可以销毁消息队列的
        }
    
        private static void prepare(boolean quitAllowed) {  // True if the message queue can be quit.
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            // sThreadLocal 是在类定义的时候就初始化了的 static final ThreadLocal<Looper>
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();  // 得到当前线程的 Looper
        }
    

    很简单,结合注释,应该都懂了,就是 new 了一个 Looper ,放到了主线程的 ThreadLocalMap 中。那 new 的时候干了什么呢?

        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    原来是这样,在 new Looper 的时候就创建了 UI 线程的消息队列,并且指定不可以删除。

        // True if the message queue can be quit.
        private final boolean mQuitAllowed;
    
        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();
        }
    

    队列是否可以删除,一直向下传递,最后原来是保存在这里。但是这里还有一个 Native 方法,是干嘛的呢?笔者认为是得到了这个对象所在线程的引用。

    到这里,ActivityThread 中的第一步就算是完成了。由于 sMainThreadHandler 的后期使用涉及复杂,就留到后面讲解,这里用 例子中的 Handler 代替讲解,最后执行的 Handler 是一样的,但是 ActivityThread.H(sMainThreadHandler )封装了其他东西。

    创建 Handler

    看着例子中的 Handler 是直接 new 出来的,那我们看一下 Handler 的无参构造方法:

    public class Handler {
        public Handler() {
            this(null, false);
        }
    
        public Handler(Callback callback, boolean async) {
           ...
            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 = callback;
            mAsynchronous = async;
        }
    }
    

    我们看到 Handler 在构造方法中,通过 Looper.myLooper() 得到当前线程(UI 线程)的Looper,并且保存到本地 final 变量 mLooper 中。

    要知道,消息队列被封装在 Looper 中,而每一个 Looper 又会关联一个线程(Looper 通过 ThreadLocal 封装),最终等于每一个消息队列都会关联一个线程。同时,由上代码可知,每个 Handler 也都会关联一个消息队列。在这里需要注意,Looper 和 MessageQueue 并没有与 Handler关联,而是Handler 与 Looper 和 MessageQueue 建立联系。

    Looper.loop()

    创建了Looper之后,就要执行消息循环了,我们知道,通过 Handler 来 Post 消息给消息队列,那么怎么处理呢?那就是最开始第二步的 Looper.loop() 中的。

        public static void loop() {
            final Looper me = myLooper();  // 获取当前线程的 Looper
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;  // 1. 获取消息队列
    
            // 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 (;;) {       // 2. 消息循环
                Message msg = queue.next(); // might block可能阻塞 3. 获取消息
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                    msg.target.dispatchMessage(msg);  // 4. 处理消息
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
    
                msg.recycleUnchecked(); // 回收
            }
        }
    
    

    我们可以看到 loop 方法中实际上就是建立一个死循环,然后通过从消息队列中逐个取出消息,最后进行处理,至到取到 null 值才退出。这里并没有任何的阻塞,那我消息取完了就退出了么?不,原理请看 MassageQueue 的 next() :

     Message next() {
            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;  // 头结点
    
                    // 如果没有 Handler,就在列表中找下一个同步的 Message 来执行。
                    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.
                             // 设置唤醒时间,距离多久执行 与 int 最大值 2^31 - 1 作比较
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // 注意这时候已经到执行时间了
                            // Got a message.
                            mBlocked = false;
                            // 从消息队列中取出这个 Message
                            if (prevMsg != null) {  // 如果前驱结点不为 null,
                                prevMsg.next = msg.next;   // 请参看 next() 图 1
                            } else {
                                mMessages = msg.next;  //  将头结点后移
                            }
                            msg.next = null;   // 断掉要处理的 Message 与 消息队列的联系
                            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;
            }
        }
    
        // Disposes of the underlying message queue.
        // Must only be called on the looper thread or the finalizer.
        private void dispose() {
            if (mPtr != 0) {
                nativeDestroy(mPtr);
                mPtr = 0;
            }
        }
    

    代码虽然很长,但是我们还是得看啊,其实就是取出单链表(我们前面已说过,MessageQueue其实是一个单链表结构)中的头结点,然后修改对应指针,再返回取到的头结点而已。因为这里采用的是无限循环,所以可能会有个疑问:该循环会不会特别消耗CPU资源?其实并不会,如果messageQueue有消息,自然是继续取消息;如果已经没有消息了,此时该线程便会阻塞在该next()方法的 nativePollOnce() 方法中,主线程便会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生(设置的nextPollTimeoutMillis到了)时,才通过往pipe管道写端写入数据来唤醒主线程工作。这里涉及到的是Linux的pipe/epoll机制,epoll机制是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

    next() 图 1

    总结:
    通过 Looper.loop() 来创建 Looper 对象( 消息队列封装在 Looper 对象中 ),并且保存在 sThreadLocal 中,然后通过 Looper.loop() 来执行消息循环。

    说了取,当然接着就是分发了。我们调用msg.target.dispatchMessage(msg) 来执行 Message 。在创建 Message 的过程中,传过来的 Handler 的引用就被保存在了Message中(最上面有代码和讲解)。接下来看一下 Handler 的处理:

        final Callback mCallback;
    
        public interface Callback {
            public boolean handleMessage(Message msg);
        }
    
        public void handleMessage(Message msg) {
        }
        
        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();   // 此时是在 Handler 所在线程中执行
        }
    
    

    这里面也特别简单,大家都知道当我们在 Handler post()消息的时候是使用 Runnable,而 sendMessage() 是自己构建的 Message。这里首先判断 Message 类型,如果是 Runnable ,就调用run运行,如果不是,先判断创建 Handler 的时候是否设置回调,设置了就调用回调中的处理方法 handleMessage(msg),如果没有就使用默认的 handleMessage(msg),这时候的这个方法大多数时候都会被重写,就像例子一样。

    这里我们可以看到,在分发消息时三个方法的优先级分别如下:

    • Message的回调方法优先级最高,即message.callback.run();
    • Handler的回调方法优先级次之,即Handler.mCallback.handleMessage(msg);
    • Handler的默认方法优先级最低,即Handler.handleMessage(msg)。


      Handler 工作原理.png

    使用 Handler 来 sendMessage(Message msg)

    既然提到 post() 和 sendMessage(),那么下面就讲解一下它是如何将一个 Message 加到队列中的。

    现讲解 sendMessage() 吧,例子就是最上面的例子,那我们接着看 Handler 的源码吧:

        /**
         * Pushes a message onto the end of the message queue after all pending messages
         * before the current time. It will be received in {@link #handleMessage},
         * in the thread attached to this handler.
         *  
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.
         */
        public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);  // 默认延时为 0
        }
    
        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {  // 校验延时,因为延时只能 >= 0
                delayMillis = 0;
            }
            // SystemClock.uptimeMillis() 从开机到现在的毫秒数(手机睡眠的时间不包括在内)
            // SystemClock.uptimeMillis() + delayMillis算出来就是更新时间
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
    
        }
    
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            // 这个值是通过从Looper 赋值过来的,就意味着是持有和Looper 中相同的引用
            // Looper 中的修改的话mQueue,这个值也会被修改
            // 当调用 Looper.quit() 的时候,这个值就被置空了的。
            MessageQueue queue = mQueue;  
            if (queue == null) {   // 因为需要发送到 MassageQueue中,所以不能为空
                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) {
            msg.target = this;   // 保存 Message 的 target
            if (mAsynchronous) {
                msg.setAsynchronous(true);  
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    一直往下调用了三个方法才到底,还好都简单,来看一下。将设置的时延转化成应该被执行的时间,拿到关联的消息队列,随后保存 Message 的 target,然后调用消息队列的 enqueueMessage(msg, uptimeMillis),将这个Message 加入队列。那怎么加的么?接下来就是 MessageQueue 的 enqueueMessage(Message msg, long when)

        boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {  // 一个新的 Message 是不会 InUse 的,在回收的时候设置为没在使用的
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {   // 获得自身的同步锁
                if (mQuitting) {  // MessageQueue 正在退出
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();  // 将Message实例回收
                    return false;
                }
    
                msg.markInUse();   // flags |= 1 标记正在使用
                msg.when = when;   // 设置要插入 Message 的执行时间
                Message p = mMessages;  // 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();  //记得看上面的英文注释 默认为 false
                    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);  // 唤醒线程的 Native 方法
                }
            }
            return true;
        }
    

    看 enqueueMessage 的实现,它的主要操作就是单链表的插入操作,并且这链表是以执行时间 when 作为顺序的。需要重提的是,这时候的 when 已经转换成了距离开机到执行的毫秒数。

    1. 校验。是检测target是否存在,因为Message. targe 是用来处理这个 Message 的,所以一定要有target,其次判断当前 Message 是否正在被使用,然后验证当前 MessageQueue 是不是已经被 quit 了(mQuitting),验证通过过后就是正式的插入操作。
    2. 配置。设置 Message 该有的属性,msg.markInUse(); msg.when = when;
    3. 这个方法比较巧妙,将几种具体情况用一份代码解决了,但是都可以归结为:在头结点之前插入结点。看到这里应该注意到,如果插入的是需要非延时 Message,并且线程阻塞了,就会调用 nativeWake(mPtr) 唤醒线程。
    • 当还没有链表的时候(p == 0)
    • 消息的执行时间 比 里面的消息还要早(when == 0 || when < p.when)
    1. 在链表中间或最后插入。循环遍历链表,拿到链表合适的 Message,然后再将新 Message 插入。由于还没有到消息的处理时间,就不会唤醒线程。此插入过程如下:

    使用 Handler 来 post()

    先上一个使用的例子:

    private Handler mHandler;//全局变量
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mHandler = new Handler();
        new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mTestTV.setText("This is post");//更新UI
                                }
                            });
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
    }
    

    例子很简单,就不讲解了,接下来就来看一下 Handler 怎么 post 的吧:

        /**
         * Causes the Runnable r to be added to the message queue.
         * The runnable will be run on the thread to which this handler is 
         * attached. 
         *  
         * @param r The Runnable that will be executed.
         * 
         * @return Returns true if the Runnable was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.
         */
        public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    
        private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();  // 得到一个新的 Message
            m.callback = r;  // 是使用 post 的 Runnable 才会保存有这个参数
            return m;
        }
    

    眼尖的童鞋估计看到就要easy了。在 post 一开始就调用了 sendMessageDelayed(Message msg, long delayMillis),是不是很眼熟,对的,前面sendMessage(Message msg)一开始也是调用这个方法。这就是非延时消息的插入。后面具体如何插入请参看上一条。

    同样的,如果是使用 postDelayed(Runnable r, long delayMillis) 呢?使用例子请参看 面试题:Handler机制,Handler除了线程通信还有什么作用 中的作用二的第二种实现方式。转回来看 Handler 的 postDelayed :

        public final boolean postDelayed(Runnable r, long delayMillis)
        {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    

    原来啊,大家都用的一套,还是调用的 sendMessageDelayed 方法,只不过延迟时延不再为默认的 0. 这时候就是延迟消息的插入。

    工作原理时间图.jpg

    退出消息循环

    退出消息循环有两种方式,分别是Looper 的 quit()quitSafely()

       public void quit() {
            mQueue.quit(false);
        }
    
        public void quitSafely() {
            mQueue.quit(true);
        }
    

    这还是很简单,直接调用了 MassageQueue 的 qiut(boolean safe) :

        void quit(boolean safe) {
            if (!mQuitAllowed) {  // 主线程设置不允许退出,其他子线程默认设置的true
               throw new IllegalStateException("Main thread not allowed to quit.");
            }
    
            synchronized (this) {
                if (mQuitting) {  // 默认为 false,表示是否正在退出消息队列
                    return;
                }
                mQuitting = true;  // 全局唯一一次赋值,表示正在退出消息队列
    
                if (safe) {
                    removeAllFutureMessagesLocked();
                } else {
                    removeAllMessagesLocked();
                }
    
                // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    首先判断是否是主线程,因为主线程的消息队列不允许退出,然后判断当前线程是否正在退出。值得注意的是,mQuitting 是 MassageQueue 的成员变量,是拥有默认值的,默认值是 false。如果不是正在退出消息队列,则将其标志为 正在退出,注意,全局就只有这里修改了 mQuitting 的值。然后判断根据要求是否安全移除 MessageQueue。

        private void removeAllMessagesLocked() {
            Message p = mMessages;  // 头结点
            while (p != null) {
                Message n = p.next;     // 下一个结点
                p.recycleUnchecked();  // 前面讲过,就是将数据抹去,放入回收的那个链表
                p = n;       
            }
            mMessages = null;   // 将MessageQueue 中保存的头结点设置为 null
        }
    
        private void removeAllFutureMessagesLocked() {
            final long now = SystemClock.uptimeMillis();  //  从开机到现在的毫秒数(手机睡眠的时间不包括在内); 
            Message p = mMessages;   // 头结点
            if (p != null) {     
                // 如果头结点设置的延迟时间 > 大于当前时间
                // 注意  这里比较的时候都是用的从开机到现在的毫秒数
                // 这里意味着还没有到设置的执行时间
                if (p.when > now) {    
                    removeAllMessagesLocked();  // 直接移除全部还未到执行时间的 Message
                } else {
                    Message n;
                    for (;;) {
                        n = p.next;  // 拿到下一个节点
                        if (n == null) {  // 结束标志:没有下一个
                            return;   
                        }
                        if (n.when > now) {  // 还没有到设置的执行时间,退出循环
                            break;
                        }
                        p = n;
                    }
                    // 这时候得到的节点 p 是没有到设置的执行时间的前一个节点
                    // n 是没有到设置的执行时间的节点
                    p.next = null;
                    do {
                        p = n;
                        n = p.next;
                        p.recycleUnchecked();
                    } while (n != null);
                }
            }
        }
    

    removeAllMessagesLocked()直接将MessageQueue 中的 Message 全部回收掉。无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息(delayMillis == 0)。

    removeAllFutureMessagesLocked() 方法呢,就是只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,在这个方法中,表现为直接return ,然后因为队列有Message,所以相应的 dispatchMessage(msg) 会调用。

    quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

    面试题

    handler发消息给子线程,looper怎么启动?

    发消息就是把消息塞进去消息队列,looper在应用起来的时候已经就启动了,一直在轮询取消息队列的消息。

    为什么在子线程中创建Handler会抛异常?

    首先看如下代码:

            new Thread(){
                Handler handler = null;
    
                @Override
                public void run() {
                    handler = new Handler();
                }
            }.start();
    

    前面说过,Looper 对象是 ThreadLocal 的,即每个线程都有自己的 Looper,这个 Looper 可以为空。但是,当你在子线程中创建 Handler 对象时,如果 Looper 为空,那就会抛出异常。源码解释一下:

    public class Handler {
           ...
       public Handler(Callback callback, boolean async) {
           ...
           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 = callback;
           mAsynchronous = async;
       }
      ...
    }
    

    从上述程序中,我们可以看到,当 mLooper 对象为空的时候,抛出了异常。这是因为该线程中的 Looper 对象还没有创建,因此 sThreadLocal.get() 会返回 null。Handler 的原理就是要与 MassageQueue 建立关联,并且将消息投递给MassageQueue,如果连 MassageQueue 都没有,那么 Handler 就没有存在的必要,而 MassageQueue 又被封装在 Looper 中,因此,创建 Handler 时 Looper 一定不能为空。解决方法:

         new Thread(){
                Handler handler = null;
                @Override
                public void run() {
                    Looper.prepare();   // 1. 为当前线程创建 Looper,并会绑定到 ThreadLocal 中
                    handler = new Handler();
                    Looper.loop();  // 2. 启动消息循环
                }
            }.start();
    
    

    在UI线程为什么可以直接使用呢,就是因为在 ActivityThread中默认帮你执行了 1,2步了的。

    Handler为什么loop是死循环。

    在android中如果主线程(UI线程)中进行耗时操作会引发ANR(Application Not Responding)异常,产生ANR的原因一般有两种:

    1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)

    2. 当前的事件正在处理,但没有及时完成

    比如onCreate()中进行了耗时操作,导致点击、触摸等不响应,就会产生ANR。为了避免ANR异常,android使用了Handler消息处理机制,让耗时操作在子线程运行,需要UI线程进行处理的操作给UI线程发送消息。

    我们知道Handler发消息给UI线程就可以处理消息,UI线程维护着一个Looper和一个消息队列,Looper不停的拿消息队列的消息去分发处理。到这里问题来了:如果这么做的话,UI线程岂不是要一直死循环轮询消息队列拿消息?死循环不是造成ANR吗?

    是的,确实是死循环,但是 ANR 还得另说。

    ActivityThread 的 main()

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        ...
        Looper.loop();
     
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    Looper.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
            ...
            msg.target.dispatchMessage(msg);
            ...
            msg.recycleUnchecked();
        }
    }
    

    Looper.loop()是在死循环处理消息,如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出,那才不正常了呢!?

    所以,ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

    那么问题重新问一遍:那为什么这个死循环不会造成ANR异常呢?

    其实原因是显然的,我们知道Android是由事件驱动的,Looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞消息。换言之,消息队列为空的时候会阻塞主线程,而处理消息的时候不可以阻塞,这时候的阻塞 5 s就会 ANR。

    也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。

    而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

    总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

    Handler 机制 很多细节需要关注:如线程如何建立和退出消息循环等等)

    这里就是让你回答 Handler 的工作原理。

    关于Handler,在任何地方new Handler 都是什么线程下?

    这个需要分类讨论:

    1. 像最开始那样呢,直接new 出来,不带Looper 参数,那么就在创建 Looper 的线程下。
        public Handler() {
            this(null, false);
        }
        public Handler(Callback callback) {
            this(callback, false);
        }
        public Handler(boolean async) {
            this(null, async);
        }
        public Handler(Callback callback, boolean async) {
            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 = callback;
            mAsynchronous = async;
        }
    
    
    1. 在任何地方 new 出指定线程的 Handler。例如主线程,看案例:
    new Thread(new Runnable() {
            @Override
            public void run() {
                    // 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
                    Handler handler = new Handler(Looper.getMainLooper());
            }
    }).start();
    

    前面讲过,UI线程调用的 prepare 函数不一样,多保存了UI线程的 Looper 到 Looper.sMainLooper 中的。其目的是在任何地方都可以实例化 UI 线程的 Handler。

    而这时候调用的构造方法是

        public Handler(Looper looper) {
            this(looper, null, false);
        }
        public Handler(Looper looper, Callback callback) {
            this(looper, callback, false);
        }
    
        public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    其实就是与传入的 Looper绑定,换言之,如果我传入的是其他线程的Looper,我同样也可以实例化其他线程的 Handler。

    请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

    讲到Handler,肯定离不开Looper、MessageQueue、Message这三者和Handler之间的关系,下面简略地带过,详细自己可以参看上面源码

    1. Handler
      将要执行的Message或者Runnable到消息队列。

    2. Looper
      每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

    那么就会有人问,为什么我们使用Handler的时候从来都不需要创建Looper呢?这是因为在主线程中,ActivityThread默认会把Looper初始化好,prepare以后,当前线程就会变成一个Looper线程。

    1. MessageQueue
      MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。

    2. Message
      消息对象,就是MessageQueue里面存放的对象,一个MessageQueu可以包括多个Message。当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

    Handler机制,Handler除了线程通信还有什么作用

    Handler的主要用途 :

    • 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
    • 在子线程把需要在另一个线程执行的操作加入到消息队列中去。

    1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列

    实例:通过Handler配合Message或者Runnable实现倒计时


    • 方法一,通过Handler + Message的方式实现倒计时。代码如下:
    public class MainActivity extends AppCompatActivity {
        private ActivityMainBinding mBinding;
    
        private Handler mHandler ;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
            //设置监听事件
            mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //通过Handler + Message的方式实现倒计时
                    for (int i = 1; i <= 10; i++) {
                        Message message = Message.obtain(mHandler);
                        message.what = 10 - i;
                        mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
                    }
                }
            });
    
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息
                }
            };
        }
    }
    

    这里用到了DataBiding,可能没用过的同学看起来有点奇怪,但其实反而简略了代码,有一定基础的同学看起来都不会有太大压力。通过这个小程序,笔者希望大家可以了解到Handler的一个作用就是,在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。

    • 方法二,通过Handler + Runnable的方式实现倒计时。代码如下:
    public class MainActivity extends AppCompatActivity {
        private ActivityMainBinding mBinding;
        private Handler mHandler = new Handler();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
            //设置监听事件
            mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    for (int i = 1; i <= 10; i++) {
                        final int fadedSecond = i;
                        //每延迟一秒,发送一个Runnable对象
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mBinding.time.setText((10 - fadedSecond) + "");
                            }
                        }, 1000 * i);
                    }
                }
            });
        }
    }
    

    方法二也是通过代码让大家加深Handler处理有序事件的用途,之所以分开Runnable和Message两种方法来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。

    其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,即将Runnable 放到的Message 中的 mCallback 存储起来,值得注意的是,如果直接将Message 加入MessageQueue的话,那么mCallback将为null,所以最终我们都是通过Handler推送了一个Message罢了,至于为什么会分开两种方法,只是为了更方便开发者根据不同需要进行调用。下面再来看看Handler的第二个主要用途。

    2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去

    实例:通过Handler + Message来实现子线程加载图片,在UI线程显示图片

    效果图如下



    代码如下(布局代码也不放出来了)

    public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
        private ActivityThreadBinding mBinding = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
            // 设置点击事件
            mBinding.clickBtn.setOnClickListener(this);
            mBinding.resetBtn.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                // 响应load按钮
                case R.id.clickBtn:
                    // 开启一个线程
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            // 在Runnable中进行网络读取操作,返回bitmap
                            final Bitmap bitmap = loadPicFromInternet();
                            // 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
                            Handler handler = new Handler(Looper.getMainLooper());
                            // 通过Handler的post Runnable到UI线程的MessageQueue中去即可
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    // 在MessageQueue出队该Runnable时进行的操作
                                    mBinding.photo.setImageBitmap(bitmap);
                                }
                            });
                        }
                    }).start();
                    break;
                case R.id.resetBtn:
                    mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
                    break;
            }
        }
    
        /***
         * HttpUrlConnection加载图片,很简单
         * @return
         */
        public Bitmap loadPicFromInternet() {
            Bitmap bitmap = null;
            int respondCode = 0;
            InputStream is = null;
            try {
                URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10 * 1000);
                connection.setReadTimeout(5 * 1000);
                connection.connect();
                respondCode = connection.getResponseCode();
                if (respondCode == 200) {
                    is = connection.getInputStream();
                    bitmap = BitmapFactory.decodeStream(is);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
                Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return bitmap;
        }
    }
    

    很简单,其实最主要的就是当我们需要在主线程执行一些操作的时候,就可以直接使用这种方式,这种方式有着直接发送 Message 不可实现的先天优势。

    handler机制组成,handler机制每一部分的源码包括looper中的loop方法、threadlocal概念、dispatchmessage方法源码,runnable封装message等

    上面讲解的还记得住么?

    请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

    1. Android的单线程模型

    当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。

    在开发Android 应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

    如果在非UI线程中直接操作UI线程,会抛出异常,这与普通的java程序不同。因为 ViewRootImpl 对 UI 操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread 方法来完成的:

        void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    

    由于UI线程负责事件的监听和绘图,因此,必须保证UI线程能够随时响应用户的需求,UI线程里的操作应该向中断事件那样短小,费时的操作(如网络连接)需要另开线程,否则,如果UI线程超过5s没有响应用户请求,会弹出对话框提醒用户终止应用程序。顺便说一下 ANR 默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

    如果在新开的线程那为什么系统不对 UI 控件的访问加上锁机制呢?缺点有两个:首先加上锁机制会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只需要通过 Handler 切换一下 UI 访问的执行线程就好。
    中需要对UI进行设定,就可能违反单线程模型,因此android采用一种复杂的Message Queue机制保证线程间通信。

    1. 后面就是分析的那一套了。Message Queue 、 Handler 、 Looper

    Handler、Thread和HandlerThread的差别

    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
    
        public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
        
        /**
         * Constructs a HandlerThread.
         * @param name
         * @param priority The priority to run the thread at. The value supplied must be from 
         * {@link android.os.Process} and not from java.lang.Thread.
         */
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }
        
        /**
         * Call back method that can be explicitly overridden if needed to execute some
         * setup before Looper loops.
         */
        protected void onLooperPrepared() {
        }
    
        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
        
        /**
         * This method returns the Looper associated with this thread. If this thread not been started
         * or for any reason is isAlive() returns false, this method will return null. If this thread 
         * has been started, this method will block until the looper has been initialized.  
         * @return The looper.
         */
        public Looper getLooper() {
            if (!isAlive()) {
                return null;
            }
            
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    
        /**
         * Quits the handler thread's looper.
         * <p>
         * Causes the handler thread's looper to terminate without processing any
         * more messages in the message queue.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p class="note">
         * Using this method may be unsafe because some messages may not be delivered
         * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
         * that all pending work is completed in an orderly manner.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         *
         * @see #quitSafely
         */
        public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
        }
    
        /**
         * Quits the handler thread's looper safely.
         * <p>
         * Causes the handler thread's looper to terminate as soon as all remaining messages
         * in the message queue that are already due to be delivered have been handled.
         * Pending delayed messages with due times in the future will not be delivered.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p>
         * If the thread has not been started or has finished (that is if
         * {@link #getLooper} returns null), then false is returned.
         * Otherwise the looper is asked to quit and true is returned.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         */
        public boolean quitSafely() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quitSafely();
                return true;
            }
            return false;
        }
    
    
        public int getThreadId() {
            return mTid;
        }
    }
    

    由于 HandlerThread 实在是代码少得变态,我就直接将全部源码贴上来了。

    直接看一下run() 方法可能就明白了,就是将 Looper 启动起来,就等于主线程一样,可以直接使用 Handler 了,没必要 Looper.prepare() 再 Looper.loop() 了。需要知道这根本不是 handler ,而是封装了 Looper 的 Thread,方便了子线程与子线程通信。

    还有一点
    Handler: 它的消息处理方式是阻塞式的,必须一条一条的处理。耗时操作 不应该用handler处理。

    HandlerThread:继承自Thread,它有个Looper,在这里可以执行耗时操作

    什么是 IdleHandler?有什么用?怎么用

    https://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg
    这里还没写,努力更新中...

    Handler消息机制,postDelayed会造成线程阻塞吗?对内存有什么影响?

    1. Handler消息机制
    2. postDelayed会造成线程阻塞吗:

    还有印象么,上面讲解的 postDelayed。postDelayed只是在 post 的时候加了延时,最后这个延时讲被转换成执行时间存在每一个 Message 中。而在 loop() 中调用 next() 的死循环是阻塞式的,只有在下个消息到达或者有事务发生(设置的nextPollTimeoutMillis到了)时,才通过往pipe管道写端写入数据来唤醒线程工作。

    也就是说如果当前消息队列中消息全部为 延时Message(全部没到执行时间),而这个 Message 的执行时间又比MessageQueue 中所有消息执行时间早,那么在loop循环取next 的时候就会因为最早这一个Message(刚刚postDelayed的Message)还没到执行时间而阻塞。

    1. 对内存有什么影响


      ThreadLocal内存解释

      Looper 是通过 ThreadLocal 存储在线程中的,而MessageQueue 是封装在 Looper 中的。

    参考文章:

    Android 官方文档 :Handler
    MessageQueue
    Looper
    《 Android 开发艺术探索 》
    《 Android 开发进阶 从小工到专家 》
    深入理解Android中的Handler机制
    一步一步分析Android的Handler机制
    【从源码看Android】03Android MessageQueue消息循环处理机制(epoll实现)

    相关文章

      网友评论

        本文标题:Android 的消息机制

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