Handler is the upper interface of the message mechanism, and the basic development can only interact with the Handler. Handler can switch a task to the thread specified by Handler. For example, using handlers to update the UI in child threads.
The Android messaging mechanism is basically how the Handler works. Handler also relies on MessageQueue, Looper, and the ThreadLocal used within Looper. MessageQueue is a MessageQueue used to store messages sent by the Handler. It is actually a single-linked list structure. Looper loops endlessly through the message queue looking for messages, fetching them and waiting for none. ThreadLocal essentially stores data in each thread. The role in Looper is to store Looper instances for each thread. Because we know that creating a Handler requires a Looper instance of the thread, and non-UI threads don’t have Looper by default.
Handler usage and Overview
1.1 Procedure
-
On the thread where the task is executed, use looper.prepare () to create Looper instances for the thread.
-
In the thread where the task is executed, create Handler instances.
-
In the thread of task execution, use looper.loop () to start the message loop.
-
In the thread from which the task originated, the message is sent using Handler instances.
For example 🌰 as shown below, clicking the button to send a message in the main thread will execute it in the child thread. In order to fully demonstrate the steps, this example creates a handler on the child thread, and sends and sends messages on the main thread. In practice, we create a handler on the main thread, send messages on the child thread and then perform UI updates on the main thread. The main thread has Looper enabled by default, so we don’t need steps 1 and 3.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testHandler();
}
private void testHandler(a) {
new Thread(new Runnable() {
@Override
public void run(a) {
ThreadLocal < looper >. Set (new looper ())
Looper.prepare();
// create a handler instance
// This overrides handleMessage. Handler is an instance of a subclass of Handler
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, "child thread, handleMessage: what="+msg.what); }};//3, looper, sthreadLocal.get (), looper, queue, queue
Looper.loop();
}
}).start();
}
public void onClick(a){
SendMessage sends messages to queue. EnqueueMessage (MSG), that is, messages are queued.
Log.i(TAG, "main thread, sendMessage");
Message message = Message.obtain();
message.what = 100;
mHandler.sendMessage(message);
}
Copy the code
1.2 Background of Using Handler
The Handler can switch UI updates from child threads to the main thread. Why switch? We know that UI access can only be done on the main thread. The child thread accessing the UI will get an exception because the thread is checked in the View tree so that only the thread that created the View tree can access the View. Normally, the thread that creates the View is the main thread, the UI thread, so child thread access will be abnormal.
void checkThread(a) {
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
Also, UI threads generally cannot do time-consuming operations, otherwise ANR will occur. So when you need to update the UI after the child thread has done a time-consuming operation, you need to use Handler. Why use checkThread() to ensure that child threads do not access the UI? Because UI controls are not thread-safe. Then why not lock it? One is that locking complicates UI access. Second, locking reduces UI access efficiency and blocks some threads from accessing the UI. Instead, use the single-threaded model to handle UI operations, switching them with handlers.
Second, Android message mechanism analysis
As mentioned earlier, the Android messaging mechanism consists of several concepts: Handler, MessageQueue, Looper, and ThreadLocal used within Looper. The following details.
2.1 ThreadLocal
ThreadLocal = new threadLocal; threadlocal.set (value); threadlocal.get ();
Set booleanThreadLocal to true for the main thread, false for thread A, and null for thread B. Then each thread prints the result of booleanthReadLocal.get (). Find that the value of get is different for each thread. It is the value of set in each thread. Here’s the magic: The same booleanthreadLocal.get () has different results on different threads.
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run(a) {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, "testThreadLocal: a thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run(a) {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, "testThreadLocal: b thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
Copy the code
Results:
2020-01- 0810:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01- 0810:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01- 0810:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01- 0810:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01- 0810:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01- 0810:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
Copy the code
Let’s look at the get() and set() methods of ThreadLocal.
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get(a) {
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;
returnresult; }}return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
Get () : Gets the ThreadLocalMap of the current thread. We then pass in an instance of threadLocal, get the key-value pair Entry, and then get the value of the Entry. If the map or value is empty, the map and value are initialized.
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
Set () also gets the ThreadLocalMap of the current thread, and then sets the ThreadLocal instance as the key, along with the value, to the map. Create a map without it and initialize it.
Looking at Thread, there is an instance of ThreadLocalMap, threadLocals, which is empty by default.
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code
What is a ThreadLocalMap? ThreadLocalMap is an inner class of ThreadLocal that acts like a Map and contains an Entry[] attribute table. So the get and set methods above are the Entry and store methods of ThreadLocalMap []. Let’s look at it in more detail.
/** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * *@param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal
key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if(e ! =null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal
key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
Use Entry[] to store multiple threadlocal-value key-value pairs, with array index associated with hashCode that is an instance of threadLocal. The only instance of ThreadLocalMap is the variable threadLocals that createMap(Thread t, t firstValue) assigns to Thread. For example, the table[] of thread A threadLocalMap can store 3 key-value pairs of int, String, and Boolean. Again, the example above.
(A regular HashMap has a fixed key-value type; The key of a threadLocalMap is ThreadLocal, and the value is T.
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run(a) {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, "testThreadLocal: a thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run(a) {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, "testThreadLocal: b thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
Copy the code
Results:
2020-01- 0810:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01- 0810:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01- 0810:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01- 0810:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01- 0810:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01- 0810:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
Copy the code
So far we know that the function of a ThreadLocal is to operate on threadLocals inside a thread to store and fetch values. The actual type of value is the generic T defined when ThreadLocal is instantiated.
2.2 messageQueue
MessageQueue, a messageQueue, is actually a one-way linked list. Look at save and fetch messages.
EnqueueMessage (), holding messages, insertion of single linked lists.
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 (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr ! = 0 because mQuitting is false.
if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code
Next () : takes a message, loops indefinitely without a message, blocks.
Message next(a) {
/ /...
// if there is MSG, return; if there is no message, loop indefinitely, block. For example, quit, return NULL.
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;
if(msg ! =null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 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 when there is a message
returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
//quit Returns null
return null;
}
// ...
}
Copy the code
2.3 which
Looper, message circulator.
Let’s look at the static method prepare () :
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare(a) {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if(sThreadLocal.get() ! =null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* 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(); }}private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
SThreadLocal is a static constant with a value of type Looper. The prepare() method calls sthreadLocal. set(new Looper) to create an instance of Looper and set it to table[I] in the ThreadLocalMap property of the current thread.
By default, the mQueue instance is created when looper instance is created. PrepareMainLooper () is the main thread, which creates a Looper instance for the main thread.
Let’s look at how to get looper and Queue:
/** * Returns the application's main looper, which lives in the main thread of the application. */
public static Looper getMainLooper(a) {
synchronized (Looper.class) {
returnsMainLooper; }}/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
/**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
public static @NonNull MessageQueue myQueue(a) {
return myLooper().mQueue;
}
Copy the code
MyLooper () method, calling sthreadLocal.get (). This is how ThreadLocal is used. The static constant sThreadLocal is used to get Looper instances for each thread.
Looper’s quit, two types, immediately exit, execute the message and then exit.
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@linkHandler#sendMessage(Message)} method will return false. * </p><p class="note"> * Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit(a) {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link#loop} method to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * However pending delayed messages with due times in the future will not be * delivered before the loop terminates. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely(a) {
mQueue.quit(true);
}
Copy the code
Static method loop() : uses threadlocal.get () to fetch the Looper of the current thread, queue it, loop the message to the handler’s dispatchMessage method -handleMessage method. The only way out of the loop is to get null, which is because quit or quitSafly was called. == Because the static method loop() is called in a thread, == is executed in the thread of the loop no matter where the handler sends the MSG from.
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop(a) {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// ...
for (;;) {
// if MSG, queue.next() blocks, loop() will block. Next processes MSG whenever it has it, in an infinite loop.
Message msg = queue.next(); // might block
if (msg == null) {
// The loop is broken only when quit() is called
// No message indicates that the message queue is quitting.
return;
}
// ...
// Target (handler) handles the message, dispatchMessage is executed on the thread where loop() calls it.
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}... }}Copy the code
The process prepare () -new hanler() -loop () is called consecutively on the same thread. Ensure that handleMessage executes in the current thread. Even if handler.sengMessage() is called in another thread.
2.4 Handler
Send, process messages. The Handler constructor calls looper.mylooper (), which gets the current thread’s Looper, and throws an exception if it doesn’t.
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler(a) {
this(null.false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {... mLooper = Looper.myLooper();if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
To send a message is to put the message into a queue
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
Process messages according to how handlers are created and used.
/** * Handle system messages here. */
public void dispatchMessage(Message msg) {
if(msg.callback ! =null) {
// MSG. Callback is the runable sent by handler.post()
handleCallback(msg);
} else {
if(mCallback ! =null) {
//mCallback is passed in when a Handler is created.
if (mCallback.handleMessage(msg)) {
return; }}// Override handleMessage() to create a handlerhandleMessage(msg); }}Copy the code
The message mechanism of the main thread
Messages for the main thread
Of which:
/**
* 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
Static method main for ActivityThread:
final H mH = new H();
public static void main(String[] args) {...//1. Prepare the main thread Looper
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())); }}}// This instantiates the ActivityThread, which instantiates the mH above, which is the handler.
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
/ / get the handler
if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); }...// start the main thread looper
Looper.loop();
// Since the Looper of the main thread cannot exit, exiting will not accept events. In the event of an unexpected exit, an exception is thrown
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
H deals with the start and stop of the four components. ActivityThread communicates with AMS through the ApplicationThread. When AMS completes the request for the ActivityThread, it calls back to the Binder method in the ApplicationThread. ApplicationThread then sends the message using H, which is then switched to the ActivityThread for execution, that is, on the main thread. This is the message loop for the main thread.
.
Well, that’s all for today, welcome to comment ~
Your likes, comments, favorites, forwarding, is a great encouragement to me!
Welcome to my public account: