Analysis of interview questions related to Handler in Android

brief

Regarding the message mechanism composed of Handler, Message, MessageQueue, and Looper in the Android system, since they are the basis for developing Android applications, they will definitely be investigated during the interview process. And every problem must be investigated for its principle. This article will analyze the problems that have been encountered.

If you don't know the principle of the message mechanism composed of Handler, Message, MessageQueue, and Looper, you can check the article on how to gracefully detect time-consuming methods in the main thread .

Handler issues related

The responsibility of Handler in the Android system message mechanism is: responsible for sending and receiving messages .

The difference between the method sendMessage and post in Handler?

Here is a look at the source code of the method sendMessage:

   public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

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

     public boolean sendMessageAtTime(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);
    }
    
      private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Look at the source code of the method 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;
    }

From the source code of the two methods, it can be seen that:

  • sendMessage → sendMessageDelayed → sendMessageAtTime → enqueueMessage → …
  • post → sendMessageDelayed → sendMessageAtTime → enqueueMessage → …

Whether it is the method sendMessage or the method post, they only get different message entity objects Message: sendMessage is passed in from the outside, generally the outside will get a Message through the obtainMessage of the Handler, and then give the what, arg1, obj variables of the Message and other assignments; while post creates a Message internally and assigns a value to the callback variable of the Message (the parameter Runnable of the post method).

In addition, it is precisely because the Message objects are different that the logic of the Handler when receiving and processing messages will be different:

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }

In the method dispatchMessage, it can be seen from the code logic that a message is sent through the method sendMessage and post. When receiving and processing the message, it will be given priority to the one sent through the method post (or sent through the callback assignment of the Message), if not , and then hand it over to the mCallback object (passed in when creating the Handler object), if not, then hand it over to the method sendMessage to send.

So the priority of receiving and processing messages is:

  1. First hand over to the one sent through the post method;
  2. Then hand it over to the callback interface Handler.Callback passed in when creating the Handler object;
  3. Finally, it is sent by the sendMessage method.

Answer

For this interview question, the answer is: send a message through the methods sendMessage and post, and assign different values ​​to the message entity object Message when sending. sendMessage obtains a Message externally, and post creates a Message internally and gives its callback Attribute assignment; when receiving and processing, the message sent through the method post will be processed first, and the message sent through the method sendMessage will be processed next .

How does Handler send a delayed message?

Sending a delayed message is achieved through the methods andsendMessageDelayed provided by Handler . postDelayedTheir method call chain is as follows:

  • sendMessageDelayed :sendMessageDelayed → sendMessageAtTime → enqueueMessage → …
  • postDelayed:postDelayed → sendMessageDelayed → sendMessageAtTime → enqueueMessage → …

These two method calls are essentially the same, so just look at the source code of the method sendMessageDelayed:

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

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //省略
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //省略
        return queue.enqueueMessage(msg, uptimeMillis);
    }

The delay time passed in from the outside delayMillis, after passing through the sendMessageDelayed method, will pass in SystemClock.uptimeMillis() + delayMillisthe parameter when of the enqueueMessage method of MessageQueue:

    boolean enqueueMessage(Message msg, long when) {
        //省略...
        synchronized (this) {
            //省略.. 
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //如果当前消息队列为空,或消息执行时间为0,或消息执行时间小于当前链表第一个元素的执行时间,就将当前消息放入链表的首部
            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 {
                //省略.. 
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 根据链表中每个消息的执行时间,也就是Message 的 when 变量的值对链表进行排序
                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;
    }

In this method: , is to assign the value msg.when = when;outside , about , it is the execution time of Message. The logic behind the assignment is: if the current message queue is empty, or the message execution time is 0, or the message execution time is less than the execution time of the first element of the current linked list, put the current message at the head of the linked list, and then exit this method ; Otherwise, sort the linked list according to the execution time of each message in the linked list, that is, the value of the when variable of Message, and then exit this method.SystemClock.uptimeMillis() + delayMillismsg.whenmsg.when

Answer

For this interview question, the answer is: to send a delayed message, the value of the current time + delay time will be assigned to the when variable of the Message, and the when variable is the execution time of the message. In addition, when inserting messages into MessageQueue, the Messages in the linked list will be sorted according to the execution time of each message in the linked list, that is, the value of the when variable of Message .

Use Handler to first send a message with a delay of 10s, then send a message with a delay of 5s, then let the main thread sleep for 5s, and finally the sequence of message execution?

From the question of how Handler sends a delayed message , we know the final execution time of the message, which is msg.whenthe value of , and msg.whenthe value of is: current time + delay time. When the Handler sends a delayed message, the MessageQueue will msg.whensort the Messages according to the execution time ( ) of each message in the linked list.

Answer

For this interview question, the answer is: send a message with a delay of 10s and 5s. Regardless of the order, when MessageQueue stores messages, it will always put the message with a delay of 5s in front of the message with a delay of 10s. Therefore, after the main thread sleeps for 5s, the message delayed by 5s will be executed first, and then the message delayed by 10s will be executed.

MessageQueue issues related

The responsibility of Message in the Android system message mechanism is to be responsible for encapsulating the message entity, that is, the parameters passed .
The responsibility of MessageQueue in the Android system message mechanism is to manage Message in the form of a linked list .

How to put a message at the head of MessageQueue?

From the above analysis of the enqueueMessage method of MessageQueue, we know that if the current message queue is empty, or the execution time of the message is 0, or the execution time of the message is less than the execution time of the first element of the current linked list, the current message will be put into the head of the linked list . For these three cases, we will analyze step by step next.

queue is empty

MessageQueue provides isIdlea method to determine whether the current queue is empty:

Looper.myQueue().isIdle()

If it is empty, just send a message directly.

message execution time is 0

Handler uses the method send or post to send a message, and the method will be called at the end sendMessageAtTime, and the parameter of this method uptimeMillisis passed to the enqueueMessage method of MessageQueue, and will be msg.whenassigned, so just assign uptimeMillisa value of 0:

handler.sendMessageAtTime(handler.obtainMessage(),0);

In fact, Handler also provides methods: sendMessageAtFrontOfQueue:

    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        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, 0); // uptimeMillis=0
    }

    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

Also uptimeMillisassign a value : uptimeMillis=0.

The execution time of the message is less than the execution time of the first element of the current linked list

Same as message execution time is 0 :

handler.sendMessageAtTime(handler.obtainMessage(),Long.MIN_VALUE);

However, there is no need to do this, as long as the execution time of the above message is 0 .

Answer

For this interview question, the answer is: in the enqueueMessage method of MessageQueue, there will be such a judgment: if the current message queue is empty, or the message execution time is 0, or the message execution time is less than the execution time of the first element of the current linked list , put the current message into the head of the linked list. Therefore, when the message queue is empty, use Looper.myQueue().isIdle()the method to judge, if it is judged to be empty, just send a message directly; when the message queue is not empty, just call it, in sendMessageAtFrontOfQueuefact, msg.whenassign the message execution time to 0.

How to make Handler send a message and let it execute first?

First, you need to understand the classification of Message:

  1. Synchronous messages, that is, ordinary messages
  2. asynchronous message

In the Message class, the field flagmarks the current message is a synchronous message or an asynchronous message:

    /** If set message is asynchronous */
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    @UnsupportedAppUsage
    /*package*/ int flags;

    /**
     * Returns true if the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     *
     * @return True if the message is asynchronous.
     *
     * @see #setAsynchronous(boolean)
     */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

As can be seen from the above, as long as msg.setAsynchronous(true);the message is an asynchronous message, the default is a synchronous message.

Usually, setting whether the Message is synchronous or asynchronous is set through the Handler.

Let's look at the source code in the class Handler:

// 该变量标记 Message 是同步的还是异步的,默认为 false
 final boolean mAsynchronous;

    public Handler() {
        this(null, false);
    }

    public Handler(@Nullable Callback callback) {
        this(callback, false);
    }

    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }

    public Handler(@NonNull Looper looper, @Nullable Callback callback) {
        this(looper, callback, false);
    }

    /**
     * @hide
     */
    @UnsupportedAppUsage
    public Handler(boolean async) {
        this(null, async);
    }

    /**
     * @hide 隐藏方法,外部不可调用
     */
    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    /**
     * @hide 隐藏方法,外部不可调用
     */
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @NonNull
    public static Handler createAsync(@NonNull Looper looper) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        return new Handler(looper, null, true); //mAsynchronous = true
    }

    @NonNull
    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true); //mAsynchronous = true
    }

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

        if (mAsynchronous) { //mAsynchronous = true 时,  Message 会设置为异步的,否则都为同步的。
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

mAsynchronousThe variable in Handler determines whether the Message is synchronous or asynchronous. Usually, the Handler object created by using the constructor will process the Message synchronously, that is, the Handler object createdmAsynchronous = false by using the method will process the Message asynchronously, that is . Finally, set whether the Message is synchronous or asynchronous in .createAsyncmAsynchronous = trueenqueueMessagemsg.setAsynchronous

Although set up according to the above, MessageQueue will not do special processing for synchronous or asynchronous messages. The messages sent by default are all synchronous messages, and asynchronous messages will only highlight their role when a synchronous barrier is set on the MessageQueue.

synchronization barrier

The role of the synchronization barrier: to allow asynchronous messages to be executed first.

MessageQueue provides methods postSyncBarrierto set synchronization barriers:

    /**
     * @hide 隐藏方法,外部不可调用
     */
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

It can be seen from the code logic that setting a synchronization barrier just adds msg.target = nulla . Usually, the message sent by Handler msg.targetcannot be empty, even if it is empty, enqueueMessagethe method will check and throw an exception.

After setting the synchronization barrier, the processing of asynchronous messages in the MessageQueue nextmethod :

    @UnsupportedAppUsage
    Message next() {
        // 省略...
        for (;;) {
           // 省略...
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //msg.target=null,循环找到找到第一个异步消息才终止
                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 {
                    // 省略...
                }
                // 省略...
            }
            // 省略...
        }
    }

At that msg.target = nulltime, it will first look for an asynchronous message from the message queue (that is, msg.isAsynchronous()return true). If it finds an asynchronous message, it will give priority to processing this message this time. Therefore, the asynchronous message will only work when the synchronization barrier is set, and it will be executed prior to the synchronous message (ordinary message) .

Answer

For this interview question, so the answer is:

  1. Use the Handler method createAsyncto create a Handler that sends an asynchronous message; or msg.setAsynchronous(true);set the message to be an asynchronous message through ;
  2. Use mHandler.getLooper().getQueue().postSyncBarrier();to set synchronization barriers;
  3. Send asynchronous messages so that MessageQueue will prioritize such messages.

What are the similarities and differences between the messages sent by the Handler during the drawing process of the View and the messages sent by the normal Handler?

Here we first review the drawing process of View, starting from ViewRootImpl, in order: scheduleTraversalsdoTraversalperformTraversals()

  1. performMeasure → …
  2. performLayout→ …
  3. performDraw→ …

Method scheduleTraversalssource code :

   int mTraversalBarrier;
   
    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

As can be seen, first the synchronization barrier is set: mHandler.getLooper().getQueue().postSyncBarrier().

Secondly, about postCallbackthe method : postCallbackpostCallbackDelayedpostCallbackDelayedInternal()

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

Here, the key point is that the Messages sent by the Handler are all set as asynchronous messages: msg.setAsynchronous(true);. This also ensures that messages sent related to View drawing will be executed first .

Looper issues related

The responsibility of Looper in the Android system message mechanism is to take out the Message from the MessageQueue and hand it over to the Handler for processing .

Can Handler be used to send and process messages in child threads?

The class HandlerThread provided by the Android system is the Handler created in the child thread:

public class HandlerThread extends Thread {
    @Override
    public void run() {
        mTid = Process.myTid();
        // 创建当前线程的 Looper
        Looper.prepare();
        synchronized (this) {
           // 获取当前线程的 Looper
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // 开始消息循环
        Looper.loop();
        mTid = -1;
    }
}

After calling Thread's start method, its run method will be executed. What is done in the run method above is:

  1. Looper.prepare(): Create the Looper of the current thread;
  2. mLooper = Looper.myLooper(): Get the Looper of the current thread;
  3. Looper.loop(): Start a message loop.

The key point here is the method of class Looper Looper.prepare():

       @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

Store the Looper object of the current thread through the ThreadLocal object.

Then, the method Looper.myLooper():

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Get the Looper object of the current thread through the ThreadLocal object.

So the way to use HanderThread is:

        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        Handler handler = new Handler(handlerThread.getLooper());
        handler.sendMessage(Message.obtain(handler));

In this way, the Handler can be used to send and process messages in the child thread.

Answer

For this interview question, the answer is: to use the Handler to send and process messages in the sub-thread, you need to create a corresponding Looper object in the sub-thread, and associate the Looper object with the Handler object. The class HandlerThread of the Android system provides such a function.

Thinking: The principle of ThreadLocal?

How to judge that the current thread is the main thread?

To answer this question, you first need to know some brief information about ThreadLocal:

  1. Data sharing within the thread range, storing data for a certain thread;
  2. Each thread Thread has one ThreadLocalMap(Thread#threadlocals). When calling ThreadLocal#setthe method, if threadlocalsthe variable is empty, a ThreadLocalMap object will be created to assign it, and if it is not empty, the set method of ThreadLocalMap will be called directly;
  3. ThreadLocalMap stores data in the form of an array. The initial size of the array is 16. Creating a ThreadLocalMap object requires a ThreadLocal object and the stored data object. The ThreadLocal object is the current object, and the array subscript is calculated according to the hashCode of the ThreadLocal object;
  4. When the current size is greater than or equal to (threshold size-threshold size/4), it will expand to double the original size.

Look at how the Looper of the main thread is created.

The entry point of the application is the method main of ActivityThread:

    public static void main(String[] args) {
        //省略...
        // 创建主线程使用的  Looper
        Looper.prepareMainLooper();
        //省略...
        // 开始消息循环
        Looper.loop();
        //省略...
    }

About methods in Looper: prepareMainLooper():

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

In fact, it is the same as the Looper object created above for the current thread, except that the current thread is the main thread, so the created Looper object is only responsible for taking out messages from the MessageQueue of the main thread and handing them over to the Handler of the main thread for processing.

sMainLooperThe Looper class here will cache the Looper object used by the main thread, that is, the object used . So judge whether the current thread is on the main line:

    public static boolean isMainThread(){
        return Looper.getMainLooper().equals(Looper.myLooper());
    }

Answer

For this interview question, the answer is: when the application starts, a Looper object used by the main thread will be created and cached. The creation of the Looper object uses the class ThreadLocal, which is data sharing within the thread range and stores data for a certain thread. So you can use the value Looper.getMainLooper()of Looper.myLooper()to compare with the value of , if they are equal, the current thread is the main thread, otherwise it is not the main thread.

The method loop() in Looper is an infinite loop, why doesn't it cause the application to freeze?

About the method in the class Looper loop():

    public static void loop() {
        final Looper me = myLooper();
       //省略...
        final MessageQueue queue = me.mQueue;
        //省略...
        for (;;) {
            Message msg = queue.next(); // might block
            //省略...
            try {
                msg.target.dispatchMessage(msg);
                //省略...
            } catch (Exception exception) {
               //省略...
            } finally {
                //省略...
            }
            //省略...
        }
    }

What the method loop() does is mainly to continuously take out messages from MessageQueue and hand them over to Handler for processing.

About the method of MessageQueue next():

    private long mPtr; 
    
    @UnsupportedAppUsage
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        //省略...
        int nextPollTimeoutMillis = 0;
        for (;;) {
             //省略...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                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) {
                        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;
         
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                 //省略...
                if (mQuitting) {
                    dispose();
                    return null;
                }
                 //省略...
            }
             //省略...
             // 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;
        }
    }

It can be seen that the method next() is also an infinite loop, but the key here is the natvie method:nativePollOnce(ptr, nextPollTimeoutMillis);

What this method does is:

nativePollOnce is a blocking operation, where nextPollTimeoutMillis represents the waiting time before the next message arrives; when nextPollTimeoutMillis = -1, it means that there is no message in the message queue and it will wait forever.
When idle, the method in IdleHandler is often executed. When nativePollOnce() returns, next() extracts a message from mMessages.

Regarding the meaning of blocking:

The main thread will release CPU resources and go to sleep until the next message arrives or a transaction occurs, and wakes up the main thread to work by writing data to the write end of the pipe. The epoll mechanism used here is an IO multiplexing mechanism that can monitor multiple descriptors at the same time. When a descriptor is ready (ready or write ready), it immediately notifies the corresponding program to read or write, essentially synchronous I/O, that is, reading and writing is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources.

For nativePollOncemore analysis of the method, you can read the detailed analysis of gityuan : Android message mechanism 1-Handler (Java layer) .

Answer

For this interview question, the answer is: First, the method loop() of the Looper class is mainly to continuously fetch messages from the MessageQueue and hand them over to the Handler for processing. When the next method of MessageQueue is called, the native method will be called nativePollOnce, and the execution of this method will make a judgment: if there is no message, the CPU will go to sleep and wait until there is a new message to wake up the CPU, that is, when the method enqueueMessage of MessageQueue goes to When a message is added to the message queue, call the native method nativeWaketo wake up the CPU and continue execution. Therefore, although the method loop() is an infinite loop, it will not cause the application to freeze, because there is no message to let the CPU go to sleep.

Summarize

The above are some questions about the Android system message mechanism that are often encountered in interviews: Handler, Message, MessageQueue, and Looper. Only when you understand the principle can you know the key points of answering the questions, which is where the interviewer's inspection point lies. Of course, this is also more helpful to your understanding of the basics of Android.

Guess you like

Origin blog.csdn.net/wangjiang_qianmo/article/details/110739505