Think twice series is my latest form of learning and summary, focusing on: problem analysis, technical accumulation, vision expansion, about think twice series
This time, you can really read through:
- Design of message queues in the Java layer
- Java layer Looper distribution
- The relationship between the Native layer message queue and the Java layer message queue
- Looper distribution at the Native layer
- The message
- epoll
preface
One of the most important mechanisms in Android has been analyzed for more than a decade, and a great deal of content has been mined. So:
- This mechanism has been compared
familiarity
The reader, in this article, cannot seeSomething new
. - Readers who are not yet familiar with the mechanics of messaging can continue to dig on the basis of the article.
However, after a simple search and analysis, most of the articles revolve around:
- Handler, Looper, MQ
- Upper-layer Handler, Looper, MQ source analysis
. Learning from these perspectives alone is not enough to fully understand message mechanisms.
The essence of this article is a brain explosion, to avoid brain explosion, and to help readers understand the context of the content. Let’s release the brain map:
Brain burst: OS solves interprocess communication problems
In the program world, there are a lot of communication scenarios. Searching our knowledge, there are several ways to solve interprocess communication problems:
This paragraph of content can be extensively read, understand on the line, do not affect to read
- The pipe
Pipe: a half-duplex mode of communication in which data flows only in one direction and can only be used between related processes.
Command flow pipe s_pipe: full-duplex, which can transmit in both directions simultaneously
Named pipe FIFO: a half-duplex communication mode that allows communication between unrelated processes.
- MessageQueue MessageQueue:
A linked list of messages, stored in the kernel and identified by a message queue identifier. Message queue overcomes the disadvantages such as little information transmitted by signal, the pipe can only carry the plain byte stream and the limited buffer size.
- SharedMemory:
Map a section of memory that can be accessed by other processes. This section of shared memory is created by one process but can be accessed by multiple processes. Shared memory is the fastest IPC method, and it is specially designed for the inefficiency of other inter-process communication methods. Often used in conjunction with other communication mechanisms, such as semaphores, to achieve synchronization and communication between processes.
- Semaphore:
Is a counter that can be used to control access to a shared resource by multiple processes. It is often used as a locking mechanism to prevent other processes from accessing a shared resource when a process is accessing the shared resource, enabling the process to exclusively access the resource. Therefore, it is mainly used as a means of synchronization between processes and between threads within the same process.
- Socket Socket:
Unlike other communication mechanisms, it enables process communication between different machines over a network.
- Signal signal:
Notifies the receiving process that an event has occurred. The mechanics are complicated.
As we can imagine, there are also a number of interprocess communication scenarios between Android, where the OS must adopt at least one mechanism to enable interprocess communication.
On closer inspection, it turns out that The Android OS does it in more than one way. Furthermore, Android has developed binders based on OpenBinder for interprocess communication in user space.
Why not just use the existing interprocess communication in Linux
This article also briefly discusses “message queues in kernel space”
Here’s a question we’ll explore later:
Does Android use MessageQueue in the Linux kernel to do things
The design of message mechanism based on message queue has many advantages, and Android has adopted this design idea in many communication scenarios.
Three elements of the message mechanism
Wherever we talk about messaging mechanisms, there are three elements:
The message queue
Message loop (distribution)
Message processing
A message queue is a queue of message objects, and the basic rule is FIFO.
Message circulation (distribution) is basically a general mechanism, which uses an infinite loop to continuously fetch messages from the head of the message queue and distribute them for execution
Message processing, it has to be mentioned here that messages come in two forms:
- The Enrichment itself has complete information
- Query-back Information is incomplete and needs to be checked Back
The trade-off between the two mainly depends on the game between the cost of generating messages and the cost of checking back information.
When the information is complete, the receiver can process the message.
Message queues in the Android Framework
There are two message queues in the Android Framework:
- Java layer
frameworks/base/core/java/android/os/MessageQueue.java
- Native layer
frameworks/base/core/jni/android_os_MessageQueue.cpp
MQ for the Java layer is not an implementation of a data structure in the Jdk such as List or Queue.
I downloaded a copy of Android 10 source code, not long, you can read the full read.
It is not difficult to understand: user space will receive messages from kernel space, as shown in the figure below, this part of the message is first learned by the Native layer, so:
- through
Native layer
Set up a message queue, which has the basic capabilities of a message queue- using
JNI
Get throughJava layer
和Native layer
的The Runtime barrier
In the Java layermapping
Outgoing message queue- Applications are built on top of the Java layer, where messages are implemented
distribution
和To deal with
PS: In the Era of Android 2.3, message queue implementation is in the Java layer, as to why 10 years ago changed to native implementation, speculation and CPU idling, the author did not continue to explore, if there are readers to understand, I hope you can leave a message to help me.
PS: And a classicSystem startup architecture diagram
I can’t find it. This is more intuitive
Code parsing
We simply read and analyze the MQ source code in Native
Native layer message queue creation:
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(a);if(! nativeMessageQueue) {jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
Copy the code
Simply create a Native layer message queue, throw an exception message and return 0 if the creation fails, otherwise convert the pointer to a Java long value and return it. Of course, it will be held by MQ in the Java layer.
Constructor of the NativeMessageQueue class
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread(a);if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper); }}Copy the code
Looper here is Looper in the native layer. The object instance is obtained through the static method Looper::getForThread(). If the object instance is not obtained, the instance is created and set through the static method.
Take a look at the Native methods used in the Java layer MQ
class MessageQueue {
private long mPtr; // used by native code
private native static long nativeInit(a);
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
}
Copy the code
Corresponding signature:
static const JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit"."()J", (void*)android_os_MessageQueue_nativeInit },
{ "nativeDestroy"."(J)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce"."(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake"."(J)V", (void*)android_os_MessageQueue_nativeWake },
{ "nativeIsPolling"."(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
{ "nativeSetFileDescriptorEvents"."(JII)V",
(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};
Copy the code
MPtr is a mapping of MQ memory addresses in the Native layer to the Java layer.
The Java layer determines if MQ is still working:
private boolean isPollingLocked(a) {
// If the loop is quitting then it must not be idling.
// We can assume mPtr ! = 0 when mQuitting is false.
return! mQuitting && nativeIsPolling(mPtr); }Copy the code
static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->getLooper() - >isPolling(a); }Copy the code
/** * Returns whether this looper's thread is currently polling for more work to do. * This is a good signal that the loop is still alive rather than being stuck * handling a callback. Note that this method is intrinsically racy, since the * state of the loop can change before you get the result back. */
bool isPolling(a) const;
Copy the code
Wake up Native layer MQ:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake(a); }void NativeMessageQueue::wake(a) {
mLooper->wake(a); }Copy the code
Native layer Poll:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL; }}Copy the code
How does a Looper at the Native layer distribute messages
//Looper.h
int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
inline int pollOnce(int timeoutMillis) {
return pollOnce(timeoutMillis, NULL.NULL.NULL);
}
/ / implementation
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p".this, ident, fd, events, data);
#endif
if(outFd ! =NULL) *outFd = fd;
if(outEvents ! =NULL) *outEvents = events;
if(outData ! =NULL) *outData = data;
returnident; }}if(result ! =0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning result %d".this, result);
#endif
if(outFd ! =NULL) *outFd = 0;
if(outEvents ! =NULL) *outEvents = 0;
if(outData ! =NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis); }}Copy the code
The stranded Response of the Native layer is handled first, and pollInner is called. The details here are complicated, and we will conduct brain burst in the analysis of Native Looper later.
Before we get into the details here, we know that when a method is called, it is blocked, which in plain English means that the caller is waiting until the method returns.
Native void nativePollOnce(long PTR, int timeoutMillis); The process is blocked.
At this point, let’s read the message fetching of MQ at the Java layer again: the code is longer, and the main points are commented directly in the code.
Before we look at it, let’s think about the main scenarios purely from a TDD perspective: of course, they don’t necessarily fit with Android’s existing design
- Whether the message queue is working
- At work, a message is expected back
- No, null is expected
- Message queues at work
The current
Is there any message?- No message, block or return null? If null is returned, it is needed externally
Stay idle
orWake up the mechanism of
To support normal operations. From the perspective of encapsulation, shouldStay idle
And solve the problem by yourself - There is news
- special
Internal functional message
Is expected to be handled internally by MQ - Message that has reached processing time, return message
- Before the processing time,If it’s all sorted,
Idling remains blocked
orReturn to silence and set wake up
? Expectations, as discussed earlierStay idle
- special
- No message, block or return null? If null is returned, it is needed externally
class MessageQueue {
Message next(a) {
// 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.
// 1. If the native message queue pointer map is 0, that is, a virtual reference, it indicates that the message queue has exited and there is no message.
// Returns null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 2. Infinite loop, when to get a message that needs to 'distribute processing', keep idle
for (;;) {
if(nextPollTimeoutMillis ! =0) {
Binder.flushPendingCommands();
}
// 3. Call the native layer method, pollMessage, notice that the message still exists in the native layer
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;
//4. If a barrier is found, look for the next possible asynchronous message in the queue
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) {
// 5.
// If the message has not arrived at the agreed time, set a maximum time difference for 'next wake up'
// Otherwise 'maintain singly linked list information' and return message
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 {
// The 'time to process' message was found. 'Maintain singly linked list information' and return message
// 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();
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Processes whether the message queue needs to be stopped
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// Maintain the IDLEHandler information that needs to be processed next,
// If there is no IDLEHandler, then go straight to the next round of message fetching
// Otherwise handle IDLEHandler
// 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);
}
/ / IDLEHandler processing
// 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
Java laminates the message
This is simpler when the message itself is valid and the message queue is still working. From the perspective of TDD:
- If the message queue does not have a header, the expectation is directly used as a header
- If there is a head
Message processing time
beforeFirst message
Or messages that need to be processed immediately, as the new header- Otherwise, in accordance with the
The processing time
Insert it into place
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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
There’s a separate brain explosion behind the barrier, and I’ll leave the rest of it alone
Java layer message distribution
In this section, we start with message distribution. We’ve already looked at MessageQueue. Message distribution is the process of constantly pulling messages from MessageQueue and assigning them to handlers. Looper did the job.
We already know that the Native layer also has Looper, but it’s not hard to understand:
- Message queue needs
bridge
Connect the Java and Native layers - Which only needs to
On its own end
To process its own message queue distribution
So, when we look at message distribution in the Java layer, we look at Looper in the Java layer.
Focus on three main approaches:
- Go to work
- work
- I come home from work
Prepare for work
class Looper {
public static void prepare(a) {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if(sThreadLocal.get() ! =null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(newLooper(quitAllowed)); }}Copy the code
There are two caveats:
- I’m out the door, and I can’t go out until I get in again. Similarly, one Looper per thread is sufficient and there is no need to build another as long as it is alive.
- To the person responsible, a Looper serves a Thread, which requires
registered
, representing theA Thread
It has been served by itself. Use ThreadLocal, because multithreaded access sets, ‘always need to be considered
Instead of competing with each other, we simply separate ThreadLocal from each other and encapsulate ThreadLocal
Work in the loop
Note that the work is distributed and does not need to be handled by yourself
- There is no
registered
Naturally, no one could be found to take the job. - Already in the work do not rush, rush will lead to work errors, order problems.
- The job is to take it out
The boss
—MQ
的instruction
—Message
And toResponsible person
—Handler
To process and record the information 007
Never sleep,When MQ no longer sends messages
There’s no work to do. Let’s all go home
class Looper {
public static void loop(a) {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow".0);
boolean slowDeliveryDetected = false;
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);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if(traceTag ! =0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if(observer ! =null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// Notice here
msg.target.dispatchMessage(msg);
if(observer ! =null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if(observer ! =null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false; }}else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true; }}}if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
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
The quit/quitSafely from work
This is a rough behavior. MQ can’t work normally if he leaves Looper. That is to say, quitting work means quitting
class Looper {
public void quit(a) {
mQueue.quit(false);
}
public void quitSafely(a) {
mQueue.quit(true); }}Copy the code
Message Handler
It’s a little bit clearer here. Apis are basically divided into the following categories:
User oriented:
- Create Message by Message
The flyweight pattern
- Send a message, and note that postRunnable is also a message
- Remove the message,
- Exit etc.
Message oriented processing:
class Handler {
/** * Subclasses must implement this to receive messages. */
public void handleMessage(@NonNull Message msg) {}/** * Handle system messages here. * Looper
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}}Copy the code
If the handleMessage is not overwritten, the message is dropped.
The message sending part can be combed with the following figure:
Summary: At this point, we have a complete understanding of the messaging mechanism of the Framework layer. Previously we combed:
- Both the Native layer and the Java layer have message queues, and there is a corresponding relationship through JNI and pointer mapping
- Native layer and Java layer MQ
The general process of message retrieval
- How does Java layer Looper work
- Java Handler overview
From what we have already discussed, we can summarize: From Java Runtime:
- The message queuing mechanism serves
Thread level
That is, a thread can have a working message queue or not.That is, a Thread has at most one working Looper.
- Looper and the Java layer MQ
One to one correspondence
- Handler is the entry point to MQ, as well
The message
The handler- Message –
Message
The application ofThe flyweight pattern
The information of oneself is sufficientSelf consistent
, the cost of creating messages is large, so the enjoy element pattern is used to reuse message objects.
Now let’s continue to explore the details and solve the confusion left by the previous ambiguities:
- Type and nature of the message
- The pollInner of a Native layer Looper
Type and nature of the message
Several important member variables in message:
class Message {
public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
/*package*/ int flags;
public long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
}
Copy the code
Where the target is the target, if there is no target, it is a special message: the synchronization barrier is a barrier;
What is the message identifier Arg1 and Arg2 are inexpensive data that can be placed in the Bundle data if insufficient to represent information.
ReplyTo and obj are used when sending messages across processes, so leave it at that.
Flags indicates the status of the message, such as whether it is in use or whether it is a synchronous message
The synchronization barrier mentioned above, or barrier, prevents subsequent synchronization messages from being retrieved, as you saw earlier in the Next method of MQ in the Java layer.
We also remember that in the next method, an infinite loop is used to try to read a message that satisfies the processing condition. If the message fails to be read, the caller (Looper) will be blocked all the time because of the existence of the infinite loop.
At this point, a conclusion can be verified that messages can be divided into three types according to functional classification:
- Ordinary message
- Sync barrier message
- Asynchronous messaging
The synchronization message is an internal mechanism. After the barrier is set, remove the barrier at a proper time. Otherwise, common messages will never be processed. To cancel the barrier, use the token returned when the barrier is set.
Native layer which
Looper at the Native layer is interesting to see what it does at the Native layer.
Those interested in the full source code can see it here and read it in the excerpts below.
As mentioned earlier in Looper’s pollOnce, pollInner is called to retrieve the message after processing the dormant Response
int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d".this, timeoutMillis);
#endif
// Adjust the timeout based on when the next message is due.
if(timeoutMillis ! =0&& mNextMessageUptime ! = LLONG_MAX) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d".this, mNextMessageUptime - now, timeoutMillis);
#endif
}
// Poll.
int result = ALOOPER_POLL_WAKE;
mResponses.clear(a); mResponseIndex =0;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
/ / note 1
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// Acquire lock.
mLock.lock(a);/ / note 2
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error, errno=%d", errno);
result = ALOOPER_POLL_ERROR;
goto Done;
}
/ / note 3
// Check for poll timeout.
if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - timeout".this);
#endif
result = ALOOPER_POLL_TIMEOUT;
goto Done;
}
/ / note 4
// Handle all events.
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - handling events from %d fds".this, eventCount);
#endif
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken(a); }else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); }}else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
/ / note 5
// Invoke pending message callbacks.
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
// Remove the envelope from the list.
// We keep a strong reference to the handler until the call to handleMessage
// finishes. Then we drop it so that the handler can be deleted *before*
// we reacquire our lock.
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock(a);#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d".this, handler.get(), message.what);
#endif
handler->handleMessage(message);
} // release handler
mLock.lock(a); mSendingMessage =false;
result = ALOOPER_POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break; }}// Release lock.
mLock.unlock(a);/ / note 6
// Invoke all response callbacks.
for (size_t i = 0; i < mResponses.size(a); i++) { Response& response = mResponses.editItemAt(i);
if (response.request.ident == ALOOPER_POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p".this, response.request.callback.get(), fd, events, data);
#endif
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = ALOOPER_POLL_CALLBACK;
}
}
return result;
}
Copy the code
It has a note on it
- 1 Epoll mechanism, wait
mEpollFd
Generates an event, and the wait has a timeout. - 2,3,4 are the three outcomes of waiting,
goto
Statement can jump directly totag
处 - 2 test poll
Whether the error
If so, jump to Done - 3 test pool
If the timeout
If so, jump to Done - 4 Process all events after epoll
- 5 Handle the callback of pending messages
- 6 Handles all Response callbacks
And we can see the following results are returned:
- ALOOPER_POLL_CALLBACK
A Response with a pending message or request.ident value of ALOOPER_POLL_CALLBACK was processed. If not:
- ALOOPER_POLL_WAKE Wakes up normally
- ALOOPER_POLL_ERROR epoll error
- ALOOPER_POLL_TIMEOUT epoll timeout
Look for enumeration values:
ALOOPER_POLL_WAKE = -1,
ALOOPER_POLL_CALLBACK = -2,
ALOOPER_POLL_TIMEOUT = -3,
ALOOPER_POLL_ERROR = -4
Copy the code
In the stage summary, we conducted a brain burst on the news and Native layer’s pollInner, leading to the epoll mechanism.
Looper distribution on the Native layer has a lot to do with it, but we can’t wait to do it with epoll.
## Brain Burst: THE I/O model in Linux
Using Libevent and LibeV to Improve network application Performance — A history of I/O Model evolution by Hguisu
PS: Some of the images in this section are directly quoted from the article. I took the liberty of not looking for the original content and citing it
Blocking I/O model diagram: The process that occurs in the kernel to wait for and copy data when recv() is called
The implementation is very simple, but there is a problem: the blocking prevents the thread from performing any other computation. In the context of network programming, you need to use multiple threads to improve the ability to handle concurrency.
Note, don’t use AndroidThe hardware is triggered by clicking on the screen
To correspond to thisNetwork concurrent
These are two different things.
If multiple processes or multiple threads are used to realize concurrent responses, the model is as follows:
So far, we’ve been looking at the I/O blocking model.
Brain-burst, blocking is the process of calling a method and waiting for the return value, as if the content executing within the thread is stuck here.
If you want to eliminate this lag, instead of calling methods to wait for the I/O result, return immediately!
Here’s an example:
- You go to a suit store, you get your suit made, you get your size, you sit in the store and you wait until it’s ready and you get it to you, that’s blocking, that can kill you;
- When you go to a suit store and have your suit made, you are told not to wait for days until you have time to check it out. This is non-blocking.
After changing to non-blocking model, the response model is as follows:
Understandably, this approach requires customers to poll. It’s not customer friendly, but it doesn’t hurt the store at all, and it makes the waiting area less crowded.
Some suit shops have reformed to be more customer-friendly:
Go to the suit shop to customize the suit, determine the style and size, leave the contact information, such as the suit is done to contact the customer, let him to pick up.
This becomes the select or poll model:
Note: The reformed suit shop needs to add an employee, the user thread identified in the figure, whose job is:
- Record customer orders and contact information at the front desk
- Take a record of
The order
To find the production room,Constantly check
If the order is completed, the completed one can be picked up and contacted with the customer.
Also, when he went to see the order completed, he couldn’t record the customer information at the front desk, which meant he was blocked and other work had to be put on hold.
This approach, for production purposes, is not so different from the non-blocking model. We added a salesperson, but we solved it with a salesperson who used to go to the production room and say, “Is the order ready?” The problem.
It is worth mentioning that in order to improve the quality of service, this employee needs to record some information every time he goes to the production room to ask for an order:
- Whether the order completion is answered when asked;
- Whether the answer lies; Etc.
Some stores have a record book for each of the different assessment items, similar to the SELECT model
Some stores use only one record book, but the book can use forms to record various items, similar to the poll model
Select model and poll model have a high degree of approximation.
Before long, the boss found that the clerk’s work efficiency was a little low. He always took an order book and went to ask the order again. It was not that the employee was not diligent, but that there was something wrong with the mode.
So the boss made another reform:
- in
The front desk
和Made between
Add a message pipeline between. - When the production room has progress to report, it sends a letter to the front desk with the order number on it.
- The front desk employee goes directly to the corresponding order.
So this becomes the Epoll model and solves the traversal efficiency problem of the select/poll model.
As a result, the front desk staff no longer need to follow the order book from top to bottom. Improved efficiency, as long as nothing happens, the front desk staff can gracefully paddle.
Let’s look at the constructor of NativeLooper:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result ! =0."Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result ! =0."Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result ! =0."Could not make wake write pipe non-blocking. errno=%d",
errno);
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0."Could not create epoll instance. errno=%d", errno);
struct epoll_event eventItem;
memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result ! =0."Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
Copy the code
conclusion
I believe that here, you have their own understanding of all kinds of problems. In accordance with the convention, or to summarize, because this is a brain storm, so the mind is relatively jumping, content before and after the relationship is not obvious.
Let’s combine a question to point out the context.
What about Java layer Looper and MQ that uses an infinite loop but does not “block” the UI thread/does not cause ANR/and can still respond to click events
- Android is based on
event-driven
And built upperfect
Message mechanism - The Message mechanism of the Java layer is only a part of it, which is responsible for message queue-oriented processing
Message queue management
.Message delivery
.Message processing
- Looper’s infinite cycle is guaranteed
The message queue
的Message delivery
Always in active operation, without a loop, the distribution stops. - The MessageQueue
Infinite loop
To ensure theLooper can get valid messages
, guaranteeing LooperIt runs as long as there's a message
When a valid message is found, the loop is broken. - And the Java layer MessageQueue calls the Native layer MQ through JNI in an infinite loop in the next() method
pollOnce
, drives the Native layer to process Native layer messages - It’s worth mentioning that everything the UI thread handles is also message-based, whether it’s updating the UI, responding to click events, etc.
Therefore, it is the infinite loop after Looper performs loop() that ensures the normal execution of various tasks of the UI thread.
Then there’s ANR, which is Android’s way of checking that the main thread messaging mechanism is working properly and healthily.
The main thread Looper needs to utilize message mechanism to drive UI rendering and interactive event processing. If the execution of a message or the business derived from it occupies a lot of time on the main thread, the main thread will be blocked for a long time and the user experience will be affected.
Therefore, ANR detection adopts a mechanism of planting time bombs, which must rely on the efficient operation of Looper to eliminate the time bombs previously installed. But this time bomb is more interesting, it will be discovered to detonate.
When it comes to responding to click events, similar events always start from the hardware, go to the kernel, and then to the user space through inter-process communication. These events exist in the form of messages in the Native layer. After processing, they show:
The ViewRootImpl receives the input from the InputManager and handles the event
Here we borrow a diagram to summarize the entire message mechanism flow:
Image from Android7.0 MessageQueue in detail by Gaugamela
PS: This article is very long, long content, time-consuming, about 10 days to write, there are still a lot of content not to enjoy. For example: “In what cases does the Java layer use JNI to call Native layer awakenings, and why?” And so on.
But given the space, I decided not to dig any further.