Links to related articles:

1. Android Framework – Learning Launcher

2. Android Handler communication – source code analysis and handwritten Handler framework

3. Android Handler communication – Thoroughly understand the communication process of the Handler

Related source files:

/frameworks/base/core/java/android/os/Handler.java
/frameworks/base/core/java/android/os/MessageQueue.java
/frameworks/base/core/java/android/os/Looper.java

framework/base/core/jni/android_os_MessageQueue.cpp
system/core/libutils/Looper.cpp
system/core/include/utils/Looper.h
Copy the code

Cross-process communication is generally driven by binder in Android application development process. Binder driver source code analysis, you can read some of my previous articles. We’re going to talk about communication between threads, and we’re going to talk about Handler. But there are some details that you may not know:

  • Based on Handler can do performance detection
  • Performance optimizations can be made based on handlers
  • How can you do cross-process communication based on Handler?

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler This is one of the questions I came across in the headline interview. Let’s start by thinking for ourselves: if we had to delay processing this message for 2s, how would our implementation handle it? (Think for five minutes) Let’s have a look at the source code:

MessageQueue () {final long PTR = mPtr; if (ptr == 0) { return null; } int nextPollTimeoutMillis = 0; for (;;) NativePollOnce (PTR, nextPollTimeoutMillis); Final long now = systemclock. uptimeMillis(); synchronized (this) {final long now = systemclock. uptimeMillis(); Message prevMsg = null; Message msg = mMessages; . if (msg ! = null) {if (now < msg.when) { NextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); } else {// Return mBlocked = false immediately without delaying the fetch when the time has passed; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; MSG. MarkInUse (); return msg; NextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; } // There is no idle IdleHandler that needs to be executed. If (pendingIdleHandlerCount <= 0) {mBlocked = true; continue; }... } pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; }}Copy the code

Through source code analysis, we found that the processing process of the message is to compare the execution time of the current message with the current system time. If the execution time is less than or equal to the current system time, the message will be returned to execute immediately. If the execution time is greater than the current system time, the nativePollOnce method will be invoked to delay waiting to be woken up. If the message queue is empty, set the wait time to -1. The source code for idleHandlers, asynchronous messages, and message barriers has been ignored by me. You can read the analysis later in this article. We follow up to the android_os_MessageQueue_nativePollOnce method of the Native layer.

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, Jint timeoutMillis) {// Address translation into native layer MessageQueue object NativeMessageQueue* NativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, Int timeoutMillis) {// Call the pollOnce method of the native Looper object mLooper->pollOnce(timeoutMillis); } inline int pollOnce(int timeoutMillis) { return pollOnce(timeoutMillis, NULL, NULL, NULL); PollOnce (int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0; pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0; for (;;) {... if (result ! = 0) {... return result; } result = pollInner(timeoutMillis); [6]}} int Looper::pollInner(int timeoutMillis) {... int result = POLL_WAKE; mResponses.clear(); mResponseIndex = 0; mPolling = true; Struct epoll_event eventItems[EPOLL_MAX_EVENTS]; // Waiting for an event to occur or a timeout, the nativeWake() method writes characters to the pipe writer, and the method returns; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); . return result; }Copy the code

Looper.loop() is not an infinite loop. If it is a simple infinite loop, it will consume more performance. In fact, the native layer calls epoll_wait to wait, timeoutMillis, here are three cases: 0, -1 and >0. If the value of epoll_wait is -1, the method will continue to wait. If the value of epoll_wait is 0, it will return immediately. If the value of epoll_wait is >0, the method will return immediately. Let’s look at the wake up method:

boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { if (mQuitting) { ... // We can assume mPtr ! // If (needWake) {nativeWake(mPtr); // If (needWake) {nativeWake(mPtr); } } return true; } void NativeMessageQueue::wake() { mLooper->wake(); } void Looper::wake() { uint64_t inc = 1; Ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))); if (nWrite ! = sizeof(uint64_t)) { if (errno ! = EAGAIN) { ALOGW("Could not write wake signal, errno=%d", errno); }}}Copy the code

Let’s summarize the processing of delayed messages: First of all, we actually have handler. CPP, MessageQueue. CPP and Looper. Only MessageQueue. Java and MessageQueue. CPP are associated. When the upper layer calculates the delay time, we call the native layer nativePollOnce method, and its internal implementation uses Epoll to process the delayed wait return (version 6.0).

When a new message is inserted, the Native layer’s nativeWake method is called. This method simply writes a simple int -1 to the file descriptor to wake up the previous epoll_wait method. In fact, it is the wait to wake up nativePollOnce. For handler. CPP, MessageQueue. CPP, and Looper. CPP objects, check out the previous article if you’re interested.

This shows that a simple interview question from a large company can filter out many people. I used to hear students complain that why do you ask me these questions, but they are not used in development. So let’s take a look at some of the source code details that can be used in development, but that we may not be familiar with.

  • IdleHandler

We are in the actual development process in order not to block the main thread of other tasks, may have to wait the main thread is free to perform a particular method, for example, we wrote a performance monitoring tool that triggered the certain conditions or time to collect some information, this is certainly not to influence the main thread, we found some performance monitoring tools, Instead, it can lead to worse overall application performance and even lag. How do I know if the main thread is free?

AddIdleHandler (new messagequeue.idleHandler () {@override public Boolean queueIdle() {// Start doing something else, Return false; return false; return false; return false; return false; }});Copy the code

This code is very simple, but we usually may be less contact, let’s take a look at the source:

public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } } Message next() { int pendingIdleHandlerCount = -1; // -1 only during first iteration for (;;) Synchronized (this) {if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Loop through all IdleHandler callbacks for (int I = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; QueueIdle Boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; }}Copy the code
  • Main thread method time consuming

In the actual application development process, we may find the interface would be caton problem, especially in some complex scenarios, such as animation studio all kinds of presents or complex list slide and so on, of course there will be a lot of the causes of caton, different models, for example, in our mobile phones as smooth as silk, on the test of mobile phone is like not to eat, Of course, it’s all about CPU utilization, main-thread time-consuming operations, disk I/O operations, and so on. In the actual process, it is quite complicated to check for stuck. Here we mainly listen to check whether the main thread has method time:

@Override public void onCreate() { super.onCreate(); Looper.getMainLooper().setMessageLogging(new Printer() { long currentTime = -1; @Override public void println(String x) { if (x.contains(">>>>> Dispatching to ")) { currentTime = System.currentTimeMillis(); } else if (x.contains("<<<<< Finished to ")) { long executeTime = System.currentTimeMillis() - currentTime; If (executeTime > 60) {log. e("TAG", "main thread time "); }}}}); }Copy the code

Of course, this can only monitor the main thread has a method time-consuming and cause the lag, may be currently these code can not track which method time-consuming caused the lag, but based on these code to achieve which method time-consuming should be Soeasy, here we do not do too much extension mainly look at the principle:

/** * 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; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // Final Printer logging = me.mlogging; if (logging ! = null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); Distribute the try {} / / execution news MSG. Target. DispatchMessage (MSG); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }} if (logging! = null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }... }}Copy the code

The principle is actually very simple, on the asynchronous message, message barrier, cross-process communication and monitoring a specific method of time, in the back of the source analysis will be repeatedly mentioned, you can continue to be interested in attention. Happy National Day ~

Video address: pan.baidu.com/s/1fLq65bUn…

Video password: GKPV