— Notes, non-professional articles, if there is a vague expression, please refer to other god articles or source code, there is a mistake thank you for pointing out
Handler is the upper interface of the Android messaging mechanism. Developers only need to interact with the Handler during development. Handler is primarily used by Android developers to update the UI, but this is really just a special case application of Handler. Essentially, the Handler does the job of switching threads.
Why can’t the UI be updated on child threads
In the View of
requestLayout
Procedure is called
CheckThread method of ViewRootImpl
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
Copy the code
MThread = thread.currentThread () during ViewRootImpl creation, which means that if we create a View in thread1, the View can only be modified in thread1. Normally, views that are created are created in the UI thread. If the UI is updated in a child thread, the checkThread() above throws an exception that causes the program to crash.
Why does Android use checkThreads to prevent child threads from accessing the UI? UI controls are not thread-safe, concurrent access can cause UI controls to be in an unpredictable state, and UI locking reduces UI access efficiency.
Second, the ThreadLocal
ThreadLocal is an internal data store class that stores data in a specified thread. Once the data is stored, only the specified thread can retrieve the stored data. The scope of a Looper is the current thread, so using THreadLocal to access a Looper is appropriate. As described above for ThreadLocal, a global HashMap could do the same thing. Why not?
1. Security: A global Hashmap can use other threads as keys to fetch Looper from the HashMap in any thread when thread description is known. Obviously, this does not conform to the scope of LoOPER and it is difficult to guarantee its security.
2. Reuse. If hashMap is used, looper needs to be provided with a LooperManager. In the same way that ActivityThread and AMS are applied to ThreadLocal, they need the corresponding Manager.
3. Weak references: A ThreadLocal encapsulates a weak reference to a value and does not need to consider memory leaks at ThreadLocal when the value needs to be destroyed.
Third, MessageQueue
The Message mMessages; // Single list data structure queueCopy the code
Entry method:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) {if (mspapers) {/** If the current queue is exiting, E = new IllegalStateException(MSG. Target + "Sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; // If the current list is empty or when = 0 or when is executed before the queue header message, Message inserted into the first team if (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; // insert within the middle of the queue. Usually we don't have to wake // Up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! = 0 because mQuitting is false. Wake native if (needWake) {nativeWake(mPtr); } } return true; }Copy the code
Next is an infinite loop. When there are no messages in the message queue, next blocks until a new message is queued, and next returns the message.
Message next() { // 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(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; Handler() = new handler (); The messages sent with this handler are actually synchronous messages. If (MSG! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. // Find the next asynchronous message in the queue ignore the synchronous message 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 Set timeout to wake up native 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(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); 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. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; Keep = idler.queueidle (); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; }}Copy the code
Four stars:
Looper will constantly check MessageQueue for new messages and process them immediately if there are any, otherwise block.
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
Constructors are private and we cannot create a looper using constructors
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)); }Copy the code
The constructor is used in the prepare method, and the prepare New Looper object is placed in a ThreadLocal.
Once Looper is created, you need to call Looper’s loop method to get Looper to work.
Five. Handler
Handler, as an application layer API of the Android message mechanism, mainly contains the sending and receiving processes. Messages can be put into MessageQueue through a series of methods, post and Send, all of which end with the sendMessageAtTime method.
The Handler sends a message by inserting a message into the message queue. In the Looper loop, the message will be returned to Looper through MessageQueue’s Next method. After Looper processing, the Handler’s dispatchMessage method will be called to enter the Handler’s message processing stage.
The Handler first checks if the message’s callback is null, and if not, processes the message through the handleCallback. Message’s callback is a Runnable object
private static void handleCallback(Message message) {
message.callback.run();
}
Copy the code
Six, IdleHandler
IdleHandler is not part of the Handler class. It is an interface defined in MessageQueue and contains a queueIdle method. The instance implementing idleHandler is passed to the mIdleHandlers list of MessageQuee through the addIdleHandler method of the MessageQueue object. The IdleHandler object is executed only when the MessageQueue MessageQueue has no content to process. The IdleHandler object is kept based on the return value of the queueIdle root method, and if true is returned it is kept and executed the next time the next method has no messages to process.
public static interface IdleHandler { boolean queueIdle(); } public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); }}Copy the code
Message loop for main thread
public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. // It will be in the format "seq=114" long startSeq = 0; if (args ! = null) { for (int i = args.length - 1; i >= 0; --i) { if (args[i] ! = null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code
The main thread for Android is ActivityThread. This is called in the main entry method
Looper.prepareMainLooper();
Looper and MessageQueue for the main thread are created using this method. Looper.loop () at the end of the main method
From there, the Activity’s main method goes into an infinite loop. Anything that needs to run on the main thread is executed by the ActivityThread Handler– ActivityThread.h.