Analysis and understanding of Android Handler

Handler in Android is a mechanism for handling messages and inter-thread communication. It can send Runnable objects or Message objects to a specific thread for processing.
The main purpose of using Handler is to communicate between different threads, especially after performing some tasks in the background thread, send the results to the UI thread for update.
It can be used to switch threads, and is often used to receive data sent by sub-threads and update the UI in the main thread.
Flowchart:
Insert image description here
The following is a sample code that demonstrates how to create a Message object and send it to the Handler:

 // 创建一个Handler对象
Handler handler = new Handler() {
    
    
    @Override
    public void handleMessage(Message msg) {
    
    
        // 在UI线程中处理接收到的消息
        switch (msg.what) {
    
    
            case 1:
                // 处理消息类型为1的情况
                String messageText = (String) msg.obj;
                // 执行相应的操作
                break;
            case 2:
                // 处理消息类型为2的情况
                // 执行相应的操作
                break;
            // 可以根据需要处理其他消息类型
        }
    }
};

// 在其他线程中创建并发送Message对象
Thread thread = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        // 创建一个Message对象
        Message message = handler.obtainMessage();
        
        // 设置消息类型
        message.what = 1;
        
        // 设置消息内容
        message.obj = "Hello, Handler!";
        
        // 发送消息给Handler所关联的线程
        handler.sendMessage(message);
    }
});

// 启动线程
thread.start();

Handler source code analysis

The first main line: Joining the team (Who joined the team? How did they join? What team did they join?)

First enter handler.sendMessage(msg);

    /**
     * Pushes a message onto the end of the message queue after all pending messages
     * before the current time. It will be received in {@link #handleMessage},
     * in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessage(@NonNull Message msg) {
    
    
        return sendMessageDelayed(msg, 0);
    }

Then click in sendMessageDelayed(msg, 0);

    /**
     * Enqueue a message into the message queue after all pending messages
     * before (current time + delayMillis). You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    
    
        if (delayMillis < 0) {
    
    
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Then come in sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    
    
        MessageQueue queue = mQueue;
        if (queue == null) {
    
    
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Enter enqueueMessage(queue, msg, uptimeMillis)

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    
    
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
    
    
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

queue.enqueueMessage(msg, uptimeMillis) actually executes the enqueueMessage method of Message

 boolean enqueueMessage(Message msg, long when) {
    
    
        if (msg.target == null) {
    
    
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
    
    
            if (msg.isInUse()) {
    
    
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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进入标记
            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;
    }

The process of joining the queue is explained: Handler.sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage()->enqueueMessage() of the MessageQueue class. Handler sends a message (message) to join the queue (MessageQueue
)
. When, if there is no data in the queue, put msg directly at the head; if there is, you need to traverse the data in the queue and enter the insertion operation;

The second main line is out of the queue (consumption)

When the app starts, the ActivityThread class will be called and
the main method in the ActivityThread class will be called.

 public static void main(String[] args) {
    
    
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        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()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
    
    
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
    
    
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Click in to see Looper.loop();

  /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
    
    
        final Looper me = myLooper();
        if (me == null) {
    
    
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
    
    
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
    
    
            Message msg = queue.next(); // might block
            if (msg == null) {
    
    
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
    
    
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
    
    
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    
    
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
    
    
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
    
    
                msg.target.dispatchMessage(msg);
                if (observer != null) {
    
    
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
    
    
                if (observer != null) {
    
    
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
    
    
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
    
    
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
    
    
                if (slowDeliveryDetected) {
    
    
                    if ((dispatchStart - msg.when) <= 10) {
    
    
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
    
    
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
    
    
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
    
    
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
    
    
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
    
    
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

There is Message msg = queue.next() in the for loop; it can be found in queue.next() of MessageQueue

 @UnsupportedAppUsage
    Message next() {
    
    
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
    
    
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        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 msg;
                    }
                } else {
    
    
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
    
    
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
    
    
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
    
    
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
    
    
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
    
    
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
    
    
                    keep = idler.queueIdle();
                } catch (Throwable t) {
    
    
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
    
    
                    synchronized (this) {
    
    
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

The for loop in MessageQueue takes the object, finds the object and returns it. At this time, msg in the Looper method is the returned object.
And use msg to call the dispatchMessage method in the loop() method

  msg.target.dispatchMessage(msg);

It explains that the process of the second main line is: main()->looper.loop()->queue.next() of the ActivityThread class
finds the msg object through next(), and then binds the target through msg, which is the handler, and calls dispatchMessage callback handleMessage method

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
    
    
        if (msg.callback != null) {
    
    
            handleCallback(msg);
        } else {
    
    
            if (mCallback != null) {
    
    
                if (mCallback.handleMessage(msg)) {
    
    
                    return;
                }
            }
            handleMessage(msg);
        }
    }

The third main line: What is the relationship between the four classes Handler, MessageQueue, Message, and Looper, and how to connect them in series?

  1. Handler creates Message object in mHandler.obtainMessage()
  2. In the enqueueMessage method in MessageQueue, Handler puts the created object into MessageQueue.
  3. Looper is created through the main() method of ActivityThread. MessageQueue will be created at the same time when creating Looper.

In ActivityThread class

Looper.prepareMainLooper();

prepareMainLooper() click in

    @Deprecated
    public static void prepareMainLooper() {
    
    
        prepare(false);
        synchronized (Looper.class) {
    
    
            if (sMainLooper != null) {
    
    
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Come in prepare(false);

    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 Looper object is created in the sThreadLocal.set method

    private Looper(boolean quitAllowed) {
    
    
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

While creating the Looper object, the MessageQueue object is also created in the constructor.

  1. In the Handler's enqueueMessage method, assign the Handler to the msg.target object through msg.target=this
    Insert image description here

And we are calling mHandler.obtainMessage()

    Message msg= mHandler.obtainMessage();

Click to obtainMessage

    /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    @NonNull
    public final Message obtainMessage()
    {
    
    
        return Message.obtain(this);
    }

Come in Message.obtain(this)

    /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
    
    
        Message m = obtain();
        m.target = h;

        return m;
    }

Perform obtain() method

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
    
    
        synchronized (sPoolSync) {
    
    
            if (sPool != null) {
    
    
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

You can see that the message object in Message.obtain() uses a reuse pool to avoid wastage of resources;
FAQ? Please correct me if there are any mistakes!
1. Why does Message need to use the reuse mechanism? Isn’t it good to just create a new one every time?
When I need the main thread and sub-threads to communicate directly, I usually use handlers. I usually send a lot of Message objects. If I create them every time I send them, it will cause a waste of resources. Every time I create a Message object, I need to Allocate memory and perform garbage collection. Using a reuse pool can avoid frequent creation and destruction of Message objects, thereby reducing the cost of memory allocation and garbage collection.
Message: The result of a singly linked list. A linked list is a non-linear, non-sequential physical structure composed of N nodes;
2. Why does message use a singly linked list? Why not use other types: arrayList

  1. Each time arrayList is created, a default size space is created. If the space exceeds the default size, it needs to be expanded, and the memory space of this ArrayList needs to be recalculated and arranged, which may lead to frequent memory copies and garbage collection. .
  2. Since arrayList requires continuous memory blocks to store elements, when the memory block of one arrayList is not enough, we need to create another memory block again. When our memory is not enough, we will perform gc. Operation, and memory fragmentation is prone to occur in the memory. If the memory is stored again, it will easily crash. Singly linked lists only need to allocate new nodes and do not require large-scale data migration. When we store an object, the memory requirements are not high, as long as there is suitable space, it can be stored.

3. Can the child thread have a new handler? How can I create a new Handler in a child thread?
The child thread cannot directly create a Handler object. If you need to call Looper.prepare() on the new Handler of the child thread,
what should you pay attention to when the child thread maintains Looper?
Pay attention to the problem of memory leaks; remember to call the quitSafely method after use.
5. How does MessageQueue ensure thread safety?
Use the synchronized synchronization keyword to limit the enqueue operation.
6. When removing Message, is it removed from the queue or out of the queue?
When removing, only data in the queue can be removed
7.Looper can prepare twice, why?
You cannot prepare it twice because Looper has added constraints during the creation process. If you prepare it twice, an error will be reported.
Insert image description here
8.Handler can switch threads, how to implement it?
Switching threads is actually a type of inter-thread communication. In order to ensure that the main thread is not blocked, we often need to perform some time-consuming tasks in the sub-thread, and after the execution is completed, notify the main thread to respond accordingly. This process is inter-thread communication.
There is an inter-process communication method in Linux called message queue. Simply put, when two processes want to communicate, one process puts the message into the queue, and the other process reads the message from the queue, thereby realizing the communication between the two processes. communication.
Handler is implemented based on this design. In Android's multi-threading, each thread has its own message queue, and the thread can start an infinite loop to continuously read messages from the queue.
When thread B wants to communicate with thread A, it only needs to send a message to A's message queue, and A's event loop will read the message to achieve inter-thread communication.
Insert image description here
9. How are event loop and message queue implemented?
Android's event loop and message queue are implemented through the Looper class.
Looper.prepare() is a static method. It will build a looper and create a MessageQueue as a member variable of Looper. MessageQueue is a queue that stores messages
. When the Looper.loop() method is called, an infinite loop will be opened inside the thread to continuously read messages from the MessageQueue. This is the event loop.
Each Handler is bound to a Looper, and Looper contains MessageQueue
Insert image description here
10. Where is this Looper stored?
Looper is stored in the thread. The Looper.prepare() method will create a Looper. It actually does one more thing, which is to put the Looper into the local variable ThreadLocal of the thread.

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

11.What is ThreadLocal?
ThreadLocal is also called the local variable of the thread. The biggest difference is that a ThreadLocal instance can retrieve different values ​​by calling the get() method in different threads.

fun main() {
    
    
    val threadLocal = ThreadLocal<Int>()
    threadLocal.set(100)

    Thread {
    
    
        threadLocal.set(20)
        println("子线程1 ${
      
      threadLocal.get()}")
    }.start()

    Thread {
    
    
        println("子线程2 ${
      
      threadLocal.get()}")
    }.start()

    println("主线程: ${
      
      threadLocal.get()}")

}

// 运行结果:
子线程1 20
主线程: 100
子线程2 null

ThreadLocal.set can turn an instance into a thread member variable

// ThreadLocal.java
public void set(T value) {
    
    
        // ① 获取当前线程对象
        Thread t = Thread.currentThread();
        // ② 获取线程的成员属性map
        ThreadLocalMap map = getMap(t);
        // ③ 将value放入map中,如果map为空则创建map
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

It is to obtain a map object of the thread based on the current thread, and then put the value into the map to achieve the purpose of turning the value into a member variable of the thread. Multiple
Theadlocal turns multiple variables into member variables of the thread. So threads are managed using ThreadlLocalMap, and the key is threadLocal.

//ThreadLocal.java
public T get() {
    
    
        // ① 获取当前线程对象
        Thread t = Thread.currentThread();
        // ② 获取线程对象的Map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                 // ③ 获取之前存放的value
                return result;
            }
        }
        return setInitialValue();
    }

It is similar to the set method. The difference is that one writes the value to the map and the other reads the value from the map.
12. Why should ThreadLocal be used as the setting and obtaining tool of Looper?
Because Looper is placed in a thread, each thread only needs one event loop and only one Looper. The event loop is an infinite loop, and extra event loops are meaningless. ThreadLocal.set can set Looper as a member variable of the thread.
At the same time, in order to facilitate obtaining Looper in different threads, Android provides a static object Looper.sThreadLocal. In this way, calling sThreadLocal.get inside the thread can obtain the Looper object corresponding to the thread.
13. Looper is an infinite loop. If there are no messages in the message queue, will this infinite loop continue to "idle"?
No, if there is no message to be processed in the event loop but it still loops, it is equivalent to a meaningless waste of CPU resources.
In MessageQueue, there are two native methods, nativePollOnce and nativeWake.
nativePollOnce means to perform a poll to find out whether there is a message that can be processed. If not, block the thread and give up CPU resources.
nativeWake means waking up the thread

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    
    
    ···
    if (needWake) {
    
    
        nativeWake(mPtr);
    }
    ···
}

In the MessageQueue class, the enqueueMessage method is used to enqueue messages. If the thread is blocked at this time, call nativeWake to wake up the thread.

// MessageQueue.java
Message next() {
    
    
    ···
    nativePollOnce(ptr, nextPollTimeoutMillis);
    ···
}

The next() method is used to retrieve the message. Before retrieval, call nativePollOnce() to check whether there is a message that can be processed. If not, the thread will be blocked. Wake up when waiting for a message to be queued.
14. Looper is an infinite loop, why doesn’t it cause ANR?
ANR is an exception thrown when an application cannot respond to an event within a specific time.
A typical example is executing time-consuming tasks in the main thread. When a touch event comes, the main thread is busy processing time-consuming tasks and cannot respond to the touch event within 5 seconds. At this time, ANR will be thrown.
But the Looper infinite loop is the cornerstone of the event loop, which Android itself uses to process events one by one. Under normal circumstances, touch events will be added to this loop and processed. But if the previous event is too time-consuming and the waiting time for the next event is too long beyond a specific time, ANR will occur. Therefore, the infinite loop of Looper is not the cause of ANR.
15. How are messages in the message queue sorted?
This depends on the enqueueMessage method of MessageQueue.
enqueueMessage is the message enqueuing method. When the Handler communicates between threads, it will call sendMessage to send the message to the message queue of the thread receiving the message, and the message queue will call enqueueMessage to enqueue the message.

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    
    
    synchronized (this) {
    
    
        // ① when是消息入队的时间
        msg.when = when;
        // ② mMessages是链表的头指针,p是哨兵指针
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
    
    
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
    
    
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
    
    
                prev = p;
                p = p.next;
                // ③ 遍历链表,比较when找到插入位置
                if (p == null || when < p.when) {
    
    
                    break;
                }
                if (needWake && p.isAsynchronous()) {
    
    
                    needWake = false;
                }
            }
            // ④ 将msg插入到链表中
            msg.next = p;
            prev.next = msg;
        }

        if (needWake) {
    
    
            nativeWake(mPtr);
        }
    }
    return true;
}

Message enqueue is divided into 3 steps:
1. Bind the enqueue time to the when attribute
2. Traverse the linked list and find the insertion position by comparing when
3. Insert msg into the linked list
16. Suppose I have a message and want to It is executed first, how to increase its priority?
The easiest thing to think of is to modify the when attribute of Message. But Android provides us with a more scientific and simple way, asynchronous messages and synchronization barriers.
In Android's message mechanism, messages are divided into three types: synchronous messages, asynchronous messages and synchronization barriers. (A synchronization barrier is a special message whose target attribute is null). Usually we call the sendMessage method to send a synchronous message. Asynchronous messages need to be used in conjunction with synchronization barriers to increase the priority of messages.
Synchronization barrier is a special kind of message. When the event loop detects the synchronization barrier, the subsequent behavior is no longer like the previous behavior of fetching messages one by one according to the value of when. Instead, it traverses the entire message queue, finds the asynchronous message, takes it out and executes it.
This special message acts like a flag in the message queue. When the event loop detects it, it changes its original behavior and instead looks for asynchronous messages. It looks like a barrier blocking synchronization messages. So it is called synchronization barrier vividly.

//MessageQueue.java
Message next() {
    
    
    ···
    // ① target为null表明是同步屏障
    if (msg != null && msg.target == null) {
    
    
        // ② 取出异步消息
       do {
    
    
        	prevMsg = msg;
        	msg = msg.next;
       } while (msg != null && !msg.isAsynchronous());
    }
    ···
}

17. What will happen if I insert a synchronization barrier and do not remove it?
Synchronization barriers are used to "block" synchronous messages and process asynchronous messages. If the synchronization barrier is not removed, asynchronous messages in the message queue will be fetched and processed one by one until the asynchronous messages are fetched. If there are no asynchronous messages in the queue at this time, the thread will block and the synchronous messages in the queue will never be executed. Therefore, the synchronization barrier must be removed in time.
18. What are the application scenarios of synchronization barrier?
The core function of synchronization barrier is to improve the priority of messages and ensure that messages are processed first. In order to avoid lagging, Android is used in View drawing.
19.Why is there a memory leak problem when using Handler? How to solve it?
In the final analysis, memory leaks are caused by the "misalignment" of the life cycle: an object should have been recycled in a short life cycle, but was referenced by an object with a long life cycle, making it impossible to recycle. The memory leak of Handler is actually caused by the internal class holding a reference to the external class. There are two ways to form:
1. Anonymous inner class holds external class reference

class Activity {
    
    
    var a = 10
    fun postRunnable() {
    
    
        Handler(Looper.getMainLooper()).post(object : Runnable {
    
    
            override fun run() {
    
    
                this@Activity.a = 20
            }
        })
    }
}

When Handler sends a message, the message.target property is the handler itself. The message is sent to the message queue and held by the thread. The thread is an object with an extremely "long" life cycle, causing the activity to not be recycled in time, causing memory leaks.
The solution is to remove the runnable in time when the activity is destroyed

2. Non-static inner classes hold references to external classes

//非静态内部类
protected class AppHandler extends Handler {
    
    
    @Override
    public void handleMessage(Message msg) {
    
    
        switch (msg.what) {
    
    

        }
    }
}

The solution is to use static inner classes and change external references to weak references

private static class AppHandler extends Handler {
    
    
    //弱引用,在垃圾回收时,被回收
    WeakReference<Activity> activity;

    AppHandler(Activity activity){
    
    
        this.activity = new WeakReference<Activity>(activity);
    }

    public void handleMessage(Message message){
    
    
        switch (message.what){
    
    
        }
    }
}

20.What are HandlerThread and IdleHandler used for?
As the name suggests, HandlerThread is a combination of Handler+Thread, which is essentially a Thread.
We know that sub-threads require us to manually start the event loop through Looper.prepare() and Looper.loop(). HandlerThread actually does this for us. It is a thread that implements an event loop . We can do some IO time-consuming operations in this thread.
Although IdleHandler is called Handler, it is actually a special "message" like a synchronization barrier. Different from Message, it is an interface

public static interface IdleHandler{
    
    
    boolean queueIdle();
}

Idle means idle. Different from the synchronization barrier, the synchronization barrier improves the priority of asynchronous messages so that they can be executed first. The IdleHandler is executed when the event loop is idle.
"Idle" here mainly refers to two situations:

(1) The message queue is empty

(2) The message queue is not empty but all are delayed messages, that is, msg.when > now

Using this feature, we can execute some unimportant initialization operations in IdleHandler to speed up app startup.
21. Can you please talk about the handler mechanism of Android?
The handler mechanism is the basis of the Android operating system. It is designed using the producer and consumer model. The producer is the handler. The handler will generate a message and deliver it to the thread-shared messageQueue ordered linked list. The thread-shared Looper will then take out the message for consumption and dispatch the message to the corresponding handler for processing.
22. Handler source code analysis.
I divided the Handler source code into three main lines. The first main line is the process of joining the queue. First, in the source code are handler.sendMessage(), sendMessageDelayed, sendMessageAtTIme(), equeueMessage(), and then in the MessageQueue. equeueMessage method.
That is to say, we send the message message in the handler to enqueue the MessageQueue.
If there is no data during the process of joining the queue, put the msg directly into the head. If there is, you need to traverse the data in the queue and perform the insertion operation. The second main line is the process of dequeue consumption. ActivityThread will be called when the app starts
. The main() method of the class, then calls looper.loop() inside, then queue.next(), finds the msg object through next(), then msg binds the target, which is the handler, and then calls the dispatchMessage callback handlerMessage() Method
The third main line: the relationship between the four classes of Handler, MessageQueue, Message and Looper
First create a message object by calling handler.obstainMessage().
In the enqueueMessage method in MessageQueue, Handler puts the created object into MessageQueue.
Looper is created using the main() method of ActivityThread. MessageQueue will be created at the same time when creating Looper.

Guess you like

Origin blog.csdn.net/ChenYiRan123456/article/details/131513298