Android Handler message mechanism core code

preface

This paper mainly analyzes the key source code of handler message mechanism. You need to have some basic understanding of handler before reading. Here is a brief review:

Basic composition

The complete message processing mechanism consists of four elements:

  1. Message: the carrier of information
  2. Messagequeue: a queue used to store messages
  3. Looper (message loop): it is responsible for checking whether there are messages in the message queue and fetching messages
  4. Handler (send and process messages): adds messages to the message queue and is responsible for distributing and processing messages

Basic usage

The simple usage of Handler is as follows:

Handler handler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {                     
        super.handleMessage(msg);
    }
};
Message message = new Message();
handler.sendMessage(message);

Note that the loop. Prepare () and loop. Loop () methods should be called in the non main thread

Workflow

The workflow is shown in the figure below:

The process from sending a message to receiving a message is summarized as follows:

  1. send message
  2. The message enters the message queue
  3. Take the message from the message queue
  4. Message processing

Here are four steps to analyze the relevant source code:

send message

handle has two types of methods for sending messages, which are essentially the same:

  • sendXxxx()
    • boolean sendMessage(Message msg)
    • boolean sendEmptyMessage(int what)
    • boolean sendEmptyMessageDelayed(int what, long delayMillis)
    • boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
    • boolean sendMessageDelayed(Message msg, long delayMillis)
    • boolean sendMessageAtTime(Message msg, long uptimeMillis)
    • boolean sendMessageAtFrontOfQueue(Message msg)
  • postXxxx()
    • boolean post(Runnable r)
    • boolean postAtFrontOfQueue(Runnable r)
    • boolean postAtTime(Runnable r, long uptimeMillis)
    • boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    • boolean postDelayed(Runnable r, long delayMillis)
    • boolean postDelayed(Runnable r, Object token, long delayMillis)

There is no analysis of the specific method features. They end up by calling sendMessageAtTime() or sendMessageAtFrontOfQueue to execute the message into the team. The only difference is that the post series method calls the getPostMessage method before sending the message:

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

It should be noted that when sendMessageAtTime() is called by other sendXxx, the typical usage is:

sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

If the caller does not specify a delay time, the execution time of the message is the current time, that is, immediate execution. The methods exposed by the Handler follow this operation. Unless otherwise specified, the execution time of msg message is: current time plus delay time, which is essentially a timestamp. Of course, you can also specify any time, which will be used in message insertion later. The code is very simple, that is, the Runnable callback passed by the caller is assigned to message (used in message processing). Both sendMessageAtTime() and sendMessageAtFrontOfQueue methods implement message stacking through enqueueMessage method:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

The code is very simple, mainly including the following operations:

  • Let message hold the reference of the handler that sent it (this is also the key to finding the corresponding handler when processing messages)
  • Set whether the message is asynchronous (asynchronous messages do not need to be queued and can be executed by jumping in the queue through the synchronization barrier)
  • Call the enqueueMessage method of MessageQueue to add the message to the queue

The message enters the message queue

Preparations before joining the team

enqueueMessage method is the key to adding messages to MessageQueue. It is analyzed in the following sections:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
	    throw new IllegalArgumentException("Message must have a target.");
    }
    //... omit the following code
}    

This code is very simple: judge whether the target of message is empty, and throw an exception if it is empty. Among them, target is the Handler reference mentioned in Handler.enqueueMessage above. Next, come down and start judging and processing messages

boolean enqueueMessage(Message msg, long when) {
    //... omit the code above
    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;
        //... omit the following code
    }
    //... omit the following code
}

First, add a synchronization lock, and then run all operations in the synchronized code block. Then, two if statements are used to handle two exceptions:

  1. Judge whether the current msg has been used. If it is used, eliminate the exception;
  2. Judge whether the message queue is closing. If so, recycle the message, return the queue failure (false) to the caller, and print the relevant log

If everything is normal, mark that the message is in use (corresponding to the exception of the first if) through markInUse, and then set the message sending time (machine system time). Next, start the operations related to insertion

Queue messages

Continue to look at the code implementation of enqueueMessage

boolean enqueueMessage(Message msg, long when) {
    //... omit the code above
    synchronized (this) {
            //... omit the code above
            //Step 1
            Message p = mMessages;
            boolean needWake;
            //Step 2
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                //Step 3
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
    }
    //Step 4
    return true;
}

First, the message queue uses a one-way linked list to maintain the of the message queue, following the first in first out soft solution. Analyze the above code:

  • Step 1: mMessages is the header. First, take out the linked list header.

  • Step 2: a judgment statement. If three conditions are met, msg is directly used as the header:

    1. If the header is empty, there is no message in the queue, and msg is directly used as the header of the linked list;
    2. when == 0 means that the message needs to be executed immediately (for example, sendMessageAtFrontOfQueue method, but the general sent messages are the time at the time of sending plus the delay time unless otherwise specified), and msg is inserted as the header of the linked list;
    3. When < p.when indicates that the execution time of the message to be inserted is earlier than the header, and msg is inserted as the header of the linked list.
  • Step 3: by continuously comparing the execution time of the message in the queue and the execution time of the inserted message, follow the principle of small timestamp first, and insert the message into the appropriate position.

  • Step 4: return to the caller and the message insertion is completed.

Note the needWake and nativeWake in the code, which are used to wake up the current thread. Because at the message fetch end, the current thread will enter the blocking state according to the status of the message queue. When inserting, it should also judge whether it needs to wake up according to the situation.

The next step is to get the message from the message queue

Take the message from the message queue

Still, let's look at the preparations first

preparation

Using a Handler in a non main thread requires two things

  1. Loop. Prepare(): create a loop
  2. Loop. Loop(): turn on loop

Regardless of its creation, let's directly look at the code at the beginning of the loop in segments: first, some inspection and judgment work. The specific details have been annotated in the code

 public static void loop() {
 		//Get loop object
        final Looper me = myLooper();
        if (me == null) {
        	//If loop is empty, an exception is thrown to terminate the operation
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
        	//Loop loop is opened repeatedly
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }
        //Mark that the current loop has been turned on
        me.mInLoop = true;
        //Get message queue
        final MessageQueue queue = me.mQueue;
        //Ensure that the permission check is based on the local process,
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);
        boolean slowDeliveryDetected = false;
        //... omit the following code
}        

Operations in loop

The next step is to officially open the loop and streamline the key code:

 public static void loop() {
 		//... omit the code above
 		for (;;) {
 			//Step 1
            Message msg = queue.next();
            if (msg == null) {
                //Step 2
                return;
            }
           //... omit non core code
            try {
            	//Step 3
                msg.target.dispatchMessage(msg);
                //...
            } catch (Exception exception) {
                //... omit non core code
            } finally {
                //... omit non core code
            }
            //Step 4
            msg.recycleUnchecked();
        }
}        

Analyze the above codes step by step:

  • Step 1: take out the message from the message queue (queue.next() may cause blocking, which will be discussed below)
  • Step 2: if the message is null, end the loop (if there is no message in the message queue, null will not be returned, but will be returned only when the queue is closed, as will be discussed below)
  • Step 3: start message distribution after receiving the message
  • Step 4: recycle the distributed messages, and then start a new round of circular data retrieval
next method of MessageQueue

Let's just look at the first step of extracting the message. We'll look at the rest in a later section. There are many queue.next() codes, and we still look at them in segments

Message next() {
	//Step 1
	final long ptr = mPtr;
    if (ptr == 0) {
		return null;
	}
	//Step 2
	int pendingIdleHandlerCount = -1; 
	//Step 3
	int nextPollTimeoutMillis = 0;
	//... omit the following code
}	
  • Step 1: if the message Loop has exited and has been disposed, return null directly. Corresponding to the above, Loop gets the message through queue.next() and exits the Loop after getting null
  • Part 2: initialize IdleHandler counter
  • Part III: the judgment conditions required to initialize native. The initial value is 0. If it is greater than 0, there are messages waiting to be processed (the delayed message has not reached the execution time). If - 1, there are no messages.

Continue analyzing Code:

Message next() {
	//... omit the code above
	for(;;){
		if (nextPollTimeoutMillis != 0) {
			Binder.flushPendingCommands();
		}
		nativePollOnce(ptr, nextPollTimeoutMillis);
		//... omit the following code		
	}
}	

This paragraph is relatively simple:

  • Open an infinite loop
  • nextPollTimeoutMillis != 0 means that there are no messages in the message queue or all messages have not reached the execution time. Call the nativeBinder.flushPendingCommands() method to send messages to the kernel thread before blocking, so that the kernel can reasonably schedule and allocate resources
  • Call the native method again. According to the judgment of nextPollTimeoutMillis, when it is - 1, the current thread will be blocked (it will re-enter the runnable state when new messages are queued). When it is greater than 0, it indicates that there is a delay message, and nextPollTimeoutMillis will be used as a blocking time, that is, the message will be executed after many times.

Continue to look at the code:

Message next() {
	//... omit the code above
	for(;;){
		//... omit the code above
       //Open synchronization lock
		synchronized (this) {
			final long now = SystemClock.uptimeMillis();     
                //Step 1
                Message prevMsg = null;
                Message msg = mMessages;
                //Step 2
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //Step 3
                if (msg != null) {
                    //Step 4
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //Step 5
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //Step 6
                    nextPollTimeoutMillis = -1;
                }
		}	
		//... omit the following IdleHandler related code		
	}
}	

Analyze the code:

  • Step 1: get the queue header
  • Step 2: judge whether the current message is a synchronous message (the target of the asynchronous message is null), and start the cycle until the synchronous message is found
  • Step 3: judge whether the message is null. If it is not empty, execute step 4, and if it is empty, execute step 6;
  • Step 4: judge the execution time of the message. If it is greater than the current time, assign a new value to the nextPollTimeoutMillis mentioned above (the time difference between the current time and the message execution time). In this step, all the cancellation operations of this cycle are basically completed. If the current message does not reach the execution time, this cycle ends and a new cycle begins, The native pollonce (PTR, nextPollTimeoutMillis) mentioned above will be used; Method enters blocking state
  • Step 5: take out the message to be executed immediately from the message queue, end the whole cycle and return.
  • Part 6: if there is no message in the message queue, mark nextPollTimeoutMillis so that the next cycle can enter the blocking state

The rest of the code is basically the processing and execution of IdleHandler. It will be explained in the IdleHandler section, which will not be expanded here.

Message processing

Remember msg.target.dispatchMessage(msg) in the loop method above; Are you? Messages are distributed through the dispatchMessage method. Where target is the reference of the handler held by msg to send it, which is assigned when sending a message. The source code of dispatchMessage is as follows:

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

The code is very simple. Decide whether to call callback or handleMessage method by judging whether the Message is Runable, and leave it to the Handler you define to handle it. It should be noted that although callback is a Runable, it does not call the run method, but executes it directly. This shows that it does not start a new thread, but is used as a method (if the Handler is written in kotlin at the beginning, this may be a high-order function).

Other key points

After the main process of message processing, let's talk about the key source code outside the main process

Creation of Loop

Remember that the loop. Prepare () and loop. Loop () methods should be called in the non main thread mentioned above? These two methods can be understood as initializing the loop and starting the loop loop. This is not necessary in the main thread because the framework layer has helped us in the main method started by app. Let's look at the two methods respectively:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
        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(new Looper(quitAllowed));
    }
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }    

Here we first use a static ThreadLocal Ensure the uniqueness of Loop and thread isolation, so that a thread has and has only one Loop instance. Then initialize Loop and create a MessageQueue (quitAllowed to set whether exit is allowed). In this step, the association between Loop and message queue is realized.

It should be noted that the Loop construction method is private. We can only create it through the prepare area and obtain it through the myloop method.

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal.get source code:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }    

You can see that each Thread holds a ThreadLocalMap, which uses the same data structure as HashMap, uses ThreadLocal as the key value, and value is the Loop instance. It is not difficult to find: we can only get the Loop instance of the current Thread.

Loop also provides a method for initialization in the main thread, preparemainloop, but this method clearly states that it is not allowed to be called and can only be called by the system itself. This is basically the key to the creation of loop. It is also here to complete the association between loop, message queue and line composition.

Creation of Handler

The constructors of Handler are as follows:

  1. public Handler()
  2. public Handler(Callback callback)
  3. public Handler(Looper looper)
  4. public Handler(Looper looper, Callback callback)
  5. public Handler(boolean async)
  6. public Handler(Callback callback, boolean async)
  7. public Handler(Looper looper, Callback callback, boolean async)

The first and second construction methods have been abandoned. In fact, the first to fifth construction methods are implemented by calling public handler (callback, Boolean async) or public handler (looper, looper, callback, Boolean async). Their source code is as follows:

 public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
        	//Note this exception. Loop cannot be empty. First, loop. Prepare();
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

The biggest difference between the two methods is that one uses the passed loop, the other directly uses the loop of the current thread, and then the same initialization operations. A key point appears here. The thread on which the handler processes the message is not related to the thread that created it, but related to the thread of loop when it was created.

This is also the reason why the handler can realize thread switching: the execution of the handler has nothing to do with the thread creating the handler, but is related to the thread creating the looper. If a handler is created in the sub thread, but the looper related to the handler is the main thread, if the handler executes post, runnable, or sendMessage, The final handle Message is executed in the main thread.

Creation, recycling and reuse mechanism of Message

We can directly use the new keyword to create a Message:

Message message = new Message(); However, this approach is not recommended. The official provides the following methods for users to create messages:

Except for some differences in formal parameters, which are used to assign values to different member variables of message, these methods essentially create message through obtain():

public static final Object sPoolSync = new Object();
Message next;
private static Message sPool;

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

The code is very simple. A single line linked list is maintained inside the Message, and sPool is used as the header to store the Message entity. It can be found that every time the caller needs a new Message, he will first get it from the head of the linked list and return it directly if there is a Message. No Message will create a new Message.

So when did the linked list insert messages? Next, let's look at Message recycling:

public static final Object sPoolSync = new Object();
private static final int MAX_POOL_SIZE = 50;

void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

This method is called every time a Message is fetched from the MessageQueue queue for distribution, which is in the Loop.loop() method mentioned above. The code is also very simple. First, restore the member variable of Message to the initial state, and then insert the recycled Message into the linked list by header insertion (the maximum capacity is limited to 50). Moreover, the same lock is used for the operation of insertion and removal to ensure safety.

Note that both insertion and extraction are operations on the header of the linked list, which is different from that in the message queue. Although they all use one-way linked lists, they use header insertion and header retrieval when recycling. It's a stack. The MessageQueue is a queue, which follows the principle of first in first out. When inserting, the location is determined according to the status of the message, and there is no fixed insertion node.

This is a typical example Sharing element mode , the biggest feature is to reuse objects to avoid memory waste caused by repeated creation. This is why android officials recommend this way to create messages: to improve efficiency and reduce performance overhead.

IdleHandler

IdleHandler is simply defined as an interface defined in MessageQueue:

  public static interface IdleHandler {
        boolean queueIdle();
    }

According to the official explanation, during the Looper loop, whenever the message queue is idle: there is no message or the execution time of any message needs to lag, the queueIdle method will be executed, and its returned Boolean value identifies whether the IdleHandler is permanent or one-time:

  • ture: permanent. Once idle, it will be executed
  • false: one time. It will be executed only when it is idle for the first time

Its usage is as follows:

Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return true;
            }
        });

Take a look at the implementation of addIdleHandler

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

 public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

The code is very simple. It is a List to save the implementation of the interface. So how does it call when idle?

Remember the code omitted in the next method of MessageQueue above?

Message next() {

        //... omit irrelevant code

        //Step 1
        int pendingIdleHandlerCount = -1; // -1 exists only in the first iteration
        for (;;) {
                //... omit irrelevant code

                //Step 2
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                //Step 3
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                //Step 4
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //Step 5
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;

                //Step 6
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                //Step 7
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            //Step 8
            pendingIdleHandlerCount = 0;
        }
    }

Next, analyze the code step by step:

  • Step 1: create a local variable pendingIdleHandlerCount to record the number of idlehandlers before the start of the loop to get the message. It is only - 1 at the beginning of the loop;
  • Step 2: assign a value to pendingIdleHandlerCount and record the number of idlehandlers when the Message is not retrieved (there is no Message or no Message that can be executed immediately, and there is no blocking status) or the Message needs to be executed later;
  • Step 3: judge the number of idlehandlers. If there is no IdleHandler, directly end the current cycle and mark the cycle to enter the suspended state.
  • Step 4: determine whether it is the first time and initialize the List of IdleHandler
  • Step 5: start traversing all idlehandlers
  • Step 6: execute the queueIdle method of IdleHandler in turn
  • Part 7: judge whether the IdleHandler is permanent or one-time according to the return value of the queueIdle of each IdleHandler, and remove the non permanent from the array;
  • Step 8: modify the quantity information pendingIdleHandlerCount of IdleHandler to avoid repeated execution of IdleHandler.

This is the core principle of IdleHandler. It is triggered only when the message queue is empty or when the header message of the message queue is a delayed message. When the message queue header is a delayed message, it will only be triggered once. In the previous section of getting messages, we said that the delay message will trigger blocking when it enters the next loop after ending the current loop.

Application of Handler in Framework layer

I wonder if you have ever thought about why Android directly calls Looper.prepare() and Looper.loop() methods in the main thread for your convenience? Isn't it a bit like killing a chicken with an ox knife?

In fact, it is far from that simple. If you look at the source code of the framework, you will find that the operation of the entire android app is based on the Handler. The operation of the four components, their life cycle is also based on the Handler event model, and click events. All these are generated by the Android system framework layer, and then handed over to a Handler for processing. This Handler is the inner class H of ActivityThread. Post a screenshot of its code

It can be seen that the life cycle of the four components and even the memory are insufficient, and handler s are involved.

This also explains why executing time-consuming tasks in the main thread will lead to UI jamming or anr: because the logic code of all main threads, that is, UI threads, is executed in the component life cycle, and the life cycle is controlled by the Handler's event system. When performing time-consuming operations in any life cycle, This will cause the subsequent messages in the message queue to not be processed in time, which will cause delay until they are visually stuck. In serious cases, it will further trigger the occurrence of ANR.

To learn more about Android, please click the small card below

Tags: Java Android RabbitMQ Design Pattern Handler

Posted on Wed, 29 Sep 2021 13:46:14 -0400 by qing