0
点赞
收藏
分享

微信扫一扫

Android 消息机制

Java旺 2021-09-29 阅读 44
Android

一、概述

说到Android消息机制大家并不陌生,我们每一个操作都是一个个消息。比如,打开某个app,这个过程都会产生非常多的消息,说个大家都知道的,暂停发起Activity--onPause,创建新的Activity--onCreate等。涉及类有Handler,Looper,Message,MessageQueue。

1.1 模型

  • Message :硬件产生的消息(触摸,按钮)和软件生成消息
  • MessageQueue:消息队列主要功能是在存消息和取消息
  • Handler:向消息队列发送消息和处理消息事件
  • Looper:不断循环执行,把循环得到消息分发给目标处理者

1.2架构图

  • Looper 有一个MessageQueue消息队列;
  • MessageQueue 有一组待处理的Message;
  • Message 中有一个用于处理消息的Handler;
  • Handler 中有Looper和MessageQueue。

1.3 常用方式

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();   
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO 定义消息处理逻辑.
            }
        };
        Looper.loop();  //
    }
}

1.4 举例

这几类之间的关系可以用如下的图来简单说明:



消息机制中,Thread是基本有了它才能有Looper和MessageQueue在上面运行,Handler能正常使用也是依靠前者两个类。他们整体关系犹如传送带机器,运作需要动力即线程,传送带需要转动起来需要Looper,传送带都是MessageQueue,货物都是Message,配备这些后我们才能把这个机体运作起来。

二、Looper

2.1 ThreadLocal

ThreadLocal:线程本地存储,每一个线程都有私有的存储空间,不同线程无法访问。
sThreadLocal:在不同线程里面用此引用(公共要钥匙)获取存储在某个线程储存区域的数据。

 public void set(T value) {
         //获取当前线程
        Thread t = Thread.currentThread();
        //查找当前线程的存储区域
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //存储区域为空需要创建此ThreadLocalMap对象,然后把value存入存储区域里
            createMap(t, value);
    }

以上代码是获取当前线程的存储区域,然后根据sThreadLocal为key存储数据。

  public T get() {
         //获取当前线程
        Thread t = Thread.currentThread();
        //查找当前线程的存储区域
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //获取当前存储数据
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T) e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //存储区域为空需要创建此ThreadLocalMap对象,然后把value存入存储区域里
            createMap(t, value);
        return value;
    }

以上代码是获取当前线程的存储区域,然后根据sThreadLocal为key存储数据。

2.2 prepare

现在我们开始分析prepare创建及其原理

    private static void prepare(boolean quitAllowed) {
        //在当前线程获取存在ThreadLocal的Looper对象,表明此方便只能调用一次,一个线程只能创建一个Looper对象,否则抛出异常。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

通过以上代码,我们可以看出prepare()一个线程只能调用一次,每个线程创建好了Looper对象都会存在线程存储区域里面。

 public static void prepareMainLooper() {
        //创建Looper且不能退出
        prepare(false);
        synchronized (Looper.class) {
            //保证主线程只有一个Looper
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

上面代码都是ActivityThread.mian() 方法为主线程创建Looper。

quitAllowed: 表示是否可以退出循环,通过上面代码可以分析出来,主线程创建的Looper循环是不允许退出的。

2.3 loop

我们来看下loop()方法的工作原理

public static void loop() {
        //获取当前线程的Looper对象
        final Looper me = myLooper();
        //子线程调用此方法,必现有Looper对象。
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取looper的消息队列
        final MessageQueue queue = me.mQueue;

        //清除远程端的uid和pid,使用本地进程的uid和pid代替
        Binder.clearCallingIdentity();
        //调用第二次目的,使用本地进程的uid和pid生成token 查看源码便能理解
        final long ident = Binder.clearCallingIdentity();

        for (; ; ) {
            //从消息队列获取消息 这个过程是阻塞的
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
            Printer logging = me.mLogging;  
            if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
            }
            //分发消息
            msg.target.dispatchMessage(msg);
            // 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);
            }
            //将Message放入消息池
            msg.recycleUnchecked();
        }
    }

通过上面源码我们可以知晓,loop是一个死循环,主要的工作是从消息队列获取消息,然后分发消息。我可以在looper设置debug日志输出功能

2.4 qiut()

退出消息循环有两个方法

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

上面代码唯一的差别都是调用参数,接下来我们看下MessageQueue.quit()

void quit(boolean safe) {
        //这都是之前提到的创建Looper时候,是否可以退出,主线程不允许退出循环
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            //防止二次调用
            if (mQuitting) {
                return;
            }
            mQuitting = true;
            if (safe) {
                //移出那些定时执行的消息
                removeAllFutureMessagesLocked();
            } else {
                //移出所有消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

quit与quitSafely主要差别在于,移除所有消息与移除尚未触发的消息对于正在触发的消息并不移除。

三、Handler

3.1构造函数

 public Handler() {
        this(null, false);
    }
    
    public Handler(@Nullable Handler.Callback callback, boolean async) {
        //匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
            }
        }
        //获取当前线程的looper,这在前面代码提及过,
        mLooper = Looper.myLooper();
        //在子线程使用handler必须先Looper. prepare()创建Looper对象
        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;//设置消息是否为异步处理方式
    }

从上面代码看出,初始化Handler我们必须做什么初始化,以及它持有那些引用。

3.2分发消息

public void dispatchMessage(@NonNull Message msg) {
        //如果callback不为空回调run
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //构造函数注册回调
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //子类或者内部内重写方法回调
            handleMessage(msg);
        }
    }

根据上面代码,我们可以看出,回调处理消息的优先级,首先是msg里面的callback,其次构造函数参数mCallback,最后才是重写方法handleMessage()回调。

3.3消息发送

    public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        //从消息池子拿空闲的消息
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        //SystemClock.uptimeMillis()表示系统开机到当前的时间总数,单位是毫秒,
        // 但是,当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时间就会停止,
        // 但是不会受到时钟缩放、空闲或者其他节能机制的影响。
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@android.annotation.NonNull 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(@android.annotation.NonNull MessageQueue queue, @android.annotation.NonNull Message msg,
                                   long uptimeMillis) {
        msg.target = this;//获得当前hanger引用,以后用来回调
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        //异步消息
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //uptimeMillis 触发截止时间
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    //把消息添加到队列最前面
    public final boolean sendMessageAtFrontOfQueue(@android.annotation.NonNull Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //时间为0
        return enqueueMessage(queue, msg, 0);
    }

四、MessageQueue

MessageQueue是消息机制的java层和C++层连接纽带,大部分核心都交给native层来处理

   private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    @UnsupportedAppUsage
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

4.1初始化

 MessageQueue(boolean quitAllowed) {
        //是否允许退出循环
        mQuitAllowed = quitAllowed;
        //通过native方法初始化消息队列,其中mPtr是供native代码使用。mPtr记录native消息队列的信息
        mPtr = nativeInit();
    }

4.2 插入消息

 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;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //消息插入最前面,新的头
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    //如果是第一次循环 p队列第一条消息
                    prev = p;
                    p = p.next;
                    //找到延迟刚好大于p.when 消息,如果时间相同插入到相同时间后面,整个单链表都是升序
                    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;
    }

插入消息的规则,消息队列是一个单链表,最近执行的消息在最前面,延迟时间相同的消息插入在相同时间的后面。
nativeWake(mPtr):mPtr这个是记录native消息队列的信息(在native也有一个消息队列,通过mPtr成为了上层与下层的连接纽带)。调用此方法都是往pipe管道写端写数据。

4.3 提取消息

    @UnsupportedAppUsage
    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();
            }
            //阻塞操作,等待nextPollTimeoutMillis时长,等待回调
            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.
                // 退出消息,告知native loop结束循环
                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.
                //当消息队列没有消息或者有延迟执行消息时候,执行空闲的IdleHandler
                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;
        }
    }

nativePollOnce():触发回调,时间过期或者有新的消息(有新的消息触发调用nativeWake)。调用此方法释放CPU资源进入休眠状态,当有新的消息来得时候调用nativeWak()方法,往pipe管道写端写入数据来唤醒,从而解除阻塞,这个采用了epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,某个描述符就绪,则立刻通知相应的应用程序进行读或写操作,本质都是IO同步,读写是阻塞的(理解:当你写完了,都通知我读)。所以说线程大多数时间都是出于休眠状态,并不会消耗大量的cpu资源。
IdleHanlder:当前没有消息或者有延迟执行消息时候,在执行IdleHandler,空闲执行完了执行阻塞操作。
消息处理流程是先处理Native Message,再处理Native Request,最后处理Java Message。理解了该流程,也就明白有时上层消息很少,但响应时间却较长的真正原因。

五、Message

静态变量sPool的数据类型为Message,通过next成员变量,维护一个消息池;静态变量MAX_POOL_SIZE代表消息池的可用大小;消息池的默认大小为50。
消息池常用的操作方法是obtain()和recycle()。

5.1 obtain

从消息池中获取消息

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null; //从sPool中取出一个Message对象,并消息链表断开
            m.flags = 0; // 清除in-use flag
            sPoolSize--; //消息池的可用大小进行减1操作
            return m;
        }
    }
    return new Message(); // 当消息池为空时,直接创建Message对象
}

obtain(),从消息池取Message,都是把消息池表头的Message取走,再把表头指向next;

5.2 recycle

把不再使用的消息加入消息池

把不再使用的消息加入消息池

public void recycle() {
    if (isInUse()) { //判断消息是否正在使用
        if (gCheckRecycle) { //Android 5.0以后的版本默认为true,之前的版本默认为false.
            throw new IllegalStateException("This message cannot be recycled because it is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

//对于不再使用的消息,加入到消息池
void recycleUnchecked() {
    //将消息标示位置为IN_USE,并清空消息所有的参数。
    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) { //当消息池没有满时,将Message对象加入消息池
            next = sPool;
            sPool = this;
            sPoolSize++; //消息池的可用大小进行加1操作
        }
    }
}

recycle(),将Message加入到消息池的过程,都是把Message加到链表的表头。

感谢:
Android消息机制
深入源码解析

举报

相关推荐

0 条评论