Android

Android 消息处理机制

2019-03-09  本文已影响0人  帅气ddd昵称

Android 消息处理机制

Android中常说的消息处理机制, 其实就是android下线程间的通信机制, 也就是Handler的工作机制.

Handler, 我想对于每一个有android开发经验的人来说都不会陌生, 经常使用到.

比如, 我们要在子线程中更新界面UI, 会通过handler.sendMessage()发送一个消息到主线程; 然后在handler的回调方法handleMessage()中去处理这个消息.

我们都知道, android中更新UI只能在主线程进行, 那么, 不知道你有没有想过, 为什么在子线程中可以通过handler发送消息给主线程, 来通知主线程更新UI呢?他们是怎么通信的呢?是不是只有主线程才能用Handler的消息机制呢?

一, android消息机制主要由Handler, Message, MessageQueue, Looper几大角色组成:
以前网络收藏的图

Handler: 负责发送和处理消息

Message: 消息的载体, 封装了what, arg, target等相关的信息.

MessageQueue: 消息队列, 存放由Handler发送过来待处理的Message.

Lopper: 初始化Looper的同时会创建一个MessageQueue, Looper启动后会一直不断的从MessageQueue中取出消息Message交给对应的Handler来处理

二, 我们以mainThread为例, 来分析下Handler机制的工作流程
1. Looper初始化

系统启动时, 在ActivityThread.java的main()中就已经初始化好了, 然后通过Looper.loop()来开启looper

public static void main(String[] args) {
        //...
        //创建Looper
        Looper.prepareMainLooper();
        // ...
        //启动Looper
        Looper.loop();
}

接下来先看下Looper.java中创建looper的代码

   /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //如果looper已存在, 抛异常,,保证looper的唯一
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

由以上代码可以看出, 在prepare(boolean quitAllowed) 中我们new了一个Looper对象, 然后保存在sThreadLocal中, 然后通过myLooper()来创建sMainLooper, 即我们的主线程Looper.

代码逻辑很简单, 主要是这个sThreadLocal是什么东东?
sThreadLocal 是 ThreadLocal的一个实例. ThreadLocal又是神马, 不是很懂, 只知道是用来提供thread-local的, 提供了get, set方法. set会保存looper和当前线程的一些信息, get返回looper, 看这个类的注释了解下

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {  
    ...
    public void set(T value) {
        Thread t = Thread.currentThread();
        //ThreadLocalMap is a customized hash map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        //ThreadLocalMap is a customized hash map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    ...
 }

顺便, 我们看下new Looper()做了什么

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

灰常简单, 就2行代码, 一个是初始化我们的MessageQueue, 然后就是获取到当前的线程mThread .
注意下这里的private构造方法, 即looper是单实例的, 也就是说, 只能通过Looper.prepare()来创建looper对象, 回过去看下prepare()的代码, 在创建looper前会先判断下looper是否存在, 如果是已经存在了会直接抛异常的, 这样就保证了一个线程只能有一个looper, 同时, 也就只能有一个MessageQueue;

2. 启动looper

嗯嗯,...sMainLooper创建好后, 回到ActivityThread.main(), 通过Looper.loop()启动looper.

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //拿到当前线程的looper
        final Looper me = myLooper();
        //looper为空就抛异常, 这就是为什么普通线程不能用Handler的原因了
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
       //拿到looper的MessageQueue
        final MessageQueue queue = me.mQueue;

        //...other code

        //无限for循环
        for (;;) {
            //拿到MessageQueue中的Message对象, 如果有, 主要这里的might block, 即这个方法会阻塞线程
            Message msg = queue.next(); // might block
          
           //...
            try {
                //dispatchMessage分发处理Message
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            //...other code

            //Message回收
            msg.recycleUnchecked();
         }
    }

看loop()的核心代码, 逻辑也很简单, 就是拿到当前线程的Looper, 拿到Looper的MessageQueue, 然后一个无限for循环, 不断的从消息队列里取出消息, 然后msg.target.dispatchMessage(msg), 来处理消息.

我们重点分析下MessageQueue是怎么拿到消息的:

Message msg = queue.next(); // might block

就是MessageQueue.next()了

    Message next() {
        // ...other code
        int nextPollTimeoutMillis = 0;//等待执行的时间
        for (;;) {
            //消息需要延时执行, e.g: postDelayed, sendMessageDelayed这些操作就会有延时
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //这里, 如果没有消息的话/时间还没到, 会堵塞线程, 直到有消息/时间到了的时候, 才会被唤醒返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //找到一个不是异步而且不为空的message.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //时间还没到, 计算出延时时间, 这里之后会进入下一个循环, 然后在nativePollOnce()中block住
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 以下..顺利取到一个消息, 然后返回这个消息
                        mBlocked = false;
                        //链表操作
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages. 没有消息了
                    nextPollTimeoutMillis = -1;
                }

                // ...
            }

            // ...
        }
    }

looper的启动流程大概就到这里了, 这里有几个问题有注意下:
 1. 调用myLooper()后, 会先判断下looper是否为空, 如果空会直接抛异常, 这就是为什么普通线程不能用Handler的原因了, 所有线程如果要有消息处理能力, 必须先Looper.prepare()来创建looper对象;
 2. 分发消息msg.target.dispatchMessage(msg), 这里的target其实就是关联的handler对象, 是在sendMessage()的时候关联的, 这样能够使Looper在分发消息的时候是不用去区分Handler对象的;
 3. queue.next()这里是会阻塞线程的, 而且这里如果是主线程的话, 为什么不会ANR呢? 这个问题要先理清楚产生anr的原因, 并不是有阻塞就会anr, anr是因为主线程的消息来不及处理而导致的. 比如, 我们在启动一个activity的时候再主线程sleep个10s, 这样不一定会anr的, 只有当主线程阻塞的时候, 有新的消息要处理又得不到处理, 比如我们按下手机返回键, 这时就会anr了.

3. obtain Message

获取一个消息对象, 一般使用obtain, 当然new也可以.

    /**
     * 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;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

private static Message sPool;

这里的sPool是一个静态Message对象, 不为空的时候, 就直接返回这个message, 否则new一个message, 这样可以减少对象的创建. 那么这个sPool是那来的呢? 就是在Looper分发消息后, 可以回过去看一下loop()方法for循环的最后一行代码, 就是对message的回收操作.

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) {
            //将当前message缓存起来, MAX_POOL_SIZE = 50
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

回收前, 先把当前对象的以下状态, 参数等清除, 然后把当前对象保存在sPool. 刚才说了, 这个sPool是个静态变量, 所有是会一直存在的. 这里Message其实也维护了一个链表这种数据结构的, 看下在对sPool赋值前会把之前的sPool赋值给next, 就是让next指向之前回收的Message对象. 当obtain获取一个Message时, 获取到的是表头的Message, 然后又让sPool指向表头的next, 即下一个Message对象, 这样每次obtain时都能获取到表头的一个Message对象.
惊不惊喜, 意不意外? 以前没看源码前, 一直以为这边是用对象池的方式来缓存Message对象的.

4. Handler发送和消息的分配

Handler要发送消息, 要先获取一个Handler对象, 一般通过以下两种方式

  1. handler = new Handler()
  2. handler = Handler(Looper looper)

需要一个Looper对象才能创建Handler, 第一种方法其实默认获取当前线程的Looper的

public Handler(Callback callback, boolean async) {
       ...
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread " + Thread.currentThread()
                       + " that has not called Looper.prepare()");
       }
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
   }

Looper.myLooper()获取的就是当前线程的Looper. 看下那个异常, 当looper为空的时候抛异常的, 这就是为什么不能再子线程用handler的原因了.

Looper在启动后, 会一直在死循环中, 等待消息进入消息队列.
那么, 消息是怎么进入消息队列的呢? 就是我们经常用的handler的一些方法了, 比如

sendMessage, sendMessageDelayed, post, postDelayed...
这些方法最终都会执行到sendMessageAtTime()这里

  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //关联handler
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

然后就到MessageQueue.enqueueMessage()加入消息队列了

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            //消息执行的时间,队列是按照消息执行时间排序的
            //when = SystemClock.uptimeMillis() + delayMillis
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //列表里没有消息 / 消息的执行时间== 0 / 要入队列的消息执行时间 < 表头消息的时间
            //把这个消息当做表头, next指向之前的那个消息(mMessages)
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //需要唤醒线程, 因为在不用处理消息的时候线程是block的, 前面分析过了
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    //从表头开始遍历消息, 根据执行时间找出前面(要先执行)的那个消息prev
                    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;
            }

            // 唤醒线程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

代码注释都有, 不难理解.
接下来分配消息, 看下Handler.dispatchMessage(msg)这部分代码:

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

这部分代码逻辑很简单, 大概解释下: handler.post()时msg.callback != null, 会调用Runnable.run();
如果是通过Handler(Callback callback)实例化handler的话, mCallback != null; 否则直接调用 handleMessage(msg), 这个是空方法, 就是我们初始化Handler时重写的那个handleMessage()方法.

三, 利用Handler机制来实现子线程间的通信

根据主线程的消息处理机制的原理, 我们完全可以在子线程中来实现我们自己的消息处理机制.
大概代码是这样的:

class MyThread extends Thread {
 
        @Override
        public void run() {
            super.run();
            //prepare looper
            Looper.prepare();

            //handler
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    
                }
            };
 
            ...

            //start looper
            Looper.loop();
        }
    }

这样, 只要在其他子线程能拿到MyThread的Handler或是Looper(创建Handler)就能向MyThread发送消息了, MyThread的looper会自行处理这些消息.




ps: 这几天在重新理了下Handler机制这块, 写下来是为了加深印象. 第一次写, 如有不对的地方, 请帮忙指正,感谢!感谢!!

上一篇 下一篇

猜你喜欢

热点阅读