Android handler thread switching principle

Time:2022-6-19

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