Android messaging mechanism
Message-driven is a mode in which processes or threads run. Various events, both internal and external, can be placed in message queues to be processed in order. This pattern is particularly well suited for handling a large number of interactive events.
The UI thread of Android application also adopts message-driven mode. All external keystroke events, touch screen events, various system intents and broadcasts are converted into internal messages, which are then distributed and processed in the main thread.
A message model
Today’s operating systems are generally message-driven. The Windows operating system is the classic message-driven type. However, Android’s message processing mechanism is not quite the same as Windows’s, so let’s take a look at the comparison between the two:
Above:
- in
Windows
In the message processing model ofSystem message queue
This queue is the core of the entire process. Almost all actions are converted into messages and then put into this queue. Messages can only be processed in theThe main thread
To complete. Android
There is no global message queue,The message queue
It’s associated with a thread. Each thread has at most oneThe message queue
Messages are fetched and processed in the thread.
By comparison:
Windows
The message model is relatively simple- Because of the global queue
- Sending messages is simple and convenient
- but
- It can become a bottleneck, and if a message is not completed in a timely manner, the entire process will hang
- Global queues, which require frequent synchronization, increase system overhead
- Because of the global queue
Android
The message model of- It’s much more complicated
- Must be constructed for threads before use
The message queue
- Send messages must first get
The message queue
theHandler
object
- Must be constructed for threads before use
- But because the
The message queue
In each thread- There is no additional overhead for intra-thread messages
- It’s much more complicated
Let’s take a look at the Android classes related to messaging, mainly:
Looper
Class:Looper
Object is a message loop handler for a thread, and only one per threadLooper
Object.Looper
There is an internal message queueMessageQueue
Messages from all threads are stored in this queue.- When a new thread is created, the system does not immediately create one for that thread
Looper
Object that needs to be created by the program itself Android
On startup, will beThe UI thread
Created aLooper
object
Handler
Class: The object isMessage
The receiver and handler of.- You can use
Handler
Object toMessage
Add to message queue - through
Handler
The callback method ofhandleMessage
In the message queueMessage
For processing Handler
Object is constructed with aLooper
Objects are associated togetherHandler
andLooper
It’s many-to-one. MultipleHandler
Can be the same asLooper
Objects are associated together
- You can use
Message
Classes andMessageQueue
:Message
Is the carrier of messages.Message
inheritedParcelable
Class, this shows thatMessage
Objects can be accessed throughbinder
To send across processesMessageQueue
storeMessage
A linked list of objects
The relationship between these classes is shown below:
To understand which kind of
The main member variables and methods of the Looper class are as follows:
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
public static void prepare(a) {
prepare(true);
}
private static void prepare(boolean quitAllowed) {... }public static void prepareMainLooper(a) {... }public static Looper getMainLooper(a) {... }public static void loop(a) {... }}Copy the code
- There can only be one per thread
Looper
Class Looper
Class instance objects must passprepare()
createprepare()
Will create aLooper
Object and save to a static variablesThreadLocal
In the
Note that multiple calls to prepare() in a thread will throw an exception
private static void prepare(boolean quitAllowed) {
if(sThreadLocal.get() ! =null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code
Once an instance object of the Looper class is created, it can be obtained through myLooper()
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
The static sThreadLocal variable is of type ThreadLocal
, which implements thread-local storage by associating the object to be saved with the thread ID.
The getMainLooper() method of the Looper class returns the main thread’s Looper object.
public static Looper getMainLooper(a) {
synchronized (Looper.class) {
returnsMainLooper; }}Copy the code
The prepareMainLooper() function is not intended for use by the application layer. The prepareMainLooper() function is not intended for use by the application layer.
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper(a) {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code
Once you have a Looper object, you can call the loop() method of the Looper class to enter the message loop.
Let’s look at the typical use of Looper on the official website
class LooperThread extends Thread {
public Handler mHandler;
public void run(a) {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here}}; Looper.loop(); }}Copy the code
The loop() method is used to distribute messages in the message queue. The code for this function is as follows:
public static void loop(a) {
final Looper me = myLooper(a);/ /... Some exception judgments about Looper objects
final MessageQueue queue = me.mQueue;
/ /... Some thread ID related information processing
for (;;) {
Message msg = queue.next(a);// might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
/ /...
msg.target.dispatchMessage(msg);
/ /...
msg.recycleUnchecked();
}
}
Copy the code
loop()
The method loops fromMessageQueue
The message is fetched from the queue and then distributed- Message distribution pass
Message
The object of thetarget
The variable is done. This variable is of typeHandler
Message
Is the message carrier, the sender of the message to be delivered in theMessage
In the object- while
Message
When an object is created, it needs to specify its processing objecttarget
, that is,Handler
Understand the Handler class
The Handler class is responsible for sending and processing messages.
You can use only one Handler object to process all messages in a thread, or you can use more than one.
To construct a Handler object, two arguments are required:
- The thread
Looper
object- Used to specify which thread to give to
Looper
Object sending messages - necessary
- Used to specify which thread to give to
- A handler for the message
callback
- through
callback
Implements centralized processing of messages - Also can put the
callback
Directly onMessage object
In the - Not necessary
- through
Handler class is a part of the message framework, in the message definition and response design is very flexible, specific message type and response logic need to be completed in the application layer code
- The traditional
A message model
The types of messages that can be processed by a thread must be defined so that a user can only use them to send messages to a thread Android
The definition and processing of messages are completely separate, and threads provide only oneThe message queue
andMessage delivery
The operating environment of
For example, the main thread of Android is implemented in the Framework, but we can use the following method to construct a message to the main thread with a callback method
public static Message obtain(Handler h, Runnable callback)
Copy the code
Thus, the callback method will be executed on the main thread
The message sending interface of the Handler class can be divided into two categories:
- One kind is
send
class - One kind is
post
class
However, before diving into the two interfaces of the Handler class, let’s take a quick look at the key interfaces in the MessageQueue class:
- And when you look at it,
MessageQueue
Class has only one interface for inserting messages - is
boolean enqueueMessage(Message msg, long when)
The MessageQueue class has only one interface. Where does the Handler class have so many instructions to send?
Not so fast. Let’s see what these two are?
send
The class interface
Handler: send (); send ();
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
Copy the code
If you just follow the code, it ends up in the enqueueMessage method in the Handler class
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
- The so-called
send
In fact, it’s just theMessage
Inserted into theMessageQueue
, and specify the processing time of the message - If the specified time is 0, immediate processing is required.
MessageQueue
It looks up the message to the head of the queue
In addition to message parameter MSG, the insertion method in MessageQueue has only one time parameter, uptimeMillis.
Therefore, the Handler class in the interface to send messages although many, but are in the time to do action, so that the application is easy to use. The send interface is summarized as follows:
- If you want immediate action, but do not plan to jump the queue, use
sendMessage
- If it is urgent and you want to deal with it as soon as possible, use
sendMessageAtFrontOfQueue
- If you want to delay processing for a period of time, use
sendMessageDelayed
- If you want to process at a specified time, use
sendMessageAtTime
- If the message defined is only
Message ID
, and can be used without additional parameterssendEmptyMessage
post
The class interface
In fact, you can see what the POST interface is doing when you see the implementation
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
/ /... Omit some of the POST functions
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}
public final boolean postAtFrontOfQueue(Runnable r)
{
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
Copy the code
From the implementation of the code
- these
post
Type methods are also usedsend
Method of type - Only in the
send
Type method addedRunnable
The parameters of the class - through
getPostMessage
Let’s pack one upMessage
object - At last,
send
Type to add an object toMessageQueue
In the
Here we can summarize the features of the two interfaces:
post
The class interface is used to send messages with processing methods (Runnable
Object)send
The class interface is used to send traditional messages with message ids
Do you rememberLooper
theloop
It’s called in the loopmsg.target.dispatchMessage
Function?
Let’s look at the implementation, the comments are very detailed yo:
// Note that there are three callbacks in this code
public void dispatchMessage(Message msg) {
if(msg.callback ! =null) {
// Here is the first callback function
// If the message contains a Runnable object
// Execute the run function in the message directly
/ / and exit
handleCallback(msg);
} else {
// Here is the second callback function
// The Handler.Callback object that may be passed in when we create the Handler object
if(mCallback ! =null) {
// If the handler. Callback object is not empty, the Callback function is executed
if (mCallback.handleMessage(msg)) {
// Please note here
// If true is returned in the Handler.Callback Callback
// The third callback will not be executed
return; }}// Here is the third callback function
// When handler. Callback is empty
// Or execute this function when the handler. Callback object returns false
// This function is an empty method body
// Subclasses of Handler can override this method to handle messageshandleMessage(msg); }}private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(Message msg) {}Copy the code
From the dispatchMessage code:
- The message is given priority to the callback method inherent in the message
- Otherwise, if
Handler
A callback method is defined that is called first for processing - if
Handler
The callback method is not processed and will be calledHandler
Their ownhandleMessage
MessageQueue
Class foreshadowing content
This section complements the study of MessageQueue class. It’s not much, but focuses on a setAsynchronous function of the Message class.
If you don’t know the setAsynchronous function of the Message class, you might be confused by some of the logic behind the MessageQueue class
In the Android Message class, there is a setAsynchronous(Boolean async) method that sets a flag bit as follows:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else{ flags &= ~FLAG_ASYNCHRONOUS; }}Copy the code
So how do I use this function?
- in
MessageQueue
There is a method calledpostSyncBarrier()
- Calling this method inserts a null in the message queue
Handler
Object messages - There is no
Handler
Object is calledSyncBarrier
- Calling this method inserts a null in the message queue
MessageQueue
Will pause processing in the queueSyncBarrier
Future news- It’s like a group of people waiting in line to buy a ticket, and someone puts a sign in the line
From here, stop selling
- It’s like a group of people waiting in line to buy a ticket, and someone puts a sign in the line
- However, if there are still messages that need to be processed, you can use it
setAsynchronous(boolean async)
Method to mark the message MessageQueue
When this flag is detected, the message is processed normally- Other unmarked messages are paused until they pass
removeSyncBarrier
Remove theSyncBarrier
Good drop, foreshadowing completed, continue to learn ~
Analysis of theMessageQueue
class
MessageQueue
Class constructor
Let’s first look at the constructor of the MessageQueue class
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Copy the code
Very succinct… :
MessageQueue
Object is constructed to call local methodsnativeInit()
To complete thenativeInit()
Method correspondingJNI
The function isNative layer
theandroid_os_MessageQueue_nativeInit()
function- I still call it
Native layer
Oh, that’s really bad
- I still call it
Android_os_MessageQueue_nativeInit ()
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(a);/ /...
}
Copy the code
The android_os_MessageQueue_nativeInit() function creates a local NativeMessageQueue object. Let’s look at the NativeMessageQueue constructor again:
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
NativeMessageQueue
The constructor simply creates a localLooper
Class objects.
From the code of the NativeMessageQueue class, it is essentially a proxy class. It turns calls to the Java layer into calls to the Looper class of the Native layer
The Looper class in the Native layer also implements a complete message processing mechanism. But there is no direct relationship between the Java layer Looper class and the Native layer Looper class.
MessageQueue uses the Looper class of the Native layer, but only uses its wait/wake mechanism. The rest, such as message queuing, is implemented in the Java layer.
Therefore, for JNI calls in MessageQueue, we can directly analyze the Looper class of the Native layer. Let’s first briefly look at the constructor of the Looper class of the Native layer:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(- 1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
// Create an eventfd object by calling eventfd
// This function creates a file descriptor for event notification.
// Set the flag to
// EFD_CLOEXEC: the file is set to O_CLOEXEC and does not inherit the parent's file descriptor when the child process (fork) is created.
// EFD_NONBLOCK: the file is set to O_NONBLOCK and is not blocked when read/write operations are performed.
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
/ /... Omitting exception judgment
// Listen for the mWakeEventFd file descriptor with the epoll operation
rebuildEpollLocked(a); }void Looper::rebuildEpollLocked(a) {
// Close old epoll instance if we have one.
/ /... Ellipsis operation
// Allocate the new epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
/ /...
// Some object data to populate
struct epoll_event eventItem;
memset(& eventItem, 0.sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
// Add epoll listener
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
/ /...
}
Copy the code
In the above code, the Looper class constructor does two things:
- Create a file descriptor for event notification,
eventfd
object - use
epoll
To listen for data
So much for the constructor of the MessageQueue class
MessageQueue
Message processing in
MessageQueue message loop in the next() method, the code is a bit long, first briefly describe the function:
- Check whether the first message in the queue is
SyncBarrier
The message- If so, look for the queue marked as
FLAG_ASYNCHRONOUS
The first message found as the current processing message - If not, the first message in the current queue is taken as the current processing message
- If so, look for the queue marked as
- If the currently fetched message is not
NULL
To check whether the processing time of the message has timed out- If there is no timeout, calculate the wait time
- If the time is up,
next()
Method returns the message and exits
- If the retrieved message is
NUll
, indicating that there are no messages in the queue that can be processed.- Set the wait time to
- 1
Wait forever
- Set the wait time to
- Check for exit flags in queues
- If an exit flag is detected, the object created in the Native layer is destroyed, and then
next()
Method of
- If an exit flag is detected, the object created in the Native layer is destroyed, and then
- Check for presence
IdleHandler
The callback function of- If not, continue the loop through
nativePollOnce()
Method suspends the thread and waits for a new message to arrive - If there is
- Call all
IdleHandler
The callback function of - For the return
false
Is removed from the queue after the call completes - Finally, the
epoll
The wait time is set to0
- Call all
- If not, continue the loop through
Before looking at the next() code, let’s look at the flow of calling the nativePollOnce() method:
nativePollOnce()
Call theNativeMessageQueue::pollOnce
NativeMessageQueue::pollOnce
Call theLooper::pollOnce
Looper::pollOnce
Call theLooper::pollInner
- In the end,
Looper::pollInner
Call theepoll_wait
Let’s look at the code for calling epoll_wait:
/ /...
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
/ /...
Copy the code
- call
epoll_wait
Blocks the current thread - When data comes in or a timeout period is reached,
epoll_wait
Will return
Let’s look at the next() code:
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.
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(a); }// Call a local method and wait for nextPollTimeoutMillis in milliseconds
// -1 means permanently blocked
nativePollOnce(ptr, nextPollTimeoutMillis);
// Synchronization is used here
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(a); Message prevMsg = null; Message msg = mMessages;if(msg ! = null && msg.target == null) {// Stalled by a barrier. Find the next asynchronous message in the queue.
// SyncBarrier is marked by a message target of null
// If the first message in the queue is SyncBarrier
// Ignore the normal message and look for the first asynchronous message
// that is a setAsynchronous(true) message
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! = null && ! msg.isAsynchronous());
}
if(msg ! = null) {// Find the message, the first time to determine the time
if (now < msg.when) {
// It is not time to process the message. Calculate how long you need to wait
// 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.
// Cancel the blocking flag
mBlocked = false;
// Fetch the message from the queue
if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
mMessages = msg.next;
}
msg.next = null;
// The tag uses and returns a message
msg.markInUse(a);returnmsg; }}else {
// Indicates that there are no messages in the queue that need to be processed
// No more messages.
nextPollTimeoutMillis = - 1;
}
if (mQuitting) {
// If the exit flag is set, the native object is destroyed and returned
dispose(a);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.
// Check whether IdleHandler is installed
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size(a); }if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// Put Idle handlers in the mPendingIdleHandlers array
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.
// All Idle handlers are processed. If the callback result is false, no further processing is performed
// Remove the Idle Handler from the list
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 {
// You can register an IdleHandler callback on the main thread
// The callback is delayed by 10 seconds to see if there is anything strange, hahaha
// You can also try to pop a window or something, you will be surprised
keep = idler.queueIdle(a); }catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if(! keep) {// This is removed
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
toMessageQueue
Send a message
The enqueueMessage() function is used to send messages to MessageQueue.
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
// If target is null, no Handler is specified and an exception is thrown
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
// If the message to be inserted is in use, the message is added repeatedly, and an exception is thrown
throw new IllegalStateException(msg + " This message is already in use.");
}
// Add a synchronization operation
synchronized (this) {
if (mQuitting) {
// If it is already in the exit state, return false and print a warning message
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(a);return false;
}
// Do some state processing with insert messages
msg.markInUse(a); msg.when = when; Message p = mMessages;// Whether a wakeup flag is required
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// p == null indicates that there is no message in the queue
/ / or when the = = 0 | | the when < p.w hen) shows that the current message need to insert into the head
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// Set whether to wake up based on the blocking state of next()
// If the thread is blocked, it needs to wake up
needWake = mBlocked;
} else {
// There are messages in the queue
// Only in the header p.target == null, SyncBarrier
MSG. IsAsynchronous ()
// You need to wake up
needWake = mBlocked && p.target == null && msg.isAsynchronous(a);// Find the right place to insert the message
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
// Note here that this is only done before the message is inserted
// If a message is already set to asynchronous before the message is inserted
// This message does not need to be woken up
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) {
// Wake up the thread operation
nativeWake(mPtr); }}return true;
}
Copy the code
enqueueMessage
Method inserts messages in order of time, the earliest beforeThe message queue
The organizational structure of theMessage
In the classnext
Pointers form a one-way linked list- When inserting a message, it calculates whether it needs to wake up the thread,
enqueueMessage
You want to avoid waking up the processing thread as much as possible- The thread wakes up after inserting a message to be processed immediately
- in
SyncBarrier
State inserts another entryAsynchronous messaging
The thread is then woken up
Finally, let’s look at the operation to wake up the thread:
void Looper::wake(a) {
/ /... Remove some debug code
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
/ /... Remove some debug code
}
Copy the code
TEMP_FAILURE_RETRY
Just ado.. while
Macro definition, the core is the system callwrite
functionwake()
By system callwrite
Write data to wake up the message processing thread- Processing thread pass
epoll
Listening to theeventfd
The data on the - As soon as the data arrives, the thread wakes up,
next()
Method continues processing the message
Here we string together the MessageQueue part:
- in
Looper
Class, passLooper.prepare
,Looper.loop
To initialize and start the messaging service, which includes:MessageQueue
The initialization- Create an infinite loop to process the message
- Cycle through
MessageQueue
thenext()
Function fetch message next()
Waits and executes based on the status of messages in the queueepoll_wait
The operation blocks the thread waiting for a message to arrive at a notification
Handler
Class throughsend*
orpost*
To complete the message sending, which includes:- The real execution is through
enqueueMessage
Insert the message - Need to wake up case via system call
write
To wake up the thread
- The real execution is through
- And for
MessageQueue
The state that we can passIdleHandler
To listen for whether the queue is empty
Interprocess messaging
Android messages can be passed between processes. Of course, interprocess messaging is based on Binder communication.
We know that with a Binder reference object you can make remote calls. If you want to send messages to another process’s Handler in Android, you must use a Binder proxy object to do so.
The Handler method getIMessage() creates a Binder object:
final IMessenger getIMessenger(a) {
synchronized (mQueue) {
if(mMessenger ! = null) {return mMessenger;
}
mMessenger = new MessengerImpl(a);returnmMessenger; }}Copy the code
The object type created in getIMessenger is MessengerImpl, a Binder service class inherited from the IMessenger.Stub class. The send() method in MessengerImpl sends a message to the Handler. The code is as follows:
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid(a); Handler.this.sendMessage(msg); }}Copy the code
Okay, we have the interface.
- call
Handler
The object’sgetIMessenger
The method will give you thisHandler
message-sendingBinder object
But to send messages across processes, how do you get the Binder object in the calling process? Let’s take a closer look at the Messenger class
understandMessenger
class
The Messenger class is essentially a wrapper around the IMessenger object that contains a MessengerImpl associated with the Handler object.
The class information is as follows:
public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
/ /... Omit some methods
// Note that this constructor is used in bindService
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }}Copy the code
- From the code,
Messenger
Or use ofBinder
The logic of communication- To achieve the
Parcelable
Interface,Binder
When communicating, it can be transmitted directly - while
Messenger
holdHandler
theIMessenger
object - In this way,
The calling process
After gettingMessenger
Object is also obtainedHandler
The ability to send messages
- To achieve the
- use
Messenger
The advantage is hidden from use in communicationBinder
The details make the whole process seem as simple as sending a local message
Let’s look at a simple example:
- Define a remote
Service
, remember to configureandroid:process
Properties,
public class RemoteService extends Service {
private static final String TAG = "HuaLee";
private static final Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG, "handleMessage=" + msg);
return true; }});private static final Messenger messenger = new Messenger(mHandler);
@Override
public IBinder onBind(Intent intent) {
returnmessenger.getBinder(); }}Copy the code
- Looking for a
Activity
, bind the service, and convert toMessenger
object
private Messenger mRemoteMessenger = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/ /...
Intent intent = new Intent(this, RemoteService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mRemoteMessenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
}
Copy the code
- That way, we can get through
Messenger
Object to send a message to the server
Message msg = Message.obtain();
mRemoteMessenger.send(msg);
Copy the code
Is there an example that makes it easier to understand? Ha ha ha
If you only need a simple message exchange between two processes, this will suffice. For the convenience of applications, Android also provides the AsyncChannel class to establish two-way communication.
Set up a communication channel – the AyncChannel class
AyncChannel classes for the upper application is a hidden, source path in: frameworks/base/core/Java/com/android/internal/util/AsyncChannel Java. Is used to establish a communication channel between two handlers. These two handlers can be in one process or in two processes.
The status of the communication parties established through the AyncChannel class is not equal
- One party is going to be
Service
And respond toAyncChannel
A message defined in a class - The other party acts as
client
Take the initiative to connect with each other
The first step in using the AyncChannel class is to determine whether the communication parties are in semi-connected mode or fully connected mode
Semi-connected mode
After the connection is established, onlyThe client
Take the initiative to giveThe service side
Send a message,The service side
Upon receipt ofThe client
After the message, use the attached messageMessenger object
To giveThe client
A reply messageFull connection mode
Both parties can actively send messages
Obviously, fully connected mode consumes more system resources than semi-connected mode.
Semi-connected mode
The AyncChannel class provides a number of connection methods that the client needs to use as needed. But before you can connect, you still need to get the Messenger object on the server.
As mentioned earlier, Binder delivery for the Java layer can be done in two ways:
- One is through what’s already established
Binder
Channel to passBinder
object - One is through components
Service
To obtain aService
Included in theBinder
object
The AyncChannel class supports both approaches.
If the client and server have a Binder channel established, the Messenger objects on the server can be passed to the client through this channel. We can then use the AyncChannel class’s Connect method to establish a connection between the two:
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
// We are connected
connected(srcContext, srcHandler, dstMessenger);
// Tell source we are half connected
replyHalfConnected(STATUS_SUCCESSFUL);
}
Copy the code
srcContext
:The client
The context in which thesrcHandler
:The client
theHandler
objectdstMessenger
From:The service side
Pass to theMessenger
object
The AyncChannel class also defines a very simple handshake protocol for the communicating parties
connect
Method will callreplyHalfConnected
methodsreplyHalfConnected
Method will sendCMD_CHANNEL_HALF_CONNECTED
A message toThe client
thesrcHandler
- How is the
The client
thesrcHandler
? Should not beThe service side
?- First of all,
Handshake protocol
Is to giveFull connection mode
Prepared, forSemi-connected mode
The handshake protocol is strictly enforced only in exceptional circumstances- In general,
Semi-connected mode
As long as you get the other side’sMessenger
It doesn’t matter if you shake hands or not - but
Full connection mode
You must make sure that both parties are ready before you can communicate, so a simple handshake is required
- In general,
- Therefore, in the
Semi-connected mode
,srcHandler
Object does not need processingCMD_CHANNEL_HALF_CONNECTED
The message Semi-connected mode
After callingconnect
Method and you can start sending messages
- First of all,
- How is the
If the client and server do not have existing Binder channels, a Binder channel can be created by starting the component Service. Follow the example of the Messenger class above.
If you don’t want to rewrite a Service, Andorid provides an abstract class AsyncService that the server can inherit to create a Service. In this case, the client needs to use another connect method of the AsyncChannel class to establish a connection:
public void connect(Context srcContext, Handler srcHandler, String dstPackageName,String dstClassName) {
/ /...
final class ConnectAsync implements Runnable {
/ /...
@Override
public void run(a) {
int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,
mDstClassName);
replyHalfConnected(result);
}
}
ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
new Thread(ca).start();
/ /...
}
public int connectSrcHandlerToPackageSync( Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
/ /...
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(dstPackageName, dstClassName);
boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
}
class AsyncChannelConnection implements ServiceConnection {
/ /...
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mDstMessenger = new Messenger(service);
replyHalfConnected(STATUS_SUCCESSFUL);
}
@Override
public void onServiceDisconnected(ComponentName className) { replyDisconnected(STATUS_SUCCESSFUL); }}Copy the code
The key methods are listed in the code above. The process of using a Service is as follows:
connect
Methods will be passed inThe package name
andThe name of the class
To start theService
, the startup time may be relatively long, thereforeconnect
Method to create a thread to start:new Thread(ca).start();
run()
Method is calledconnectSrcHandlerToPackageSync
Method, it callsbindService
To start a componentService
Service
After startup, useServiceConnection
The implementation class of the interfaceAsyncChannelConnection
To receive theService
Passed onBinder
object- Then, in
onServiceConnected
In theBinder
Object wrapped asMessenger
objectmDstMessenger
Semi-connected mode
Next to theThe service side
To send a messagemDstMessenger
object
Note that replyHalfConnected will be executed twice during this process.
connectSrcHandlerToPackageSync
In thereplyHalfConnected
Mainly for binding failure after the prompt
In this case, the client’s Handler object must receive the CMD_CHANNEL_HALF_CONNECTED message to indicate that the communication has been successfully established.
I don’t think it’s necessary to use AsyncService, so I’m going to use Service instead. Write your own more relief, ha ha ha
Full connection mode
Fully connected mode is based on semi-connected mode:
- when
The client
theHandler
Object receives messageCMD_CHANNEL_HALF_CONNECTED
after - If you want to establish
All connection
.The client
Need to:- to
The service side
sendCMD_CHANNEL_FULL_CONNECTION
The message - At the same time with
The client
theMessenger
objectMessage
Object has areplyTo
ha
- to
The service side
After receiving the message- to
The client
replyCMD_CHANNEL_FULLY_CONNECTED
- if
The service side
Agreed to establishAll connection
Will take the first argument of the messagemsg.arg1
Is set to0
Otherwise, it is set toNon-zero value
.
- to
The following isFull connection mode
Message interaction diagram of
WifiService and WifiManager(client) in Android are taken as examples to understand the establishment process of the full connection mode:
Wifi this part of the details is still a lot of, we just briefly introduced the client and server Handler communication process ha
The service side
andThe client
Of the twoHandler
- First of all, as
The client
theWifiManager
Defined in theHandler
As follows:
private class ServiceHandler extends Handler {
@Override
public void handleMessage(Message message) {
synchronized(sServiceHandlerDispatchLock) { dispatchMessageToListeners(message); }}private void dispatchMessageToListeners(Message message) {
Object listener = removeListener(message.arg2);
switch (message.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
// The half-connection part is successfully connected
if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
// Send a full connection request
mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
} else {
Log.e(TAG, "Failed to set up channel connection");
// This will cause all further async API calls on the WifiManager
// to fail and throw an exception
mAsyncChannel = null;
}
mConnected.countDown();
break;
case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
// Ignore
break;
/ /...}}}Copy the code
- As a
The service side
theWifiService
In the classHandler
The definition is as follows:
private class ClientHandler extends WifiHandler {
/ /...
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
Slog.d(TAG, "New client listening to asynchronous messages");
// We track the clients by the Messenger
// since it is expected to be always available
mTrafficPoller.addClient(msg.replyTo);
} else {
Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
}
break;
}
/ /...
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
// After receiving a full connection request from the client, create an AsyncChannel object
AsyncChannel ac = mFrameworkFacade.makeWifiAsyncChannel(TAG);
// Establish a connection based on the Messenger object carried by the client message
ac.connect(mContext, this, msg.replyTo);
break;
}
/ /...}}Copy the code
According to the above two handlers, we can deduce the flow as follows:
WifiManager
As aThe client
In return for theHalf a connection
After, a message is sentCMD_CHANNEL_FULL_CONNECTION
The news of theWifiService
As aThe service side
, after receiving theCMD_CHANNEL_FULL_CONNECTION
after- So let’s create one
The service side
Their ownAsyncChannel
object - Then through
AsyncChannel
The object’sconnect
Method to disconnectThe client
- And then,
connect
The method will giveThe service side
Their ownHandler
Object sends aCMD_CHANNEL_HALF_CONNECTED
The message
- So let’s create one
WifiService
As aThe service side
, after receiving theCMD_CHANNEL_HALF_CONNECTED
after- the
The client
Sent overMessenger
Object to a member variablemTrafficPoller
In the mTrafficPoller
Object that holds oneMessenger
theList
A collection of
- the
- Please note that,
WifiService
There was no reply.CMD_CHANNEL_FULLY_CONNECTED
. It’s not a surprise, it’s not a surprise
Sending synchronization messages
The sendMessageSynchronously() method of the AyncChannel class sends synchronization messages. The sendMessageSynchronously() method suspends the thread to wait after sending a message, and resumes the thread after receiving the reply.
The core code for the AyncChannel class sendMessageSynchronously() is as follows:
private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
SyncMessenger sm = SyncMessenger.obtain();
Message resultMsg = null;
try {
if(dstMessenger ! =null&& msg ! =null) {
// Note that the replyTo is set to the SyncMessenger object
msg.replyTo = sm.mMessenger;
synchronized (sm.mHandler.mLockObject) {
if(sm.mHandler.mResultMsg ! =null) {
Slog.wtf(TAG, "mResultMsg should be null here");
sm.mHandler.mResultMsg = null;
}
// Send a message to the server
dstMessenger.send(msg);
// Call wait to wake up
sm.mHandler.mLockObject.wait();
resultMsg = sm.mHandler.mResultMsg;
sm.mHandler.mResultMsg = null; }}}/ /...
sm.recycle();
return resultMsg;
}
Copy the code
- create
SyncMessenger
object - will
Message.replyTo
Set toSyncMessenger
object- The receipt of the message is then forwarded to
SyncMessenger
The object of the
- The receipt of the message is then forwarded to
- Send a message and call
wait()
Method suspends the thread
The reply message sent by the server is processed in SyncHandler, which is defined in SyncMessenger:
private class SyncHandler extends Handler {
/** The object used to wait/notify */
private Object mLockObject = new Object();
/** The resulting message */
privateMessage mResultMsg; /.../** Handle of the reply message */
@Override
public void handleMessage(Message msg) {
Message msgCopy = Message.obtain();
msgCopy.copyFrom(msg);
synchronized(mLockObject) { mResultMsg = msgCopy; mLockObject.notify(); }}}Copy the code
The processing logic of this part is also relatively simple:
- Saves the received message to a variable
mResultMsg
In the - call
notify()
Method to wake up a thread
At this point, threads suspended by wait() are awakened and returned to resultMsg
Concepts related to Android synchronization
This part of the book added here feel a little bit inconsistent, multithreading and synchronization related knowledge is very much, first pick a part of the concept of learning, Java part of the multithreading has been completed, but not all, and then separate comb
As mentioned earlier, Binder threads are automatically generated when Android applications run, so multiple threads are running in an Android application even if the upper application code does not create any threads.
This will inevitably encounter the problem of resource competition. Threads in Linux operate in preemptive mode. Therefore, the synchronization mechanism provided by the system is required to ensure thread safety for accessing shared resources.
Although the synchronization mechanism can solve the problem of resource access conflict, it inevitably brings performance loss. Therefore, the synchronization mechanism should be avoided as far as possible without affecting the correctness.
Atomic operation
Even some simple operations, such as addition and subtraction, require multiple instructions at the assembly level when operating on global variables of simple types.
The completion of the entire operation process requires:
- First read the value in memory
- Computes in the CPU
- And then write it back to memory
If a thread switch occurs and the value in memory is changed, the result of the final execution will be different from the expected result. We can solve this problem by adding locks; But the best way to avoid this problem is to use atomic manipulation
Android implements a set of atomic manipulation functions in assembly language, which are widely used in the implementation of synchronization mechanisms
Android atomic manipulation functions
This part of the function in the system/core/libcutils/include/atomic. H is defined
Let’s look at the functions related to atomic variable manipulation:
// Atomic variable increment operation
int android_atomic_inc(volatile int32_t* addr);
// Decrement of atomic variables
int android_atomic_dec(volatile int32_t* addr);
// Atomic variable addition operation
int android_atomic_add(int32_t value, volatile int32_t* addr);
// Atomic variables and operations
int android_atomic_and(int32_t value, volatile int32_t* addr);
// The or operation of an atomic variable
int android_atomic_or(int32_t value, volatile int32_t* addr);
// Atomic variable read operation
int android_atomic_acquire_load(volatile const int32_t* addr);
int android_atomic_release_load(volatile const int32_t* addr);
// Set the atomic variable
void android_atomic_acquire_store(int32_t value, volatile int32_t* addr);
void android_atomic_release_store(int32_t value, volatile int32_t* addr);
// Compare and swap atomic variables
int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue,volatile int32_t* addr);
int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue,volatile int32_t* addr);
// Macro definitions for two atomic variables
#define android_atomic_write android_atomic_release_store
#define android_atomic_cmpxchg android_atomic_release_cas
Copy the code
The way atomic operations are implemented is closely related to the CPU architecture, and atomic operations are now generally implemented at the CPI instruction level. This method is not only simple, but also very efficient.
Memory barriers and compilation barriers
Background knowledge
- In modern CPUS, the order of instruction execution is not necessarily in the order written by the developer. Instructions without correlation can be executed in disordered order to make full use of the CPU’s instruction pipeline and improve execution speed. use
The memory barrier
Countermeasures to - The compiler itself also optimizes instructions. For example, adjust the instruction sequence to take advantage of the CPU’s instruction pipeline. use
Compile the barrier
Countermeasures to
These are actually out-of-order memory accesses. The reason for out-of-order memory access behavior is to improve the performance of program runtime. For out-of-order execution, we can refer to Wikipedia
However, for some programs with strict requirements on the order of execution, out-of-order execution may have disastrous consequences.
This situation requires us to do some special treatment.
The concept description
Also called memory barrier, memory gate barrier, barrier instruction, etc. It allows the CPU or compiler to perform operations on memory in a strict order, which means that instructions before and after a memory barrier cannot be out of order due to system optimization.
Most modern computers execute out of order to improve performance, which makes memory barriers necessary.
Semantically, all writes prior to the memory barrier are written to memory; Any read operation after the memory barrier can obtain the result of any write operation before the synchronous barrier. Therefore, for sensitive blocks, memory barriers can be inserted after write operations but before read operations.