Android消息机制(二):Message和MessageQueue

Message

消息结构

每个消息用Message表示,Message主要包含以下内容:

filed 含义 说明
what 消息类别 由用户定义,用来区分不同的消息
arg1 参数1 是一种轻量级的传递数据的方式
arg2 参数2 是一种轻量级的传递数据的方式
obj 消息内容 任意对象,但是使用Messenger跨进程传递Message时不能为null
data Bundle数据 比较复杂的数据建议使用该变量(相比上面几个,这个县的比较重量级)
target 消息响应方 关联的Handler对象,处理Message时会调用它分发处理Message对象
when 触发响应时间 处理消息时间
next Message队列里的下一个Message对象 用next指向下一条Message,实现一个链表数据结构,用户一般使用不到该字段。

这里的用户指一般的APP开发者。

一般不用手动设置target,调用Handler.obtainMessage()方法会自动的设置Message的target为当前的Handler。
得到Message之后可以调用sendToTarget(),发送消息给Handler,Handler再把消息放到message queue的尾部。
对Message除了给部分成员变量赋值外的操作都可以交由Handler来处理。

消息池

在通过Handler发送消息时,我们可以通过代码Message message=new Message();新建一条消息,但是我们并不推荐这样做,因为这样每次都会新建一条消息,很容易造成资源浪费。Android中设计了消息池用于避免该现象:

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

从消息池取Message,都是把消息池表头的Message取走,再把表头指向下一条消息next;

  • 回收消息 recycle()
    把不再使用的消息回收到消息池 mgs.recycle();
    recycle()方法源码:
public void recycle() {
        if (isInUse()) {//判断消息是否正在使用
            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.
        //将消息标示位置为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操作
            }
        }
    }

消息回收,就是将Message内容重置后,再把Message加到链表的表头,加入到消息池的过程;

MessageQueue

负责管理消息队列,实际上Message类有一个next字段,会将Message对象串在一起成为一个消息队列,所以并不需要LinkedList之类的数据结构将Message对象组在一起成为队列。

  • 创建消息队列
    MessageQueue(boolean quitAllowed) {
       mQuitAllowed = quitAllowed;
       mPtr = nativeInit();//通过native方法初始化消息队列,其中mPtr是供native代码使用
   }

在MessageQueue初始化的时候调用了nativeInit,这是一个Native方法:

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) { 
   NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); 
   if (!nativeMessageQueue) { 
       jniThrowRuntimeException(env, "Unable to allocate native queue"); 
       return;
       }
   nativeMessageQueue->incStrong(env); 
   android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
} 
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
       NativeMessageQueue* nativeMessageQueue) { 
   env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr, 
   reinterpret_cast<jint>(nativeMessageQueue)); 
 }

方法名由java层类的包名+类名+方法名组成,这不是标准,是习惯写法,也可以采用其他名称组合,具体是什么名称由JNINativeMethod方法中Java对象与c++对象的映射决定,此处是JNI方面的内容,不作过多解释。
在nativeInit中,new了一个Native层的MessageQueue的对象,并将其地址保存在了Java层MessageQueue的成员mPtr中,Android中有好多这样的实现,一个类在Java层与Native层都有实现,通过JNI的GetFieldID与SetIntField把Native层的类的实例地址保存到Java层类的实例的mPtr成员中,比如Parcel。

再看NativeMessageQueue的实现:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) { 
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
      mLooper = new Looper(false);
      Looper::setForThread(mLooper); 
    }
}
  • 消息入队 enqueueMessage()
    enqueueMessage 用于将Message对象插入消息队列。MessageQueue永远是按照Message触发的时间先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。
    该方法会被Handler对象调用。
    源码如下:
   /**
     * 添加一条消息到消息队列
     * @param msg 要添加的消息
     * @param when 消息处理时间
     * @return 添加成功与否
     */
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {// 每一个Message必须有一个target
            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.
                //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
                //消息队头存在barrier,并且同时Message是队列中最早的异步消息。

                // 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 (;;) {
                    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.
            //消息没有退出,我们认为此时mPtr != 0
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • 消息轮询 next()
    最重要的方法,用于获取下一个Message对象,如果没有需要处理的Message对象,该方法将阻塞。MessageQueue用本地方法做同步互斥,因为这样时间更精准。每个Message对象都有一个什么时刻处理该Message对象的属性when,没到时间都不会处理该Message对象,如果时间不精准的话,会导致系统消息不能及时处理。
   /**
     * 依次从MessageQueue中取出Message
     * @return 消息
     */
    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  循环迭代的首次为-1
        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) {
                    //查询MessageQueue中的下一条异步消息
                    // 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();//设置消息flag成使用状态
                        return msg;//成功地获取MessageQueue中的下一条即将要执行的消息
                    }
                } else {
                    // No more messages.//没有消息了
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {//消息正在退出,返回null
                    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) {
                    //没有idle handlers 需要运行,则循环并等待。
                    // 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.
            //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler//去掉handler的引用

                boolean keep = false;
                try {
                    keep = idler.queueIdle();//idle时执行的方法
                } 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.
            //重置idle handler个数为0,以保证不会再次重复运行
            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.
            //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
            nextPollTimeoutMillis = 0;
        }
    }

nativePollOnce(ptr, nextPollTimeoutMillis)是一个native方法,是一个阻塞操作。其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长;当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。空闲后,往往会执行IdleHandler中的方法。当nativePollOnce()返回后,next()从mMessages中提取一个消息。nativePollOnce()在native做了大量的工作,想深入研究可查看资料: Android消息机制2-Handler(Native层)

  • 移除消息 removeMessages()
    就是将消息从链表移除,同时将移除的消息添加到消息池,提供循环复用。
    采用了两个while循环,第一个循环是从队头开始,移除符合条件的消息,第二个循环是从头部移除完连续的满足条件的消息之后,再从队列后面继续查询是否有满足条件的消息需要被移除。
    void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;
            //从消息队列的头部开始,移除所有符合条件的消息
            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            //移除剩余的符合要求的消息
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
  • 退出消息队列
    消息退出的方式:

    • 当safe =true时,只移除尚未触发的所有消息,对于正在触发的消息并不移除;
    • 当safe =flase时,移除所有的消息

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

推荐阅读更多精彩内容