关于android:被字节跳动小米美团面试官问的AndroidFramework难倒了-这里有23道面试真题助力成为offer收割机

6次阅读

共计 21492 个字符,预计需要花费 54 分钟才能阅读完成。

目录

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. 一张图片 100×100 在内存中的大小?(字节跳动)
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 收费获取。

正文完
 0