Android消息机制

Android消息机制

Android小彩虹2021-08-24 3:07:32220A+A-

一、概述

Android的Handler消息机制涉及Android系统多个方面,例如ActivityService的生命周期调用,开发中展示从网络下载的数据,线程通信等。通过阅读源码解决下面问题。

问题:

  • 消息处理的优先级?
  • 消息是怎么存储的?
  • 一个线程中LooperHandlerMessageQueue数量的对应关系?
  • 主线程Looper.loop()无限循环为什么不会ANR
  • 如何避免使用Handler造成的内存泄露?
  • IdleHandler是什么?有什么用?
  • 消息(Message)能没有target(Handler)吗?

二、引出主题

模拟使用Handler显示下载的数据。

       //step1,创建Handler
       var handler = object : Handler() {
            override fun dispatchMessage(msg: Message) {
                println(msg.obj)
            }
        }
        //模拟线程下载数据
        Thread{
            Thread.sleep(1000)
            var msg=Message.obtain()
            msg.obj="download data"
            //step2,发送消息
            handler.sendMessage(msg)
        }.start()

三、Handler

1.发送消息

使用无参构造创建的Handler会调用2个参数的构造方法。

    public Handler(@Nullable 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是否初始化完成,但我们前面使用时并没有初始化这个Looper,为什么没有抛出这个RuntimeException?其实在App启动时系统已经帮我们初始化好了这个主线程的Looper。像每个Java程序都有个main方法的入口,App也有个main方法入口。在这个方法中,会初始化UI线程Looper,并开始无限循环,从这可以侧面看出整个应用的运转基本依赖于这套消息机制。

ActivityThread.java public static void main(String[] args) {
       ...代码省略
       //初始化Looper
       Looper.prepareMainLooper();
       ActivityThread thread = new ActivityThread();
       thread.attach(false, startSeq);

       if (sMainThreadHandler == null) {
           sMainThreadHandler = thread.getHandler();
       }
       //开启循环
       Looper.loop();
       //执行到这就是出大问题了
       throw new RuntimeException("Main thread loop unexpectedly exited");
}

sendMessage()会一直调用到enqueueMessage(),然后指定msgtarget为当前Handler,在调用MessageQueueenqueueMessage()

2.处理消息

Looperloop方法中会不断取出消息,然后找到msgtarget处理消息,然后回调到HandlerdispatchMessage方法,通过源码可以看出,消息的处理优先级为:

  1. msgcallback优先级最高
  2. 然后是在创建Handler时指定的CallBack
  3. 最后才是HandlerhandleMessage方法
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

四、Looper

1.prepare方法

prepare方法会初始化Looper,保存在ThreadLocal中。ThreadLocal是一个与线程相关的数据保存类,保证了每个线程的这份数据独立性。所以通过sThreadLocal取出的Looper都是当前线程的独一份的Looper,得到的MessageQueue也是独一份。

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

2.loop方法

loop方法无限循环从MessageQueue中取出msg,交给Handler处理,然后将处理的msg放回消息池。如果消息队列为空了则结束此循环。

    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;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);
            } catch (Exception exception) {
            }
            msg.recycleUnchecked();
        }
     }

五、MessageQueue

1.添加消息enqueueMessage()

MessageQueue意为消息队列,就是消息池。通过下面的enqueueMessage方法可以看出MessageQueue的实现方式是单链表。单链表在这的优势在于插入删除快,不用申请一块大的连续内存空间(相比数组)。

    boolean enqueueMessage(Message msg, long when) {
        //这种方式添加的msg必须有target
        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) {
                //被标记退出循环,回收msg
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 新头部,消息池中没消息,或者消息的处理时间是最早的。
                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 (;;) {
                    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;
    }

2.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();
            }
           //nataive方法,阻塞方法。
            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) {
                    //最近的消息还没到触发时间,算出需要等好久,去nativePollOnce方法里等着
                    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 {//取出消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;//从链表断开
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    //没有消息去nativePollOnce一直等。
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {//标记退出循环
                    dispose();
                    return null;
                }
                //第一次空闲,得到IdleHandlers的数量
                //队列为空或者第一条消息将来要处理时,才运行空闲处理。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    //没有idle handlers需要执行,循环去等着
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // 运行idle handlers.
            // 只有第一次循环才会到这执行
            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 {//执行idler
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {//false执行一边就删除,true执行多次
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重置为0,我们就不会在再执行idle handler
            pendingIdleHandlerCount = 0;

            // 当我们执行了idle handler,可能就有新消息来了,就不等了,去看看。
            nextPollTimeoutMillis = 0;

}

nativePollOnce方法是一个native方法,没有消息处理时,程序会在这阻塞。nextPollTimeoutMillis表示阻塞的时间,-1表示一直会阻塞,0表示不阻塞,大于0表示下条消息等的时间。当没有消息时,会进入nativePollOnce方法里,线程会释放CPU的资源,进入休眠状态。

如果没有消息处理,在进入等待前,会认为这个线程处于空闲状态,会把添加了的IdleHandler执行了。

六、Message

Message比较重要的是obtain()recycleUnchecked(),创建Message最好使用obtain()而不是直接newobtain()方法会从消息池中拿出一个已经创建好的Message,可以减少对象的创建。recycleUnchecked()则是将已经使用过的消息重新放回消息池,消息池最大为50。

    private static final int MAX_POOL_SIZE = 50;
    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();
    }
    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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

七、其它

1.IdleHandler

从上面分析MessageQueuenext()方法可以看出,当暂时没消息处理进入休眠前,它会查看是否有其它杂事要干,可以通过Looper.myQueue().addIdleHandler(MyIdleHandler())添加执行内容。所以IdleHandlerHandler提供的一种当消息队列空闲时,执行任务的机制。由于执行时机不稳定,可以用来处理一些不重要的杂事。例如ActivityThread中的GcIdler用来执行GC任务。注意返回falsetrue的区别,false执行一次,true执行多次。

2.postSyncBarrier()

postSyncBarrier()的作用是设置一个同步屏障。设置一个msg为屏障,它的标志是没有target,把这个msg按时间when插入链表中。在next()方法中,如果发现了同步屏障,则在链表中寻找第一个异步消息返回。这个机制就实现了,只要设置一个没有target的同步屏障msg,则消息机制就转成优先处理异步消息,同步消息就会阻塞到等同步屏障移除后才能执行。而postSyncBarrier()被标记为hide,普通开发者不能调用。它的作用在与处理一些优先级比较高的任务。比如绘制UI,在ViewRootImplscheduleTraversals()方法设置了同步屏障,保证了UI绘制优先执行。

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }
    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
    Message next() {
     ...
     synchronized (this) {
                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());
                }
      ...
    }
    ViewRootImpl.java void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
           //设置同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

八、总结

  • 消息处理的优先级?
  • 消息是怎么存储的?
  • 一个线程中LooperHandlerMessageQueue数量的对应关系?
  • 主线程Looper.loop()无限循环为什么不会ANR
  • 如何避免使用Handler造成的内存泄露?
  • IdleHandler是什么?有什么用?
  • 消息(Message)能没有target(Handler)吗?

综上,问题基本能够解答,还差个内存泄露问题。造成这个问题的原因是,平时开发中创建Handler大多是在Activity中用非静态内部类或者匿名内部类的方式,Handler持有Activity的引用,当Activity被销毁时,如果还有消息msg没处理,而msg持有Handler引用,导致Activity不能被回收,造成内存泄露。

解决办法是2种(打破这个引用链即可):

  1. 静态内部类+弱引用
  2. Activity销毁时调用handler.removeCallbacksAndMessages(null)清空没有处理完的消息。

本文参考:

Android消息机制1-Handler(Java层)

面试官:“看你简历上写熟悉 Handler 机制,那聊聊 IdleHandler 吧?

Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

揭秘 Android 消息机制之同步屏障:target==null ? Android 多线程:你的 Handler 内存泄露 了吗?

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示 | 支付宝红包
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们