Looper is the manager of the entire cross-thread communication
// Internal variables are as follows:
ThreadLocal<Looper>
MainLooper
Observer
MessageQueue
Thread
Copy the code
1. Remember how to use Handler first
Communication between Android threads can be divided into the following two situations: 1. Child threads send messages to UI threads. 2. A child thread sends messages to another child thread
1. The child thread sends messages to the UI thread
class FragmentContentActivity : AppCompatActivity(a){
val FLAG = 1
lateinit var handler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
FLAG -> {
findViewById<TextView>(R.id.text).text = msg.data["Text"].toString()
}
}
}
}
thread {
Thread.sleep(2000L)
handler.sendMessage(Message.obtain().apply {
what = FLAG
data = Bundle().apply {
this.putString("Text"."ThreadMessage")}})}}}Copy the code
2. The UI thread/child thread sends messages to the child thread
class FragmentContentActivity : AppCompatActivity(a){
val THREAD_FLAG =2
lateinit var threadHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) thread { Looper.prepare() threadHandler = object :Handler(Looper.myLooper()!!) {override fun handleMessage(msg: Message) {
when(msg.what){
THREAD_FLAG -> {
Toast.makeText(
this@FragmentContentActivity."${msg.data["Text"]}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
Looper.loop()
}
}
override fun onResume(a) {
super.onResume()
findViewById<TextView>(R.id.text).postDelayed({
threadHandler.sendMessage(Message.obtain().apply {
what = THREAD_FLAG
data = Bundle().apply {
putString("Text"."UI Message")}})},2000L)}}Copy the code
In the case of child threads, looper.prepare () and looper.loop () must be performed before and after looper.prepare (), so let’s look at the logic of Looper with this in mind
// Make sure you call loop() after prepare() and quit() to end the message loop
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
Prepare () initializes a Looper object into a ThreadLocal, initializes the Looper and queues the mQueue
public static void loop(a){
Binder.clearCallingIdentity()
for (;;) {
Message msg = queue.next(); // might block
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// Loop () only makes this call. The rest of the call is to monitor whether the current message loop times out
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);
}
// Message entity reclamation
msg.recycleUnchecked();
Copy the code
You can see the stars. The loop is just in a for loop, get the next mQueue MSG node, and then call MSG. Target. DispatchMessage (MSG). At first glance, this is just an operation inside the MSG object.
Because loop() is logically dead-loop, this means that the spontaneous sequential execution of commands by the current thread ends, and that the current thread can only passively execute commands by triggering the handler mechanism of another thread. The current thread becomes a fully responsive thread
The Looper class is just a switch that initialates and starts the thread loop, which is done in MessageQueue
MessageQueue MessageQueue
The addition of messages to the queue is not called directly by MessageQueue, but by the Handler associated with Looper
MessageQueue internally holds the following variables: ArrayList
mMessages SparseArray
IdleHandler[] mBlocked
The main functions of MessageQueue class are as follows: insert elements into queues, obtain the head elements of queues, search for elements in queues, review is to add, delete, change and check queues, where mMessage is the entrance of this queue and the head node of this queue
boolean enqueueMessage(Message msg,long when) // MSG elements are inserted into the queue
boolean hasMessages(Handler h,int what,Object object) // find MSG. What /object with the same MSG
boolean hasEqualMessages(Handler h,int what,Object obj)// find MSG. Object. Equal (obj)
removeMessages(Handler h,int what,Object obj)/(Handler h,Runnable r,Object obj)
removeEqualMessages(...). // Delete the MSG with the same or equal value as the msg.object argument
Message next(a) // Get the header element in the queue
Copy the code
As you can see, synchronized(this) is called internally by these methods, and the queue operations are thread synchronized
Message next(a) ->
...
NextPollTimeOut is how long the bus is suspended
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// Check whether msg.target is null, indicating whether the current message is asynchronous
if(msg ! =null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// Synchronization barrier: fetch asynchronous messages from the current queue
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// Recalculate the time when the thread enters the suspended state
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1; }...Copy the code
You can see that there are two main types of logic inside next() to get MSG
1. The current messages are ordinary messages, sorted according to the size of msg.when. Each cycle is executed to determine whether to acquire MSG or suspend the current thread by detecting whether when is greater than now. Async (MSG. IsAsynchronous ()==true); async (MSG. IsAsynchronous ()==true);
In layman’s terms, all ordinary messages are queued according to the order of the scheduled execution time. In this way, messages can be executed according to the scheduled execution time and the maximum number of messages can be executed within a certain period with maximum efficiency. However, this ignores the time consumed by the execution of each message. For example, message A is the No.1 in the queue, the execution time of message A is 1s later, and the whole queue is in the waiting state. At this time, message B arrives, and the time of message B is 0.999s later. According to the queuing mechanism of reservation time, message B will jump the queue before message A, and THEN B becomes the No.1 in the queue. A became No.2, and the waiting time of the whole queue was still 1s(because the waiting time was set before, so there was No need to wake up). However, the execution process of B message was as long as 0.5s, which had already exceeded the scheduled execution time of many subsequent messages, so some important messages could not be executed on time.
Thus, there is the mechanism of asynchronous message synchronization barrier, which is equivalent to the ordinary message queue when a VIP message, first find their reservation time, and then shout: “Move your feet to me, no one is allowed in front of me”. At this time, all the ordinary messages in front of him can only be moved to the side of the queue, and then the queue will set the waiting time according to the VIP message. During this period, the new ordinary messages will also be inserted to the side of the queue to ensure the accurate and timely execution of the VIP message. Wait for VIP messages to finish, and then merge the queue waiting for ordinary messages. Of course, the news waiting for before all delayed, but after all, ordinary news is not important.
// The synchronization barrier method, which is only called in the ViewRootImpl class
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// Target is not set
Message prev = null;
Message p = mMessages;
if(when ! =0) {
while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}if(prev ! =null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
//mMessages becomes synchronization barrier messages, and next() returns the synchronization barrier first in the next loop
mMessages = msg;
}
return token;
}
// ViewRootImpl
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if(! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }// After the synchronization barrier is set, all Msg messages sent by handlers with Aysnc flag bits are asynchronous messages.
//MessageQueue also prioritises such asynchronous messages until the synchronization barrier flag is removed and then the MessageQueue is restored.
Copy the code
The synchronization barrier must be set up to ensure that the Vsync signal is refreshed on time. SendMsg (1,time0.2)-> asynChandler. sendMsg(2,time0.5) -> 2->1.
Then look at Handler
The Handler member variables are as follows
MQueue: MessageQueue queue reference mAsynchronous obtained from Looper. MQueue: MessageQueue queue reference mAsynchronous obtained from Looper. MQueue: MessageQueue queue reference mAsynchronous obtained from Looper. MMessenger: IMessager indicates whether the Handler is currently sending asynchronous messages. MCallback: The Handler’s own callback interface, which precedes message. callback
Most of these member variables are final, which means that Handler is also final in its use, meaning that there is no way to avoid memory leaks by separating the Handler from the context’s lifecycle
The Handler method is as follows
The first method is to set what, data for a Message
// Do not set runnable: Callback
boolean sendMessage(Message msg) -> boolean sendMessageDelayed(Message msg,long delayTime)
-> boolean sendMessageAtTimeThe Message (MSG, SystemClock uptimeMillis ()+delayTime)
-> mQueue.enqueueMessage(msg,uptime)
// In the second way, Message sets only runnable:Callback
boolean postAtTime(Runnable r, Object token,long uptime)
-> sendMessageAtTime(getPostMessage(r,token),uptime)
--> Message getPostMessage(Runnable r,Object token){
Message.obtain().callback=r
...
}
// Remove Message and verify Message
removeMessages()
hasMessages()
...
// The Message callback executes
void dispatchMessage(Message msg){
if(msg.callback! =null){
handleCallback(msg) ->
}else{
if(mCallback! =null){
mCallback.handleMessage(msg)
}
handleMessage(msg)
}
// You can see that the Message callback has three levels
//No.1 MSG callback
//No.2 Handler's own mCallback member variable. MCallback is final
// Subclasses of Handler override the handleMessage method
Copy the code
Message
Message implements the Parcelable interface, which means it can be used as a vehicle for interprocess communication
The Message member variables are as follows
int what //Handler sends the Message code under the body Message
int arg1 // Low cost parameter passing
int arg2
Object obj // A token object that can be nullable, usually used in process communication
Bundle data // A container of common parameters used in thread communication
Handler target // Send the body
Runnable callback //Message callback itself
Messenger replyTo // Process communication, commonly used in AMS
------
// Message cache pool correlation
Object sPoolSync = new Object() // Synchronize the flag
Messsage next
static Message sPool
static int sPoolSize
Copy the code
The Message method follows
// You can see that this is a very clever method
static Message obtain(a){
synchronized(sPoolsSync){
if(sPools! =null){
Message m= sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
returnm; }}return new Message();
}
// The main body is a synchronous factory pattern with a linked list of cache pools, and also allows for more multithreaded blocking
// You can declare the initializer directly
// Reclaim the Message object to the cache pool list
void recycleUnchecked(a){... Parameter =null
synchronized(sPoolSync){
if(sPoolSize < MAX_SIZE){
next = sPool;
sPools = this; sPoolSize++; }}}Copy the code
Other bits and pieces about the Handler
1. RunWithScissors () / / to be continued
2. What are the three types of messages
Ordinary Message Asynchronous Message: isAsynchronous(true) barrier flag Message: Target == NULL
3. IdleHandler / / to be continued
4. Why use nativePollOnce() /nativeWeak(mPtr)
The Blocking of the Linux message bus, which ensures that messages from other threads can be received at the native layer, can be handled internally by the JVM using Wait /notify
5. When does nativeWeak(mPtr) wake up the current thread
1. The current new MSG. When < mMessage. When | | MSG. The when = 0 | | MSG. The target = = null is the current message booking time efficiency of the head of a queue waiting time, or the current news is synchronous barrier marker, then need to wake up the queue 2. When removeSyncBarriar() is removed, the mQueue becomes a normal message queue and needs to be woken up
6. Can Handler be used for IPC communication?
Since Looper objects within handlers reside in ThreadLocal and can only be retrieved within the same process, they cannot communicate between processes, but Message should act as a container for interprocess communication, with a Messenger reference