Android 的消息机制 - Handler

为了更好的理解 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实现)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 1. ANR异常 Application No Response:应用程序无响应。在主线程中,是不允许执行耗时的操...
    JackChen1024阅读 1,299评论 0 3
  • 【Android Handler 消息机制】 前言 在Android开发中,我们都知道不能在主线程中执行耗时的任务...
    Rtia阅读 4,699评论 1 28
  • 前言 Handler是Android消息机制的上层接口,平时使用起来很方便,我们可以通过它把一个任务切换到Hand...
    eagleRock阅读 1,622评论 0 13
  • 前言: 提到Android消息机制大家应该都不陌生,在日常开发中都不可避免地要涉及到这方面的内容。从开发的角度来说...
    JiaYang627阅读 340评论 0 1
  • 01 如果你知道你的未来是什么样的,你还有勇气去面对吗?《无问西东》《我在未来等你》 02 今天在看书的时...
    嘻哈__阅读 118评论 0 0