研究可以Android的消息机制,觉得还是自己亲自来记录下来才印象深刻

简单认识

Android的消息机制最多的用处就是在子线程中去更新UI,四个主要成员:Looper、Handler、Message和MessageQueue
下面自己一一写出自己的见解:

Loop

先抛开loop的作用,我们看看loop的初始化。每个线程里只能有一个loop,而且每个loop只能初始化一次!
下面我来证实这个:

Image_01.png

可以看到我在主线程开启looper初始化时就会抛异常,看看looper源码!

Image_02.png
说的很清楚,如果looper已经初始化后,再初始化的时候就会报错 Only one Looper may be created per thread,
因为主线程已经初始化过looper,我们看看ActivityThread里的源码:

Image_03.png

初始化调用了Looper的prepareMainLooper(),跟进去看:

Image_03.png

这里有疑问系统是怎么区分主线程的loop和子线程的loop呢,这就要去看Loop源码里的loop存放了,

Image_04.png

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。
从loop源码可知在创建loop时调用了:

Image_05.png

调用了ThreadLocal,看看ThreadLocal的set方法

Image_06.png Image_07.png

在上面的set方法中,首先会通过getMapt方法来获取当前线程中的ThreadLocal数据,如果获取呢?其实获取的方式也是很简单的,在Thread类的内容有一个成员专门用于存储线程的ThreadLocal的数据,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果ThreadLocalMap的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。

  private void set(ThreadLocal key, Object value) {            // We don't use a fast path as with get() because it is at            // least as common to use set() to create new entries as            // it is to replace existing ones, in which case, a fast            // path would fail more often than not.            Entry[] tab = table;            int len = tab.length;            int i = key.threadLocalHashCode & (len-1);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                ThreadLocal k = e.get();                if (k == key) {                    e.value = value;                    return;                }                if (k == null) {                    replaceStaleEntry(key, value, i);                    return;                }            }            tab[i] = new Entry(key, value);            int sz = ++size;            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

再来Loopr如何get()

Image_08.png

跟随进去看

Image_09.png

这样我们就可以在不同的线程里获取不同的loop了,这样loop也初始化了,也可以获取了;

Handler

Handler业务逻辑的三种方式,它们分别是重写handleMessage(Message msg)方法、实现Handler.Callback接口和实现Runnable接口。三种方式最终都是在Looper所在的线程中执行的,是我们执行异步操作的地方,比如更新主线程的UI
重写handleMessage(Message msg)方法,也是我们经常用的,
我们跟随

 handler.sendEmptyMessage(0);

进入到 handler的源码,找到核心代码:

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;        if (queue == null) {            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;        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }

当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。

Message

Message是消息载体,它的作用就是存储执行某一个任务所需要的数据和实现接口,该接口是Runnable、Handle.Callback或者Handler.handleMessage(Message msg)
Message的获取

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

该方法就是获取Message的关键,其他obtain()的重载方法最后调用的都是该方法,只是有传入参数进行初始化赋值或者进行浅拷贝而已。方法内部首先定义了一个同步块synchronized防止多线程操作时出现两个以上的线程同时申请同一个Message对象。在同步快内部进行的是Message的获取操作,如果消息池不为null(sPool != null),就从链表(消息池)中获取一个Message对象,并且将sPool指向下一个元素,同时链表的长度减一。如果消息池中没有任何可用的Message对象,就直接实例化一个新的Message对象。

MessageQueue

MessageQueue既消息队列,Handler将Message发送到消息队列中,消息队列会按照一定的规则取出要执行的Message。
添加消息

    boolean enqueueMessage(Message msg, long when) {        if (msg.target == null) {            throw new IllegalArgumentException("Message must have a target.");        }        if (msg.isInUse()) {            throw new IllegalStateException(msg + " This message is already in use.");        }        synchronized (this) {            if (mQuitting) {                IllegalStateException e = new IllegalStateException(                        msg.target + " sending message to a Handler on a dead thread");                Log.w(TAG, e.getMessage(), e);                msg.recycle();                return false;            }            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            // 根据when的比较来判断要添加的Message是否应该放在队列头部。            // 当第一次添加消息的时候,测试队列为空,所以该Message也应该            // 位于队列的头部。            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                // Inserted within the middle of the queue.  Usually we don't have to wake                // up the event queue unless there is a barrier at the head of the queue                // and the message is the earliest asynchronous message in the queue.                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;             // 不断遍历消息队列,根据when的比较找到适合插入Message的位置。                for (;;) {                    prev = p;                                  p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }            // We can assume mPtr != 0 because mQuitting is false.            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

将Message加入到消息队列中的操作也很简单,就是遍历消息队列中的所有消息,根据when的比较找到适合添加Message的位置。

四者关系

当然从源头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 (;;) {          //可能会阻塞,因为next()方法可能会无限循环            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }            // This must be in a local variable, in case a UI event sets the logger            final Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            final long traceTag = me.mTraceTag;            if (traceTag != 0) {                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));            }            try {                msg.target.dispatchMessage(msg);            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            if (logging != null) {                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);            }            // Make sure that during the course of dispatching the            // identity of the thread wasn't corrupted.            final long newIdent = Binder.clearCallingIdentity();            if (ident != newIdent) {                Log.wtf(TAG, "Thread identity changed from 0x"                        + Long.toHexString(ident) + " to 0x"                        + Long.toHexString(newIdent) + " while dispatching to "                        + msg.target.getClass().getName() + " "                        + msg.callback + " what=" + msg.what);            }            msg.recycleUnchecked();        }    }

跟随到MessageQueue中的next()的方法中去:

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

当loop得到消息后就会分发消息

 public static void loop() {    ...                msg.target.dispatchMessage(msg);   ...

因为Message发送时已经设置了tag,那就是发送者handler.然后在调用消息分发,利用原来的handler发送消息.

Image_10.png Image_11.png

我们自己实现了这个方法,可以操作更新UI

更多相关文章

  1. Android(安卓)点击通知栏消息打开activity,并判断app是否运行
  2. android 开机启动流程分析(16)Launcher启动
  3. android 通过子线程跳转activity并传递内容
  4. Android支持多并发的异步任务框架MultiAsynctask
  5. MTP 服务流程
  6. Android(安卓)surfaceview详解
  7. Android消息机制——ThreadLocal(Android艺术开发探索读书笔记)
  8. Android(安卓)2016年度知名企业公司面试题
  9. 开发必读:如何成为一名优秀的Android开发者

随机推荐

  1. 说说 Android(安卓)的常见 UI 控件
  2. (一)Android事件分发机制 - View篇
  3. Android中的Parcel机制 实现Bundle传递对
  4. Android系列之浅谈Android(安卓)3D旋转
  5. android - 为安全而设计 - 1 - 开发文档
  6. Cordova与Android之一 Activity跳转
  7. Flash Android(安卓)ANE打包之基本流程
  8. Android软件安全与逆向分析
  9. Android中 GridLayout 【网格布局】 全面
  10. Android(安卓)JNI开发入门之二