Have done Android development should know Handler, whether it is development or interview should know, today a brief summary. I’ll just summarize the main points.

Android messaging mechanism 1-Handler(Java layer) Android messaging mechanism 2-Handler(Native layer)

Select /poll/epoll comparative analysis welcome to the original article

Handler

Model 1.

  • Message: Messages are divided into hardware generated messages (such as buttons and touches) and software generated messages.
  • MessageQueue: The main function of the message queue to deliver messages to the message pool (MessageQueue.enqueueMessage) and take messages from the message pool (MessageQueue.next);
  • Handler: Message helper class, the main function is to send various message events to the message pool (Handler.sendMessage) and handle corresponding message events (Handler.handleMessage);
  • Looper: Executes continuously in a loop (Looper.loop), the message is distributed to the target handler according to the distribution mechanism.

2. Key API

2.1 sendEmptyMessage

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}
Copy the code

2.2 sendEmptyMessageDelayed

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
Copy the code

2.3 sendMessageDelayed

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code

2.4 sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
Copy the code

2.5 sendMessageAtFrontOfQueue

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}
Copy the code

This method adds the Message to the queue head of the Message queue by setting the Message trigger time to 0.

2.6 post

public final boolean post(Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
Copy the code

2.7 postAtFrontOfQueue

public final boolean postAtFrontOfQueue(Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}
Copy the code

2.8 enqueueMessage

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

section

Handler. SendEmptyMessage () series of methods, such as the final call MessageQueue. EnqueueMessage (MSG, uptimeMillis), add a message to the message queue, with uptimeMillis for the current system, run time, Sleep time is not included. SendMessageAtFrontOfQueue and postAtFrontOfQueue two when the method of the triggering time of the set to 0, so the message will be in the front, the rest of the way the triggering time of the transfer of the current of the system running time

3. Message blocking and wake up

The native method in MessageQueue is as follows:

private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
Copy the code

3.1 nativeInit ()

This method will create NativeMessageQueue at the native layer. During the process of NativeMessageQueue creation, native layer Looper will be created. This method returns a long value that is strongly represented by the NativeMessageQueue created by the native layer. Call epoll’s epoll_create()/epoll_ctl() to complete the listening for readable events on mWakeEventFd and mRequests

3.2 nativeDestroy ()

NativeMessageQueue Removes the strong reference and if the reference, the resource is freed.

3.3 nativePollOnce ()

The method eventually calls epoll_WAIT to wait for an event to occur or time out

// Wait for an event to occur or time out, in the nativeWake() method, write a character to the pipeline write side, then this method will return; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);Copy the code

Epoll_wait () returns if and only if:

  • POLL_ERROR: if an error occurs, jump to Done.
  • POLL_TIMEOUT, timeout occurs, jump to Done;
  • If an event occurs in the pipeline is detected, corresponding processing shall be made according to the situation:
    • If the event is generated by the pipeline reader, the data of the pipeline is read directly.
    • If it is another event, it processes the request, generates the corresponding reponse object, and pushes it into the Reponse array.

3.4 nativeWake ()

NWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, & Inc, sizeof(uint64_t))));Copy the code

Let epoll_wait() return by writing the character 1 to the pipe

select/poll/epoll

In select/poll, the kernel scans all monitored file descriptors only after the process calls certain methods. Epoll registers a file descriptor in advance with epoll_ctl(). Once a file descriptor is in place, the kernel uses a callback mechanism similar to callback. Activate this file descriptor quickly and be notified when the process calls epoll_wait(). The traversal of file descriptors has been removed, and the callback mechanism is monitored. That’s the beauty of epoll.)

Epoll advantage

  1. There is no limit to the number of descriptors to monitor, and the maximum number of FD’s supported is the maximum number of files that can be openedcat /proc/sys/fs/file-maxCheck, generally speaking this number and system memory relationship is very large, with 3G mobile phone for this value is 200,000-300,000.
  2. IO performance does not decrease as the number of monitored FDS increases. Epoll is different from select and poll polling. Instead, it is implemented with a callback function defined for each FD. Only ready FDS execute the callback function.

Epoll is not much more efficient than SELECT /poll without a large number of idle or dead connections. However, when a large number of idle connections are encountered, epoll is much more efficient than SELECT /poll.

ThreadLocal

Usage scenario:

Typical scenario 1: Each thread needs an exclusive object (usually a utility class, typically SimpleDateFormat and Random)

Typical scenario 2: Global variables need to be stored within each thread (for example, to get user information in an interceptor), which can be used directly by different methods without the hassle of passing parameters.

The principle of

ThreadLocal has an internal class, ThreadLocalMap, as the storage structure. ThreadLocalMap has an internal class, Entry, as the final KV storage structure. ThreadLocalMap has an internal array of entries to hold entries

The Thread class internally holds a ThreadLocalMap to store data, one for each object, so that threads can be isolated

ThreadLocal is essentially a utility class and key, with itself as a key, the data to be stored as a value, and the KV stored in a ThreadLocalMap held internally by the calling thread to complete data storage and thread isolation

code

1. The Thread class

public class Thread implements Runnable { ... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; .Copy the code

2. ThreadLocalMap

Static class ThreadLocalMap {/* * Entry inherits WeakReference and uses ThreadLocal as key. * If key is null(entry.get() == null), This means that the key is no longer referenced, * so the entry can also be removed from the table. */ static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; Private static final int INITIAL_CAPACITY = 16; private static final int INITIAL_CAPACITY = 16; /** * The table and Entry classes are defined below * Also, the array length must be a full power of 2. */ private Entry[] table; /** * Specifies the number of entrys in the array, which can be used to determine whether the current table usage exceeds the threshold. */ private int size = 0; /** * Specifies the threshold for expansion. If the table usage exceeds the threshold, expansion is performed. */ private int threshold; // Default to 0 }Copy the code

3. ThreadLocal set

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); // (3.1) else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }Copy the code

3.1 ThreadLocalMap. Set

private void set(ThreadLocal<? > key, Object value) { Entry[] tab = table; int len = tab.length; Int I = key.threadLocalHashCode & (len-1); /** * ThreadLocalMap uses' linear probe 'to resolve hash conflicts. * This method probes the next address at a time until there is an empty address and then inserts it. If no empty address is found in the entire space, an overflow occurs. For example, if the current table has a length of 16, that is, if the calculated hash value of the key is 14, if * table[14] already has a value and its key is not the same as the current key, then a hash conflict has occurred. In this case, add 14 by 1 to get 15. * Select table[15] from table[15], and then return to 0. Select table[0] from table[15], and so on until insert is available. * Consider Entry[] table as a circular array as described above. for (Entry e = tab[i]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); if (k == key) { e.value = value; return; } // The Entry in the current array is a stale element if (k == null) {// Replace the stale element with the new one; This method does a lot of garbage cleaning to prevent memory leaks replaceStaleEntry(key, value, I); return; } } tab[i] = new Entry(key, value); int sz = ++size; / * * * cleanSomeSlots used to remove those um participant et () = = null elements, * the data key associated objects have been recovered, so the Entry (table [index]) can be null. * If no entry is cleared, and the current usage reaches 2/3 of the length defined by the load factor, then * rehash (perform a sweep of the entire table) */ if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private final int threadLocalHashCode = nextHashCode(); AtomicInteger is an Integer class that provides atomic operations. Private static AtomicInteger nextHashCode = new AtomicInteger(); // 'HASH_INCREMENT = 0x61C88647', the value is related to the Fibonacci sequence (golden ratio), the main purpose of which is to evenly distribute the hash code in the 2 ^ n array, namely the Entry[] table, so as to avoid hash collisions as much as possible. private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }Copy the code

3. The ThreadLocal GET method

public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); // return if (e! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; Null return setInitialValue(); // (0) 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); // (0.1) private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // look for while (e! = null) { ThreadLocal<? > k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } // (1) private T setalValue () {T value = initialValue(); // (2) Thread t = thread.currentthread (); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); return value; } // (2) protected T initialValue() {return null; }Copy the code

Difficult points:

  • How to solve memory leak

    • The reason is that it will not be done until the next set of datareplaceStaleEntryThe empty value is cleared
    • You are advised to remove the data manually after using it
  • How to resolve Hash conflicts

    • Linear detection method

Reference:

  • Github.com/Snailclimb/…

  • zhuanlan.zhihu.com/p/167937566