美文网首页Android开发Android进阶之路
被字节跳动、小米、美团面试官问的AndroidFramework

被字节跳动、小米、美团面试官问的AndroidFramework

作者: 炙热是你233 | 来源:发表于2020-12-18 17:45 被阅读0次

    目录

    1.Android中多进程通信的方式有哪些?
    a.进程通信你用过哪些?原理是什么?(字节跳动、小米)
    2.描述下Binder机制原理?(东方头条)
    3.Binder线程池的工作过程是什么样?(东方头条)
    4.Handler怎么进行线程通信,原理是什么?(东方头条)
    5.Handler如果没有消息处理是阻塞的还是非阻塞的?(字节跳动、小米)
    6.handler.post(Runnable) runnable是如何执行的?(字节跳动、小米)
    7.handler的Callback和handlemessage都存在,但callback返回true handleMessage还会执行么?(字节跳动、小米)
    8.Handler的sendMessage和postDelay的区别?(字节跳动)
    9.IdleHandler是什么?怎么使用,能解决什么问题?
    10.为什么Looper.loop不阻塞主线程?
    a.Looper无限循环为啥没有ANR(B站)
    11.Looper如何在子线程中创建?(字节跳动、小米)
    12.Looper、handler、线程间的关系。例如一个线程可以有几个Looper可以对应几个Handler?(字节跳动、小米)
    13.如何更新UI,为什么子线程不能更新UI?(美团)
    14.ThreadLocal的原理,以及在Looper是如何应用的?(字节跳动、小米)
    15.Android 有哪些存储数据的方式?
    16.SharedPreference原理,commit与apply的区别是什么?使用时需要有哪些注意?
    17.如何判断一个 APP 在前台还是后台?
    18.如何做应用保活?
    19.一张图片100x100在内存中的大小?(字节跳动)
    20.Intent的原理,作用,可以传递哪些类型的参数?
    21.如果需要在Activity间传递大量的数据怎么办?
    22.打开多个页面,如何实现一键退出?
    23.LiveData的生命周期如何监听的?(B站)

    参考解析

    1.Android 线程间通信有哪几种方式

    跨进程通信要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。

    然后,返回值将沿相反方向传输回来。

    Android 为我们提供了以下几种进程通信机制(供开发者使用的进程通信 API)

    • 文件
    • AIDL (基于 Binder)
    • Messenger (基于 Binder)
    • ContentProvider (基于 Binder)
    • Socket

    在上述通信机制的基础上,我们只需集中精力定义和实现 RPC 编程接口即可。

    2.描述下Binder机制原理?(东方头条)

    Linux系统将一个进程分为用户空间和内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式。普通的跨进程通信方式一般需要2次内存拷贝,如下图所示:

    一次完整的 Binder IPC 通信过程通常是这样:

    • 首先 Binder 驱动在内核空间创建一个数据接收缓存区。
    • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。
    • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

    3.Binder线程池的工作过程是什么样?(东方头条)

    Binder设计架构中,只有第一个Binder主线程(也就是Binder_1线程)是由应用程序主动创建,Binder线程池的普通线程都是由Binder驱动根据IPC通信需求创建,Binder线程的创建流程图:

    每次由Zygote fork出新进程的过程中,伴随着创建binder线程池,调用spawnPooledThread来创建binder主线程。当线程执行binder_thread_read的过程中,发现当前没有空闲线程,没有请求创建线程,且没有达到上限,则创建新的binder线程。

    Binder的transaction有3种类型:

    call: 发起进程的线程不一定是在Binder线程, 大多数情況下,接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程;

    reply: 发起者一定是binder线程,并且接收者线程便是上次call时的发起线程(该线程不一定是binder线程,可以是任意线程)。

    async: 与call类型差不多,唯一不同的是async是oneway方式不需要回复,发起进程的线程不一定是在Binder线程, 接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程。

    Binder系统中可分为3类binder线程:

    Binder主线程:进程创建过程会调用startThreadPool()过程中再进入spawnPooledThread(true),来创建Binder主线程。编号从1开始,也就是意味着binder主线程名为binder_1,并且主线程是不会退出的。
    Binder普通线程:是由Binder Driver来根据是否有空闲的binder线程来决定是否创建binder线程,回调spawnPooledThread(false) ,isMain=false,该线程名格式为binder_x。
    Binder其他线程:其他线程是指并没有调用spawnPooledThread方法,而是直接调用IPC.joinThreadPool(),将当前线程直接加入binder线程队列。例如: mediaserver和servicemanager的主线程都是binder线程,但system_server的主线程并非binder线程。

    4.Handler怎么进行线程通信,原理是什么?(东方头条)

    Handler的消息传递机制涉及到四个部分:

    1. Message:线程间传递的对象。
    2. MessageQueue: 消息队列,用来存放Handler发布的Message.
    3. Handler:负责将Message插入到MessageQueue中以及对MessageQueue中的Message进行处理。
    4. Looper:负责从MessageQueue中取出Message,并交给Handler.

    其中:

    • Looper存储在ThreadLocal中,Looper在创建时会同时创建MessageQueue,作为其成员对象.因此Looper和MessageQueue是属于创建者线程的,各线程之间的Looper和MessageQueue相互独立。
    • Handler在创建时会从当前线程的ThreadLocal中取得Looper.
    • 发送消息时,在发送线程中调用接收线程中的Handler的sendMessage方法,过程中,Handler会将自身赋予到Message的target中,并将Message插入到Handler对应的MessageQueue中。
    • 而接收线程中的Looper在循环过程中会取出这个Message,通过Message.target取出接收线程中的Handler,并将消息交Handler对象处理。由此实现了跨线程通信。
    • 要注意的是:线程与Looper和MessageQueue是一对一的关系,即一个线程只维护一个Looper和一个MessageQueue;而线程与Handler的关系是一对多,即一个线程可以有很多Handler,一个Handler只对应一个线程,这也是为什么Handler在发送消息时,为什么要将自身赋给Message.target的原因。

    5.Handler如果没有消息处理是阻塞的还是非阻塞的?(字节跳动、小米)

    // 下面这个方法有可能会产生阻塞,主要是通过native层的epoll机制,监听文件描述符的写入事件实现的。
    // -1:一直阻塞;0:不阻塞;n>0:最多阻塞n秒
    nativePollOnce(ptr, nextPollTimeoutMillis);

    6.handler.post(Runnable) runnable是如何执行的?(字节跳动、小米)

    关于这个handler.post(Runnable r)这个方法,用过很多次,

    看下源码,它到底是怎样处理的。

    public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    

    看下getPostMessage(r)这个方法的源码,

    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    

    给message设置了回调,
    然后,looper进行消息循环,进行消息的分发,

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

    如果回调不为空,handleCallback(msg) 找个方法就会执行,

    private static void handleCallback(Message message) {
            message.callback.run();
        }<span style="font-family: Arial, Helvetica, sans-serif;">调用了run方法。</span>
    

    7.handler的Callback和handlemessage都存在,但callback返回true handleMessage还会执行么?(字节跳动、小米)

    sendMessageAtTime 使用的uptimeMillis依赖的是系统以开机时间的绝对时间;

    而sendMessageDelayed使用的delayMillis依赖的是系统以开机时间的相对时间。

    啥意思呢:就是说delayMillis使用的时候要加一个SystemClock.uptimeMillis(),

    也就是sendMessageAtTime等于snedMessageDelayed的情况为uptimeMillis == delayMillis - SystemClock.uptimeMillis()[这是一个相对时间].

    SystemClock.uptimeMillis()是获取系统从开机启动到现在的时间,期间不包括休眠的时间,这里获得到的时间是一个相对的时间,而不是通过获取当前的时间(绝对时间)。

    而之所以使用这种方式来计算时间,而不是获得当前currenttime来计算,在于handler会受到阻塞,挂起状态,睡眠等,这些时候是不应该执行的;如果使用绝对时间的话,就会抢占资源来执行当前handler的内容,显然这是不应该出现的情况,所以要避免。

    查看源码如下:

        /**
         * Enqueue a message into the message queue after all pending messages
         * before (current time + delayMillis). You will receive it in
         * {@link #handleMessage}, in the thread attached to this handler.
         *  
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.  Note that a
         *         result of true does not mean the message will be processed -- if
         *         the looper is quit before the delivery time of the message
         *         occurs then the message will be dropped.
         */
        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    
        /**
         * Enqueue a message into the message queue after all pending messages
         * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
         * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
         * You will receive it in {@link #handleMessage}, in the thread attached
         * to this handler.
         * 
         * @param uptimeMillis The absolute time at which the message should be
         *         delivered, using the
         *         {@link android.os.SystemClock#uptimeMillis} time-base.
         *         
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.  Note that a
         *         result of true does not mean the message will be processed -- if
         *         the looper is quit before the delivery time of the message
         *         occurs then the message will be dropped.
         */
        public boolean sendMessageAtTime(Message msg, long uptimeMillis)
        {
            boolean sent = false;
            MessageQueue queue = mQueue;
            if (queue != null) {
                msg.target = this;
                sent = queue.enqueueMessage(msg, uptimeMillis);
            }
            else {
                RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
            }
            return sent;
        }
    

    8.Handler的sendMessage和postDelay的区别?(字节跳动)

    Thread/Hander/Looper是Android在Java线程基础之上提供的线程通信/消息处理机制,这个众所周知,不再细说。Handler提供了两个发送延迟处理任务的api:

    /**
     * Enqueue a message into the message queue after all pending messages
     * before (current time + delayMillis). You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    
    /**
     * Causes the Runnable r to be added to the message queue, to be run
     * after the specified amount of time elapses.
     * The runnable will be run on the thread to which this handler
     * is attached.
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     *  
     * @param r The Runnable that will be executed.
     * @param delayMillis The delay (in milliseconds) until the Runnable
     *        will be executed.
     *        
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the Runnable will be processed --
     *         if the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean postDelayed(Runnable r, long delayMillis)
    

    问题在于,这两个delay的精度到底能有多大?如何理解?很多APP的定时处理机制都是使用这两个api递归抛延迟任务来实现的。所以有必要研究一下框架层的实现,心中有数。Android这套消息循环机制工作在最上层,距离Linux kernel的时间管理甚远。本文仍然采用跟踪分析代码的方式,基于android7.1.1。

    postDelayed()实际上封装了sendMessageDelayed(),第一时间便殊途同归:
        public final boolean postDelayed(Runnable r, long delayMillis)
        {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    
        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    

    postDelayed()首先通过getPostMessage()将传入的Runnable对象封装成一个Message,调用sendMessageDelayed(),而sendMessageDelayed()增加了一个delay时间参数的健壮性检查,然后转化成绝对时间,调用sendMessageAtTime()。至此,再多说一句:最简单的sendMessage()和post()实际上也是sendMessageDelayed(0)的封装。所以,Handler五花八门的post/send api们本质上无差别。只是为了让使用者在简单的情况下避免手动封装Message,只需提供一个Runnable即可。Handler调用关系整理如下:
    post()/postDelayed()/sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage()

    postAtTime()->sendMessageAtTime()->enqueueMessage()

    postAtFrontOfQueue()->sendMessageAtFrontOfQueue()->enqueueMessage()

    最后都以enqueueMessage()告终

    enqueueMessage()->MessageQueue.enqueueMessage(Message msg, long when)

    如前所述,这时候when已经转化成绝对系统时间。转入消息队列类MessageQueue看一下enqueueMessage()这个方法:

        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 (;;) {
                        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插入到消息队列中,插入的新消息有三种可能成为消息队列的head:

    (1)消息队列为空;

    (2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue系列的API才会满足这个条件;

    (3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。

    接下来就要看看消息循环(Looper)如何使用when,这是本文问题的关键。关键的方法,Looper.loop(),启动线程消息循环:

        /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the 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 (;;) {
                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.isTagEnabled(traceTag)) {
                    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();
            }
        }
    

    从for(;;)可以看到一次循环开始于从消息队列中去取一个消息,MessageQueue.next(),如果next()返回null,则loop()会返回,本次消息循环结束。取出消息之后,通过Handler.dispatchMessage()处理消息:
    msg.target.dispatchMessage(msg);

    也就是说,取下一个消息的实际执行时间取决于上一个消息什么时候处理完。再看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;
            }
        }
    

    看到next()实际上也有一个for(;;),而出口只有两个:消息队列已经退出,返回null;找到了一个合适的消息,将其返回。如果没有合适的消息,或者消息队列为空,会block或者由IdleHandler处理,不在本文问题范畴,暂不展开。主要看找到合适的消息的逻辑:

                    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;
                    }
    

    可以看到,如果在消息队列中顺序找到了一个消息msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于msg.when,那么会计算一个timeout,以便在到执行时间时wake up;如果当前系统时间大于或等于msg.when,那么会返回msg给Looper.loop()。所以这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理。很好理解:

    (1)在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。

    (2)即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。

    (3)在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。

    所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

    9.IdleHandler是什么?怎么使用,能解决什么问题?

    那么首先我们就先来通过源码分析了解一下 IdleHandler 的工作原理吧。

    文章出自:IdleHandler原理分析

    IdleHandler的源码分析

    首先,我们来看下 IdleHandler 的添加和移除。

        public void addIdleHandler(@NonNull IdleHandler handler) {
            if (handler == null) {
                throw new NullPointerException("Can't add a null IdleHandler");
            }
            synchronized (this) {
                mIdleHandlers.add(handler);
            }
        }
    
        public void removeIdleHandler(@NonNull IdleHandler handler) {
            synchronized (this) {
                mIdleHandlers.remove(handler);
            }
        }
    

    我们可以通过当前线程的 Looper 获取消息队列,然后调用 addIdleHandler(IdleHandler)removeIdleHandler(IdleHandler) 添加和移除 IdleHandler。

    知道了如何添加和移除 IdleHandler,我们再来看下 IdleHandler 的使用。

    在 Looper 的 loop()方法中,会调用 MessageQueue 中的 next() 来取消息处理,而 IdleHandler 就是在此方法中使用了。

        Message next() {
            // ......
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // ......
    
                    // 1
                    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;
                    }
    
                    // 2
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    // 3
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null;
    
                    boolean keep = false;
                    try {
                        // 4
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    // 5
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                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;
            }
        }
    

    next()中取消息的部分我们省略了,具体逻辑可参考之前介绍的Android消息机制。现在我们只分析有关 IdleHandler 的这一段代码。

    首先 pendingIdleHandlerCount 默认为-1,所以注释1中的 pendingIdleHandlerCount<0 条件是成立的,可是还有一个 mMessages == null || now < mMessages.when 条件,这个条件的意思是当前消息队列没有消息了,或者说消息队列有消息,但是消息不是现在处理的(之后某个时间再处理)。通俗说就是当前没有可处理的消息的时候,就会进入注释1中计算 IdleHandler 的数量。也就是说,IdleHandler是用在消息队列闲暇的时候的,当消息队列中有消息的时候 IdlerHandler 不会起作用,只有在消息队列处理完消息的时候才会发生作用。

    接着在注释2中,创建了一个数量至少为4的 IdleHandler 数组。并且将消息队列中的 IdleHandler 全部赋值为数组中的元素。

    注释3中,获取 IdleHandler,之后释放数组中的 IdleHandler 引用。

    注释4中,调用 IdleHandler的 queueIdle() 函数,并且返回一个 bool 值。

    注释5中,利用 queueIdle()的返回值来判断是否需要移除 IdleHandler, 当 queueIdle() 返回 false 的时候,消息队列会移除该 IdleHandler, 返回为 true时,则继续保留。

    从上述的代码中我们可以了解到 IdleHandler 的几点特性。

    • IdleHandler 是在消息队列当前无可用消息的时候发生作用的,如果你想在消息队列空暇时做一些处理,那么你就可以在当前线程的消息队列中添加 IdleHandler,并重写它的 queueIdle() 函数。
    • IdleHandler 作用次数可为一次或者多次。这取决于它的 queueIdle() 的返回值,如果 queueIdle() 返回 false, 那么消息队列第一次空暇调用完 queueIdle() 之后便会将该 IdleHandler 移除。如果返回 true, 那意味着每次只要消息队列空闲,就会调用一次 queueIdle()

    可能有些小伙伴这个时候会有些疑问,如果 queueIdle() 返回 true 的时候,如果消息队列一直没有消息处于空闲状态,是不是就会无限循环调用 queueIdle() ? 回答这个问题之前,我们需要回想起以前介绍的在 next()中的函数 nativePollOnce()。我们之前介绍过,这个函数在 native 层会将线程阻塞,等有新事件来临的时候再进行唤醒,所以就不会出现我们之前猜测的无限调用 queueIdle() 的问题。

    IdleHandler 有什么作用呢?

    那从源码角度讨论完 IdleHandler ,了解了它的特性和工作原理,接下来我们就来分析一下它有什么作用。

    其实分析 IdleHandler 的作用也是需要从它的特性和工作原理来思考的。
    首先,IdleHandler 是在消息队列当前无可用消息的时候发生作用的,那么我们就可以猜测 IdleHandler 是不是可用来执行一些优化动作。比如,处理一些不是十分重要的初始化操作。在启动 Activity 的时候,如果将一些不太重要的初始化动作 onCreate()onResume()等函数中可能会造成页面显示缓慢的问题,影响应用启动速度。而如果将这些操作使用 IdleHandler 的 queueIdle() 来进行初始化,那么就可以在线程空闲的时候来进行这些初始化动作,加快用户看到界面的速度,从而提高用户体验。但同时需要注意一点, IdleHandler 依赖于消息队列中的消息,如果当前一直有消息进入消息队列,那么 IdleHandler 可能就一直都无法有执行的机会。

    其次,我们有时候想获取某个控件的宽高,又或者是想要某个View绘制完之后添加依赖于这个View的View,那么就可以使用 IdleHandler 来进行获取宽高或者添加View。 当然也可以使用 View.post()来实现,区别是前者是在消息队列空闲的时候执行, 后者是作为一个消息来执行。

    LeakCanary 中也有使用到 IdleHandler, LeakCanary 2.0中的源码使用Kotlin编写的,还没有研究。等之后看了再补上。

    IdleHandler 在系统源码中的运用

    IdleHandler 在系统源码中也有过使用,其中 GcIdler便实现了 IdleHandler 接口。我们来看下 GcIdle 是如何工作的。

       final class GcIdler implements MessageQueue.IdleHandler {
           @Override
           public final boolean queueIdle() {
               doGcIfNeeded();
               return false;
           }
       }
    
       void doGcIfNeeded() {
           mGcIdlerScheduled = false;
           final long now = SystemClock.uptimeMillis();
           //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
           //        + "m now=" + now);
           if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
               //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
               BinderInternal.forceGc("bg");
           }
       }
    

    GcIdler 用来实现强制性GC操作的。当现在时间超过了上一次GC的 MIN_TIME_BETWEEN_GCS == 5000, 也即离上一次GC超过5秒之后就会再一次强制性触发GC, 并且每个GcIdler 返回 false,只触发一次。而 GcIdler是怎么添加的呢?我们可以看下。

        void scheduleGcIdler() {
            if (!mGcIdlerScheduled) {
                mGcIdlerScheduled = true;
                Looper.myQueue().addIdleHandler(mGcIdler);
            }
            mH.removeMessages(H.GC_WHEN_IDLE);
        }
    

    通过调用 scheduleGcIdler() 来进行触发。而 scheduleGcIdler()又是通过 ActivityThread 中的 Handler H发送消息触发的。再往上追溯,我们可以知道是在 AMS 中的这两个方法调用之后触发:

    • doLowMemReportIfNeededLocked
    • activityIdle

    所以Android 会在内存不够的时候使用 IdleHandler 来进行强制性GC 优化。或者可能当 ActivityThread 的 handleResumeActivity方法被调用时触发的。

    10.为什么Looper.loop不阻塞主线程?

    我们平时看IntentService时看到了Thread的run方法如下:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    

    代码Looper.loop()是一个for死循环,然后突然想到主线程中也有Looper为什么不卡主线程于是找到了ActivityThread的源码

    public static void main(String[] args) {    
    
          Looper.prepareMainLooper();    
    
         ActivityThread thread = new ActivityThread();
    
         thread.attach(false);        
    
      if (sMainThreadHandler == null) {          
    
      sMainThreadHandler = thread.getHandler();      
    
      }
    
      if (false) {            
    
      Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));    
    
       }         // End of event ActivityThreadMain.        
    
     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);    
    
     Looper.loop();         throw new RuntimeException("Main thread loop unexpectedly exited");    
    
    }
    
    

    main方法的退出就是Looper.loop();的执行完毕,所有事件都是Looper的监听,主线程本事就是个阻塞。

    Android是事件驱动,Looper内部是一个while死循华,只有程序退出后循环才会停止,如果Looper使用中死掉了,任何事件都不会有反应了。事件只会阻塞Looper,而Looper不会阻塞事件。

    最后

    关于如何学习Android Framework开发知识,最近有幸在前阿里技术总监手里扒到这份Android framework高级开发笔记,部分知识章节发布到了在知乎上竟然1000+点赞,今天就拿出来分享给大家。

    本笔记讲解了Framework的主要模块,从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用架构如何解决实际的问题,由浅入深,详细解析Framework,让你简单高效学完这块知识!

    由于篇幅有限,仅展示部分内容,所有的知识点 整理的详细内容都放在了我的【GitHub】,有需要的朋友自取。

    第一章:深入解析Binder

    Binder机制作为进程间通信的一种手段,基本上贯穿了andorid框架层的全部。所以首先必须要搞懂的Android Binder的基本通信机制。Binder机制作为进程间通信的一种手段,基本上贯穿了andorid框架层的全部。所以首先必须要搞懂的Android Binder的基本通信机制。

    第二章:深入解析Handler

    相信大家都有这样的感受:网上分析 Handler 机制原理的文章那么多, 为啥还要画蛇添足整理这份笔记呢?不是说前人们写的文章不好,我就是觉得他们写的不细, 有些点不讲清楚,逻辑很难通顺的,每次我学个什么东西时遇到这种情况都贼难受。

    本章先宏观理论分析与 Message 源码分析,再到MessageQueue 的源码分析,Looper 的源码分析,handler 的源码分析,Handler 机制实现原理总结。最后还整理Handler 所有面试题大全解析。

    Handler这章内容很长,但思路是循序渐进的,如果你能坚持读完我相信肯定不会让你失望。

    第三章:Dalvik VM 进程系统

    Andorid系统启动、init 进程、Zygote、SystemServer启动流程、 应用程序的创建使用,Activity的创建、销毁 Handler和Looper。

    第四章 深入解析 WMS

    窗口管理框架 系统动画框架 View的工作原理。

    第五章 PackagerManagerService

    包管理服务,资源管理相关类。

    如有需要获取完整的资料文档的朋友点击我的GitHub免费获取。

    相关文章

      网友评论

        本文标题:被字节跳动、小米、美团面试官问的AndroidFramework

        本文链接:https://www.haomeiwen.com/subject/wczxnktx.html