Handler is a class we often come into contact with in development, because in Android, sub threads generally cannot update the UI
So we will use the handler to switch to the main thread to update the UI. How does the handler switch between different threads?
Let’s start with an example
1. Simple use of ThreadLocal
public class HandlerActivity extends AppCompatActivity {
private final static String TAG = "HandlerActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. Create a ThreadLocal object,
final ThreadLocal threadLocal = new ThreadLocal<>();
//Assign a value to ThreadLocal
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log. D (tag, "current thread:" + thread. Currentthread() getName());
}
});
//Create a new thread and assign a value to ThreadLocal
Thread thread = new Thread(){
@Override
public void run() {
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log. D (tag, "current thread:" + thread. Currentthread() getName());
}
});
threadLocal.get().onExecute();
}
};
//Get the ithreadcallback interface instance and execute the onexecute() method
threadLocal.get().onExecute();
thread.setName("otherThread");
//Start the thread, get the ithreadcallback interface instance inside the thread and execute the onexecute() method
thread.start();
}
//Defines a callback interface
public interface IThreadCallback{
void onExecute();
}
}
As you can see, we have called the set () method twice in total, and the executing threads are also under different threads. Therefore, the handler uses ThreadLocal to
For thread switching So how does ThreadLocal do it?
2. Principle of ThreadLocal
First, let’s look at threadlocal Let’s look at what the set () method does line by line
public void set(T value) {
//Gets the thread executed by the current method (set method)
Thread t = Thread.currentThread();
//Getmap () =====> t.threadlocals, which is a member variable defined in the thread class, that is to say
//Each thread has a unique threadlocals, which is a threadlocal Threadlocalmap type,
//It is an internal class of ThreadLocal
ThreadLocalMap map = getMap(t);
if (map != null)
//Directly bind value to the current ThreadLocal
map.set(this, value);
else
//If it is blank, new threadlocalmap (this, value) will be directly generated;
createMap(t, value);
}
Getmap() =====> t.threadlocales, which is a ThreadLocal defined in the thread class Threadlocalmap object,
Threadlocalmap is an internal static class of ThreadLocal. If the map is not empty, it will call the set method to pass us in
Object is encapsulated into an entry object and saved in the entry[] array
private void set(ThreadLocal> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
...
...
//New creates an entry () object, which is also the internal static class of ThreadLocal
//There is an object type member variable inside, which is used to save the value we passed in
tab[i] = new Entry(key, value);
...
...
}
Get() method
public T get() {
//Get the thread of the current method (get method)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//Get the saved value from the entry[] table array
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//Force the value we passed in to the type represented by T, and this value is the object of the corresponding thread
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
The above basically describes the basic process of ThreadLocal. It doesn’t matter if you still can’t understand it,
As long as you know which thread you call the set method, the object belongs to that thread, because each thread has its own
The memory region and variables defined in the thread will exist in the current thread’s own memory region
For example:
//This is an instance of new when the main thread initializes. It exists in the memory area of the main thread
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log. D (tag, "current thread:" + thread. Currentthread() getName());
}
});
Thread thread = new Thread(){
@Override
public void run() {
//Not in the main thread, so new ithreadcallback()
//It is saved in the current thread (otherthread). Remember, we give it
//Set a name otherthread
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log. D (tag, "current thread:" + thread. Currentthread() getName());
}
});
threadLocal.get().onExecute();
}
};
3. UI thread switching principle of handler
After knowing the principle of ThreadLocal, it is actually very simple, similar to our above example
We know that creating a handler in a child thread (not a UI thread) will result in an error
The solution is also very simple. Just call looper The prepare () method will not report an error,
Let’s take a look at looper What does prepare() do
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));
}
As for the mechanism of message circulation, the reader should find relevant materials to understand, which is not the focus of this article Here is just a brief mention
You can see that there is an instance of ThreadLocal within looper, sthreadlocal. Calling the prepare() method will directly create a new looper() object,Bind the looper object to the corresponding thread through ThreadLocal, so that all the methods and variables executed in the looper are in the corresponding thread, so as to achieve the effect of switching between different threadsThe looper class is an important member of the handler that can complete message sending. After creating the handler, call looper Loop() can perform a message loop. There is an internal dead loop that continuously fetches messages from the message queue and calls messages in sequence target. Dispatchmessage() method, and finally call handlemessage() method, message Target is an object of type handler, which is assigned when sendmessage()
We can try toast in the child thread, if we don’t call looper Loop() method. On previous versions of the system, toast will not be displayed. You must call loop Loop() performs a message loop to display Android11 can be displayed without calling. It should be called internally for us
Handlemessage can be executed in the main thread because the loop object itself is created in the main thread Some people will say that I didn’t call looper Prepare () method, why doesn’t it report an error? Because the looper object of the main thread is already in the activitythread In the main method
Already called, so we don’t need to call looper Prepare() can be used If you want to get the looper object of the main thread, you can call looper Getmainlooper () method. You can use this looper object to create a handler object and let its handlemessage method execute in the main thread