【問答集】主線程任務(wù)隊(duì)列耗時(shí)如何分析?

【問答集】主線程任務(wù)隊(duì)列耗時(shí)如何分析?

可以通過Looper.loop()方法的log進(jìn)行查看。

在Android中所有的事件(Activity、Fragment生命周期方法,點(diǎn)擊事件等)最終都會被轉(zhuǎn)換(封裝)成Message,再交給主線程去執(zhí)行。

任務(wù)隊(duì)列耗時(shí)可以理解為處理每個(gè)msg的時(shí)間。

Looper.loop()方法分析:(Android api 30)

    public static void loop() {
        final Looper me = myLooper();

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

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                
                // Q1、這個(gè)地方為什么可以return呢?第一個(gè)猜測是系統(tǒng)對主線程的message進(jìn)行了非null驗(yàn)證,保證了到這里的msg不會為null,不然主線程的loop方法都退出了。第二個(gè)猜測是系統(tǒng)有什么方法可以把loop方法再調(diào)用一下,保證循環(huán)可用。這個(gè)需要再做研究。
                return;
            }

            // here1:關(guān)鍵就在這里,開始的時(shí)候,會去打印一條日志
            
            // 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);
            }
                // here2:msg分發(fā)給對應(yīng)的target(也就是handler)進(jìn)行處理
                msg.target.dispatchMessage(msg);

            // here3:關(guān)鍵就在這里,結(jié)束的時(shí)候,也會去打印一條日志,通過計(jì)算兩條日志的時(shí)間差,就可以得到每條消息的處理時(shí)間
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
    }

分析:

1、主要的方法就是靠注釋:here1-here3

2、分析源碼的時(shí)候新發(fā)現(xiàn)了一個(gè)問題(注釋:Q1),要去思考一下。

驗(yàn)證1——生命周期耗時(shí)

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private long lastTime;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        long startTime = System.currentTimeMillis();
        Log.i(TAG, "onCreate:" + startTime);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mockSpendTimeOperate();
        
        Looper.myLooper().setMessageLogging(new Printer() {
            @Override
            public void println(String x) {
                Log.i(TAG, x);
                if (lastTime != 0L) {
                    long spendTime = System.currentTimeMillis() - lastTime;
                    lastTime = 0L;
                    Log.i(TAG, "println: " + spendTime);
                } else {
                    lastTime = System.currentTimeMillis();
                }
            }
        });
        Log.i(TAG, "onCreate:" + (System.currentTimeMillis() - startTime));
    }

    private void mockSpendTimeOperate() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

這里我預(yù)期的是:能夠把onCreate等生命周期方法的執(zhí)行時(shí)間給打印出來,但是結(jié)果和預(yù)期并不符合。

結(jié)果:
image-20210802204749204.png

失敗了。。。

用這種方法是統(tǒng)計(jì)不到的 Activity 的生命周期方法。

Q2:為什么會統(tǒng)計(jì)不到呢?

是我我這種方法不對?還是原理就沒理解好呢?

看來還是要對Activity的生命周期方法調(diào)用過程再做一個(gè)分析。

Q3:那統(tǒng)計(jì)耗時(shí)的方法有哪些呢?

1、手動加日志,再計(jì)算

2、字節(jié)碼插入日志代碼進(jìn)行統(tǒng)計(jì)(忘了叫啥了,這個(gè)得再學(xué)一下)

3、還有啥呢?

那既然不能統(tǒng)計(jì)生命周期的方法,就再看看它能不能統(tǒng)計(jì)到我們自己發(fā)的msg耗時(shí):

驗(yàn)證2:統(tǒng)計(jì)我們自己寫的代碼、發(fā)的msg

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private long lastTime;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        long startTime = System.currentTimeMillis();
        Log.i(TAG, "onCreate:" + startTime);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // here:就用一個(gè)點(diǎn)擊事件來測一下
        Button btnTestMsg = findViewById(R.id.btn_testMsg);
        btnTestMsg.setOnClickListener(v -> mockSpendTimeOperate());

        mockSpendTimeOperate();

        // 首先拿到 主線程的 looper,把Printer對象設(shè)置進(jìn)去。
        // 這里的目的就是統(tǒng)計(jì)耗時(shí)
        Looper.myLooper().setMessageLogging(new Printer() {
            @Override
            public void println(String x) {
                Log.i(TAG, x);
                if (lastTime != 0L) {
                    long spendTime = System.currentTimeMillis() - lastTime;
                    lastTime = 0L;
                    Log.i(TAG, "msg spendTime: " + spendTime);
                } else {
                    lastTime = System.currentTimeMillis();
                }
            }
        });
        Log.i(TAG, "onCreate:" + (System.currentTimeMillis() - startTime));
    }

    private void mockSpendTimeOperate() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

這里用點(diǎn)擊事件來測一下,能不能打印出時(shí)間來呢?

結(jié)果:
image-20210802215027114.png

這次和預(yù)期的完全符合了

然后再來看看系統(tǒng)給的日志:

Dispatching to Handler (android.view.ViewRootImplViewRootHandler) {770eec3} android.view.ViewPerformClick@9ede30f: 0

image-20210802215316683.png

因此Button點(diǎn)擊事件的msg信息為:

target:ViewRootImpl$ViewRootHandler,處理msg的handler。也是發(fā)送這條msg的Handler。

callback:android.view.View$PerformClick,msg的callback實(shí)際就是一個(gè)Runnable,就是一坨可執(zhí)行的代碼。

Message.java

/*package*/ Runnable callback;

callback和handlerMessage方法的關(guān)系如下:

Handler.java

public void dispatchMessage(@NonNull 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();
    }

msg.what:找到具體的事件,一般都是通過Switch去匹配。

因此,Button的點(diǎn)擊就執(zhí)行了callback的run方法,如下:
private final class PerformClick implements Runnable {
    @Override
    public void run() {
        recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
        performClickInternal();
    }
}

    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            
            // here1:執(zhí)行了我們的onClick事件
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

看注釋here1處,最終執(zhí)行了我們的onClick方法。

那還有一個(gè)問題?msg是在哪里發(fā)送的呢?

首先我們知道發(fā)送msg的Handler是android.view.ViewRootImpl$ViewRootHandler,但一個(gè)Handler是可以在多處使用的,我們試試這樣找:

在點(diǎn)擊事件里把調(diào)用棧打印出來:

        Button btnTestMsg = findViewById(R.id.btn_testMsg);
        btnTestMsg.setOnClickListener(v -> {
            mockSpendTimeOperate();
            Exception dumpStack = new Exception("dumpStack");
            dumpStack.printStackTrace();
        });
image-20210802222659171.png
image-20210802223357490.png

很可惜,毫無收獲,思路不對。

那我們再回過去看看

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
        performClickInternal();
    }
}

前面說了,最終會調(diào)用這坨代碼,那看看它在哪調(diào)用的就好了(即這個(gè)callback是在哪設(shè)置給msg的)。

好在幸運(yùn)的是,這個(gè) PerformClick 是private的。

直接搜素 new PerformClick()找到唯一使用地方:

image-20210802223357490.png

是在 onTouchEvent()中的。看到這肯定就知道是走的事件分發(fā)了,一切都串聯(lián)了起來。正著分析,反著分析,都是一樣的。

也就是從 Activity 傳到 view 的那套東西(事件方法:Activity-Window-DecorView-ViewGroup)了。

但到這里還是沒有回答:msg是從哪里發(fā)出來的呢???

Q4:問題又來了,點(diǎn)擊事件的msg是從哪里發(fā)出來的呢?

還沒搞懂,再朝著這個(gè)方向?qū)W一下。

思考:這個(gè)就沿著找——調(diào)用 Activity#dispatchTouchEvent()方法的地方。然后再往上找。

Q5:事件又是如何傳遞到Activity的呢?

這個(gè)代碼我還沒看,又有新任務(wù)了。

然后其實(shí)看Looper.loop()方法時(shí),還發(fā)現(xiàn)了個(gè)東西:

Looper.java

    private static Observer sObserver;

    public static void setObserver(@Nullable Observer observer) {
        sObserver = observer;
    }

    public static void loop() {
        final Looper me = myLooper();

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

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

            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                // here1:
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    // here2:
                    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);
                }
            }
           
        }
    }

我們可以設(shè)一個(gè)observe對象進(jìn)去,那么在dispatch msg 的時(shí)候就會回調(diào)我們。

observe接口提供了3個(gè)方法,根據(jù)上面的代碼,我們可以在注釋here1處,返回一個(gè)當(dāng)前時(shí)間,再在結(jié)束的時(shí)候here2處,算一下耗時(shí),也可以計(jì)算時(shí)間,算是一個(gè)簡單的運(yùn)用。

public interface Observer {
    /**
     * Called right before a message is dispatched.
     *
     * <p> The token type is not specified to allow the implementation to specify its own type.
     *
     * @return a token used for collecting telemetry when dispatching a single message.
     *         The token token must be passed back exactly once to either
     *         {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
     *         and must not be reused again.
     *
     */
    Object messageDispatchStarting();

    /**
     * Called when a message was processed by a Handler.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched.
     */
    void messageDispatched(Object token, Message msg);

    /**
     * Called when an exception was thrown while processing a message.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched and caused an exception.
     * @param exception The exception that was thrown.
     */
    void dispatchingThrewException(Object token, Message msg, Exception exception);
}

知識點(diǎn):

Handler機(jī)制、Looper#loop()、點(diǎn)擊事件分發(fā)

待解決問題匯總:

Q1、下面注釋here1處為什么可以return呢?第一個(gè)猜測是系統(tǒng)對主線程的message進(jìn)行了非null驗(yàn)證,保證了到這里的msg不會為null,不然主線程的loop方法都退出了。第二個(gè)猜測是系統(tǒng)有什么方法可以把loop方法再調(diào)用一下,保證循環(huán)可用。這個(gè)需要再做研究。

public static void loop() {
    final Looper me = myLooper();

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

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

Q2:為什么會統(tǒng)計(jì)不到呢?

是我我這種方法不對?還是原理就沒理解好呢?

看來還是要對Activity的生命周期方法調(diào)用過程再做一個(gè)分析。

Q3:那統(tǒng)計(jì)耗時(shí)的方法有哪些呢?

1、手動加日志,再計(jì)算

2、字節(jié)碼插入日志代碼進(jìn)行統(tǒng)計(jì)(忘了叫啥了,這個(gè)得再學(xué)一下)

3、還有啥呢?

Q4:問題又來了,點(diǎn)擊事件的msg是從哪里發(fā)出來的呢?

還沒搞懂,再朝著這個(gè)方向?qū)W一下。

思考:這個(gè)就沿著找——調(diào)用 Activity#dispatchTouchEvent()方法的地方。然后再往上找。

Q5:事件又是如何傳遞到Activity的呢?

這個(gè)代碼我還沒看,又有新任務(wù)了。

參考:

1、阿里盒馬Android面試題:主線程任務(wù)隊(duì)列耗時(shí)情況

他講的也沒有很清楚,比如要如何實(shí)踐也沒講,算是給了個(gè)方向。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容