Android FrameWork

directory

1. What are the ways of multi-process communication in Android? A. Process communication which have you used? How does it work? (Bytedance, Xiaomi) 2. Describe the principle of Binder mechanism Binder thread Pools 3.Binder thread pools 4. How does Handler communicate with threads? If there is no message processing is blocking or non-blocking? Handler. post(Runnable) How is Runnable executed? 7. The handler Callback and handleMessage exist, but does the handleMessage return true? (Bytedance, mi) 8.Handler sendMessage and postDelay? 9. What is IdleHandler? How to use it? What problems can it solve? 10. Why doesn’t Looper. Loop block the main thread? 11. How can Looper be created in child threads? (Bytedance, Xiaomi) 12.Looper, Handler, thread relationship. For example, how many loopers can a thread have for each Handler? (Bytedance, Xiaomi) 13. How do I update the UI? Why can’t child threads update the UI? 14. The principle of ThreadLocal and how it is applied in Looper? (Bytedance, Xiaomi) 15. What ways does Android store data? 16.SharedPreference principle, what is the difference between commit and apply? What should I pay attention to when using it? 17. How to determine whether an APP is in the foreground or background? 18. How to do application maintenance? 19. The size of an image 100×100 in memory? (Bytedance)

What types of parameters can be passed with Intent?

21. What if you need to pass a large amount of data between activities?

22. Open multiple pages, how to achieve one-click exit?

23. How is the life cycle of LiveData monitored? B (stand)

Refer to parse

1. What are the ways of communication between Android threads

Cross-process communication requires that the method call and its data be decomposed to a level recognized by the operating system, transferred from the local process and address space to the remote process and address space, where the call is reassembled and executed.

The return value is then transmitted back in the opposite direction.

Android provides the following process communication mechanisms (process communication apis for developers) :

  • file

  • AIDL (Based on Binder)

    • Android Advanced: Use of AIDL for process communication
    • Android Advanced: AIDL parsing for process communication
  • Binder

    • Android Advanced: Binder mechanisms for Process Communication
  • Messenger (Based on Binder)

    • Android Advanced: Using and parsing Messenger for process communication
  • ContentProvider (Based on Binder)

    • Android advanced: ContentProvider ContentProvider for process communication
  • Socket

    • Android Advanced: Process Communication Socket (also review TCP UDP)

On the basis of the above communication mechanisms, we only need to focus on defining and implementing RPC programming interfaces.

2. Describe the principle of Binder mechanism? (Oriental Headline)

Linux systems divide a process into user space and kernel space. Data in user space cannot be shared between processes, while data in kernel space can be shared. To ensure security and independence, one process cannot directly operate or access another process. In other words, Android processes are independent and isolated from each other, which requires cross-process data communication. Common cross-process communication requires two memory copies, as shown in the following figure:

A complete Binder IPC communication process usually looks like this:

  • The Binder driver first creates a data receive cache in kernel space.
  • Then, a kernel cache is created in the kernel space, and the mapping relationship between the kernel cache and the kernel data receiving cache, as well as the mapping relationship between the kernel data receiving cache and the user space address of the receiving process is established.
  • The sender process uses the system call copyfromuser() to copy the data to the kernel cache in the kernel. Since there is memory mapping between the kernel cache and the user space of the receiving process, the data is sent to the user space of the receiving process, thus completing an inter-process communication.

3. How do Binder thread pools work? (Oriental Headline)

Binder_1 thread (Binder_1 thread) is the binder_thread (Binder_1 thread). Binder_binder_thread (Binder_1 thread) is the binder_binder_thread (Binder_1 thread). Binder_binder_thread (Binder_1 thread) is the binder_binder_thread (Binder_1 thread).

During each new process fork by Zygote, the spawnPooledThread is called to create the binder main thread along with the creation of the binder thread pool. A new binder thread is created when a thread executes binder_thread_read and finds that there are currently no idle threads, no request to create threads, and no upper limit has been reached.

Binder transactions come in three types:

Call: The thread that initiates the process is not necessarily a Binder thread. In most cases, the receiver only points to the process and is not sure which thread will handle it, so no thread is specified. Reply: The initiator must be a binder thread and the receiver thread is the same thread that initiated the last call (it doesn’t have to be a binder thread, it can be any thread). Async: Similar to call, the only difference is that Async is oneway and does not require a reply. The thread that initiates the process is not necessarily a Binder thread. The receiver only points to the process and is not sure which thread will handle it, so no thread is specified. Binder system can be divided into three types of Binder threads:

Binder main thread: Process creation calls startThreadPool() and spawnPooledThread(true) to create the Binder main thread. The numbering starts at 1, which means the binder main thread is named binder_1 and does not exit. Binder common thread: The spawnPooledThread(false) is created using the spawnPooledThread(isMain=false) callback. The thread name is in the format of binder_x. Binder Other Thread: The other thread is added directly to the Binder thread queue by calling IPc.joinThreadPool () instead of calling the spawnPooledThread method. For example, the main threads of Mediaserver and Servicemanager are binder threads, but the main threads of System_server are not binder threads.

4. How does Handler communicate with threads? (Oriental Headline)

The Handler messaging mechanism involves four parts:

  1. Message: Object passed between threads.
  2. MessageQueue: Message queue used to store messages published by the Handler.
  3. Handler: Inserts messages into a MessageQueue and processes messages in a MessageQueue.
  4. Looper: Is responsible for fetching messages from MessageQueue and handing them to Handler.

Among them:

  • Looper is stored in a ThreadLocal, and when Looper is created, MessageQueue is created as its member object. Therefore, Looper and MessageQueue belong to the creator thread, and the Looper and MessageQueue between threads are independent of each other.
  • The Handler gets the Looper from the current thread’s ThreadLocal when it is created.
  • When sending a Message, the sending thread calls the sendMessage method of the Handler in the receiving thread. During the process, the Handler assigns itself to the target of the Message and inserts the Message into the corresponding MessageQueue of the Handler.
  • The Looper on the receiving thread picks up the Message during the loop, fetches the Handler from the receiving thread via message. target, and passes the Message to the Handler object for processing. This enables cross-thread communication.
  • Note that threads have a one-to-one relationship with Looper and MessageQueue. That is, a thread maintains only one Looper and one MessageQueue. The relationship between a thread and a Handler is one-to-many, that is, a thread can have many handlers, and only one Handler corresponds to one thread. This is why the Handler assigns itself to message.target when sending a Message.

5.Handler If there is no message processing is blocking or non-blocking? (Bytedance, Xiaomi)

// The following method may block, mainly through the native layer epoll mechanism, listening for the write event of the file descriptor. // -1: block; 0: does not block. N >0: blocks nativePollOnce(PTR, nextPollTimeoutMillis) for at most n seconds.

Handler. post(Runnable) How is Runnable executed? (Bytedance, Xiaomi)

The handler. Post (Runnable r) method, which I’ve used many times,

Take a look at the source code to see how it is handled.

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

Take a look at the source code for getPostMessage(r),

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

The message callback is set, and looper then loops through the message to distribute the message,

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

If the callback is not empty, handleCallback(MSG) finds a method to execute it,

private static void handleCallback(Message message) { message.callback.run(); }<span style="font-family: Arial, Helvetica, sans-serif;" > calls the run method. </span>Copy the code

7. The handler Callback and handleMessage exist, but does the handleMessage return true? (Bytedance, Xiaomi)

The uptimeMillis used by sendMessageAtTime depends on the absolute time when the system is started.

DelayMillis, used by sendMessageDelayed, relies on the relative time of the system to boot time.

Add systemclock. uptimeMillis() to delayMillis

UptimeMillis == delayMillis – SystemClock. UptimeMillis ()[this is a relative time].

Systemclock. uptimeMillis() is used to obtain the system time from boot up to now, excluding sleep time. The time obtained here is a relative time, not the current time (absolute time).

And the reason we’re using this method to calculate the time, rather than getting the currenttime, is because the handler is blocked, suspended, asleep, etc., which should not be executed; Using absolute time preempts resources to execute the contents of the current handler, which obviously should not happen, so avoid it.

View the source code as follows:

    /**
     * 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);
    }
Copy the code

/** * 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; }Copy the code

8. What is the difference between sendMessage and postDelay in Handler? (Bytedance) Thread/Hander/Looper is a well-known Thread communication/message handling mechanism provided by Android on top of Java threads. Handler provides two apis for sending deferred processing tasks:

/**
 * 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)
Copy the code

/** * 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)Copy the code

The question is, how accurate can these two delays be? How to understand? Many APP timing mechanisms are implemented using these two apis to recursively dump delayed tasks. So it is necessary to look at the implementation of the framework layer, with a clear idea. The Android messaging loop works at the top level, far from the Linux Kernel’s time management. This article still uses the method of tracking and analyzing the code, based on android7.1.1.

PostDelayed () actually encapsulates sendMessageDelayed(), which leads to the same outcome in the first place:  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); }Copy the code

PostDelayed () first encapsulates the incoming Runnable object into a Message via getPostMessage(), calling sendMessageDelayed(), SendMessageDelayed () adds a robustness check for the delay time parameter, which is then converted to absolute time by calling sendMessageAtTime(). At this point, one more word: The simplest sendMessage() and post() are actually wrappers for sendMessageDelayed(0). As a result, the Handler’s various POST /send apis are essentially the same. This is just to allow users to avoid manually encapsulating messages in simple cases and simply provide a Runnable. Handler call relationship is as follows: Post ()/postDelayed()/sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage()

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

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

They all end up with enqueueMessage()

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

As mentioned earlier, at this point when has been converted to absolute system time. Go to MessageQueue and look at 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; }Copy the code

This method is relatively simple, using a thread-safe way to insert a Message into the Message queue, the new Message can become the head of the Message queue in one of three ways:

(1) Message queue is empty;

(2) When is 0, because at this point, when has been converted to absolute time, so only the AtFrontOfQueue API will meet this condition;

(3) The current head Message execution time is after WHEN, that is, there is no Message in the Message queue that needs to be executed before this Message.

The next step is to look at how the message loop (Looper) uses WHEN, which is the crux of this article’s problem. The key method, looper.loop (), starts the thread message 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(); }}Copy the code

From for (;;) You can see that a loop starts with a message fetched from the MessageQueue, messagequeue.next (), and if next() returns null, loop() returns and the message loop ends. Take out the news through the Handler. DispatchMessage () processing messages: MSG. Target. DispatchMessage (MSG); That is, the actual execution time for fetching the next message depends on when the last message was processed. Messagequyue.next () does what it does:

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; }}Copy the code

See that next() actually has a for(;) as well. , and there are only two exits: the message queue has exited, returning NULL; A suitable message is found and returned. If there is no suitable message, or if the message queue is empty, it will be blocked or handled by IdleHandler, which is beyond the scope of this article. Find the appropriate message logic:

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; }Copy the code

If the current system time is less than msg.when, a timeout will be calculated. If the current system time is less than msG. when, a timeout will be calculated. To wake up at execution time; If the current system time is greater than or equal to msg.when, MSG is returned to looper.loop (). So this logic only guarantees that the message will not be processed until when, but not at when. (1) In loop.loop (), the message is processed sequentially. If the previous message takes a long time to process and has exceeded when after completion, the message cannot be processed at the time point when.

(2) Even if the time point when is not occupied by processing other messages, a thread may be scheduled to lose the CPU slice.

(3) In the process of waiting for time point WHEN, messages with earlier processing time are likely to be processed preferentially, which increases the possibility of (1).

Therefore, it can be seen from the above three points that the API provided by the Handler for specifying processing time such as postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime() can only be guaranteed not to be executed before the specified time. There is no guarantee that it will be executed at the specified point in time.

9. What is IdleHandler? How to use it? What problems can it solve?

So first we will first through the source code analysis to understand how IdleHandler works.

Article from: IdleHandler principle analysis

IdleHandler source code analysis

First, let’s look at adding and removing idleHandlers.

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); }}Copy the code

We can get the message queue from the current thread’s Looper, and then call addIdleHandler(IdleHandler) and removeIdleHandler(IdleHandler) to add and remove idleHandlers.

Now that we know how to add and remove idleHandlers, let’s look at the use of idleHandlers.

In Looper’s loop() method, next() in MessageQueue is called to fetch message processing, and IdleHandler is used in this method.

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; }}Copy the code

We have omitted the message fetching part in next(). The logic can be found in the Android message mechanism described earlier. For now, let’s just look at the IdleHandler code.

PendingIdleHandlerCount defaults to -1, so the pendingIdleHandlerCount<0 condition in comment 1 is true. But there is also a mMessages = = null | | now < mMessages. The when condition, and the meaning of this condition is the current message queue without the news, or a message queue the message, but the message is not now processing (processing after a certain time). Note 1 is used to count the number of IDleHandlers when there are no current messages to process. In other words, the IdleHandler is used when the message queue is idle. The IdlerHandler does not work when there is a message in the message queue, but only when the message queue has finished processing the message.

Next, in comment 2, an IdleHandler array of at least 4 is created. And assign all idleHandlers in the message queue to elements in the array.

In comment 3, we get the IdleHandler and then release the IdleHandler reference from the array.

In comment 4, the queueIdle() function of IdleHandler is called and returns a bool.

If queueIdle() returns false, the message queue removes the IdleHandler. If queueIdle() returns true, the message queue keeps the IdleHandler.

From the above code we can see several features of IdleHandler.

  • IdleHandler is used when the message queue is currently empty. If you want to do some processing when the message queue is idle, you can add IdleHandler to the message queue of the current thread and rewrite its message queuequeueIdle()Function.
  • IdleHandler can be used once or multiple times. It depends on itsqueueIdle()Return value of, ifqueueIdle()Return false, then the message queue is exhausted for the first timequeueIdle()The IdleHandler is then removed. If it returns true, that means it will be called every time the message queue is idlequeueIdle().

QueueIdle () is called indefinitely if queueIdle() returns true and there are no idle messages in the queue. To answer this question, we need to recall the function nativePollOnce() in next() introduced earlier. As we mentioned earlier, this function blocks the thread at the native layer and wakes it up when a new event comes in, so we don’t have the problem of infinite calls to queueIdle() that we suspected earlier.

What does IdleHandler do?

After discussing IdleHandler from the source point of view, we understand its features and working principles, and then we will analyze what it does.

In fact, analysis of the role of IdleHandler is also needed from its characteristics and working principle to think. First, IdleHandler is used when there are no messages currently available on the message queue, so we can guess if IdleHandler can be used to perform some optimization actions. For example, handling initialization operations that are not very important. When you start an Activity, some unimportant initialization actions, such as onCreate() and onResume(), may cause the page to display slowly, affecting the startup speed of the application. If these operations are initialized using queueIdle() of IdleHandler, they can be initialized when the thread is idle, speeding up the user experience. However, it is important to note that the IdleHandler depends on the messages in the message queue. If there are messages entering the message queue, the IdleHandler may not be able to execute at all.

Second, we sometimes want to get the width and height of a control, or we want to add a View that depends on the View after a View is drawn, so we can use IdleHandler to get the width and height or add a View. It can also be implemented using view.post (), except that the former is executed when the message queue is idle, while the latter is executed as a message.

IdleHandler is also used in LeakCanary. The source code for LeakCanary 2.0 is written using Kotlin and has not yet been studied. Wait until you read it and then fill it in.

IdleHandler in the system source code

IdleHandler has also been used in the system source code, where GcIdler implements the IdleHandler interface. Let’s take a look at how GcIdle works.

   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");
       }
   }
Copy the code

GcIdler is used to implement mandatory GC operations. When the time now exceeds the MIN_TIME_BETWEEN_GCS == 5000 of the last GC, that is, more than 5 seconds after the last GC, the MANDATORY GC is triggered again, and each GcIdler returns false, only once. How does GcIdler add it? We can look at that.

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

This is triggered by calling scheduleGcIdler(). ScheduleGcIdler (), in turn, is triggered by Handler H in ActivityThread sending a message. Further back, we can see that it was triggered after these two method calls in AMS:

  • doLowMemReportIfNeededLocked
  • activityIdle

So Android uses IdleHandler to enforce GC optimizations when memory is low. Or it might be triggered when the ActivityThread’s handleResumeActivity method is called.

10. Why doesn’t Looper. Loop block the main thread?

IntentService: Thread run (); 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;
}
Copy the code

Looper.loop() is a for loop, and then I realized that the main thread also has Looper, so I found the source of 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");    

}
Copy the code

The exit of the main method is looper.loop (); All events are listeners to Looper, and the main thread is a block.

Android is event-driven, and Looper is a while loop that stops only after the program exits. If Looper dies, no events will occur. Events only block Looper, and Looper does not block events.

To be continued… Welcome to continue to follow