Handler Mechanism Principle Resolution (ThreadLocal,Looper,MessageQueen,Message Relations)

Keywords: Android network Java

Why is there a handler mechanism?

In Android, all UI controls run in the main thread. If we access the UI from the sub-thread, the system will report an exception. Why not allow subthreads to access the UI? Because Android's UI controls are not County safe, in order to prevent the UI controls from being in an uncontrollable state, they are forbidden. Starting with Android 2.3, the main thread is not allowed to make any network requests, so how can the requested data be synchronized to the UI? Therefore, in order to facilitate inter-thread communication, an andler mechanism is created, which aims at facilitating data transfer and switching between threads. Updating the UI is the most common thing we use.

Let's start with a piece of code:

private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            // TODO: 9/7/17 do what you wanna  
        }
    };

This is a typical handler usage. We usually initialize the handler in the main thread when we use it. Is it possible to create handler in the sub-thread? The answer is yes, but if the handler is created in the direct sub-thread, the exception will be reported:

RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");

It's clear that we need to initialize Looper in the subthread through Looper.prepare(); so what is this Looper? What does it have to do with handler? Don't worry. Listen to me carefully.

Before we talk about Looper, let's talk about MessageQueen. As its name implies, it is a message queue and follows the principle of "first in first out". But its interior is not a queue, but is realized by the data structure of single-linked list. The advantages of single-linked list need not be mentioned. The speed of insertion and deletion is relatively fast. The corresponding methods are enqueueMessage and next. But he doesn't process messages, and Looper handles them.

Looper can be said to be the core class of handler. It is the medium of linking MessageQueen and handler. When it is opened, it will go to MessageQueen to get messages in an infinite loop, and then hand them to Handler for processing. Before initializing Handler, you must initialize Looper first, otherwise the exception will be reported. Looper is bound to threads, each thread has only one Looper. This can also be seen from Looper's static initialization method:

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));
    }

The judgment of the if statement is to determine whether there is a Lower of the current thread, and if so, to report an exception. We note that his judgment is based on sThreadLocal, which is an example of ThreadLocal. This is an important thread tool class. Let's talk about it in detail below.
ThreadLocal

ThreadLocal is a data storage class within a thread in java. It's not a Thread, but it's very simple. In fact, every thread that uses this variable provides a copy of the value of the variable. Each thread can change its own copy independently without conflict with the copy of other threads. When some data is scoped by threads and different threads have different copies of data, you can consider using ThreadLocal. Obviously, the scope of Looper is the current thread. Through the following examples, the characteristics and usage of ThreadLocal are briefly shown.

public static void main(String[] args){
        final ThreadLocal<String> mThreadLocal = new ThreadLocal<String>();
        mThreadLocal.set("main");
        new Thread("Thread-1") {
            public void run() {
                mThreadLocal.set(Thread.currentThread().getName());
                System.out.println("currentThread:"+Thread.currentThread().getName()+","+mThreadLocal.get());
            };
        }.start();
        System.out.println("currentThread:"+Thread.currentThread().getName()+","+mThreadLocal.get());
    }

First, I initialize a ThreadLocal variable mThreadLocal, then call the set method in the main thread, put in the string "main". Then I open a sub-thread, put in the name of the sub-thread, and print the data obtained by the get method in the sub-thread and the main thread respectively. The printing results are as follows:

currentThread:main,main
currentThread:Thread-1,Thread-1

From the log, we can see that although the same ThreadLocal is called when the data is plugged, the data obtained by different threads is different. It's great, but how on earth does he implement different copies of different threads? Let's look at the source code of its set method.

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }    

From the set method, we can see that we get a ThreadLocalMap object through getMap, and then set it through the set method of ThreadLocalMap. If the ThreadLocalMap object is null, we execute the createMap method.

From the method content of getMap, we can see that he is a member variable within the returned Thread: ThreadLocal.ThreadLocalMap (see here I just want to talk about pit daddy, mmp). So this ThreadLocalMap is the key to data storage. It's an internal static class. Let's see what new ThreadLocalMap (this, first Value) does specifically:

static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

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);
        }

table is an array, and Entry is the static inner class of ThreadLocalMap, inherited from WeakReference, with ThreadLocal as the key, and ThreadLocal's generic instance as the value. In the new ThreadLocal Map, key and value are stored in Entry, and then Entry is put into the array through a certain algorithm (the specific content of the algorithm is not analyzed, interested brothers can see for themselves). Next let's look at the set(ThreadLocal,Object) method of ThreadLocalMap:

private void set(ThreadLocal key, Object value) {
            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();
        }

You can see that it opens a for loop inside to traverse the array table, and when Entry is not null, take the value inside Entry and replace it. Here we have figured out the relationship between ThreadLocal and ThreadLocalMap, as well as their creation and set methods.

Next, let's look at the get method of ThreadLocal:

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

 private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}

It should be easy to understand what I just said and look at it again. The code logic is clear. Get Entry through ThreadLocalMap, and then get the value of Entry. It's worth noting that initial Value () returns null directly, so instead of calling ThreadLocal.set by the corresponding thread, we get null through get.

By analyzing the source code, we can conclude that ThreadLocal get and set methods, ThreadLocalMap objects, and ThreadLocalMap objects rely on Thread to be created, so the data accessed in different threads is different. With that in mind, it's easy to understand Looper.
(To be continued)

Posted by deras on Fri, 14 Dec 2018 10:33:03 -0800