blockCanary原理

blockCanary

對(duì)于android里面的性能優(yōu)化,最主要的問(wèn)題就是UI線程的阻塞導(dǎo)致的,對(duì)于如何準(zhǔn)確的計(jì)算UI的繪制所耗費(fèi)的時(shí)間,是非常有必要的,blockCanary是基于這個(gè)需求出現(xiàn)的,同樣的,也是基于LeakCanary,和LeakCanary有著顯示頁(yè)面和堆棧信息。

使用

首先在gradle引入

implementation 'com.github.markzhai:blockcanary-android:1.5.0'

然后Application里面進(jìn)行初始化和start

 BlockCanary.install(this, new BlockCanaryContext()).start();

原理:

其中BlockCanaryContext表示的就是我們監(jiān)測(cè)的某些參數(shù),包括卡頓的閾值、輸出文件的路徑等等

//默認(rèn)卡頓閾值為1000ms
public int provideBlockThreshold() {
        return 1000;
    }
//輸出的log
 public String providePath() {
        return "/blockcanary/";
    }
//支持文件上傳
public void upload(File zippedFile) {
        throw new UnsupportedOperationException();
    }
//可以在卡頓提供自定義操作
@Override
    public void onBlock(Context context, BlockInfo blockInfo) {

    }

其中,init只是創(chuàng)建出BlockCanary實(shí)例。主要是start方法的操作。

   /**
     * Start monitoring.
     */
    public void start() {
        if (!mMonitorStarted) {
            mMonitorStarted = true;
            Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
        }
    }

其實(shí)就是給主線程的Looper設(shè)置一個(gè)monitor。
我們可以先看看主線程的looper實(shí)現(xiàn)。

public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}

在上面的loop循環(huán)的代碼中,msg.target.dispatchMessage就是我們UI線程收到每一個(gè)消息需要執(zhí)行的操作,都在其內(nèi)部執(zhí)行。
系統(tǒng)也在其執(zhí)行的前后都會(huì)執(zhí)行l(wèi)ogging類的print的方法,這個(gè)方法是我們可以自定義的。所以只要我們?cè)谶\(yùn)行的前后都添加一個(gè)時(shí)間戳,用運(yùn)行后的時(shí)間減去運(yùn)行前的時(shí)間,一旦這個(gè)時(shí)間超過(guò)了我們?cè)O(shè)定的閾值,那么就可以說(shuō)這個(gè)操作卡頓,阻塞了UI線程,最后通過(guò)dump出此時(shí)的各種信息,來(lái)分析各種性能瓶頸。
那么接下來(lái)可以看看這個(gè)monitor的println方法。

@Override
    public void println(String x) {
        //如果當(dāng)前是在調(diào)試中,那么直接返回,不做處理
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if (!mPrintingStarted) {
            //執(zhí)行操作前
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            startDump();
        } else {
            //執(zhí)行操作后
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            //是否卡頓
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }

在ui操作執(zhí)行前,將會(huì)記錄當(dāng)前的時(shí)間戳,同時(shí)會(huì)startDump。
在ui操作執(zhí)行后,將會(huì)計(jì)算當(dāng)前是否卡頓了,如果卡頓了,將會(huì)回調(diào)到onBlock的onBlock方法。同時(shí)將會(huì)停止dump。
為什么操作之前就開(kāi)啟了startDump,而操作執(zhí)行之后就stopDump呢?

private void startDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.start();
        }

        if (null != BlockCanaryInternals.getInstance().cpuSampler) {
            BlockCanaryInternals.getInstance().cpuSampler.start();
        }
    }
public void start() {
        if (mShouldSample.get()) {
            return;
        }
        mShouldSample.set(true);

        HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
        HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
                BlockCanaryInternals.getInstance().getSampleDelay());
    }

其實(shí)startDump的時(shí)候并沒(méi)有馬上start,而是會(huì)postDelay一個(gè)runnable,這個(gè)runnable就是執(zhí)行dump的真正的操作,delay的時(shí)間就是我們?cè)O(shè)置的閾值的0.8
也就是,一旦我們的stop在設(shè)置的延遲時(shí)間之前執(zhí)行,就不會(huì)真正的執(zhí)行dump操作。

 public void stop() {
        if (!mShouldSample.get()) {
            return;
        }
        mShouldSample.set(false);
        HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
    }

只有當(dāng)stop操作在設(shè)置的延遲時(shí)間之后執(zhí)行,才會(huì)執(zhí)行dump操作。

 private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            doSample();

            if (mShouldSample.get()) {
                HandlerThreadFactory.getTimerThreadHandler()
                        .postDelayed(mRunnable, mSampleInterval);
            }
        }
    };

這個(gè)doSameple分別會(huì)dump出stack信息和cpu信息。
cpu:

  try {
            cpuReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/stat")), BUFFER_SIZE);
            String cpuRate = cpuReader.readLine();
            if (cpuRate == null) {
                cpuRate = "";
            }

            if (mPid == 0) {
                mPid = android.os.Process.myPid();
            }
            pidReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
            String pidCpuRate = pidReader.readLine();
            if (pidCpuRate == null) {
                pidCpuRate = "";
            }

            parse(cpuRate, pidCpuRate);
        }

stack:

 protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();

        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }

        synchronized (sStackMap) {
            if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
            sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
        }
    }

這樣,整個(gè)blockCanary的執(zhí)行過(guò)程就完畢了。

ANR

當(dāng)卡頓時(shí)間大于一定值之后,將會(huì)造成ANR,那么Android系統(tǒng)的ANR是如何檢測(cè)出來(lái)的呢?其實(shí)就是通過(guò)Watchdog來(lái)實(shí)現(xiàn)的,這個(gè)Watchdog是一個(gè)線程。

public class Watchdog extends Thread {
}

我們主要看一下其中的run方法的實(shí)現(xiàn)。

 @Override
    public void run() {
        boolean waitedHalf = false;
        while (true) {
            final ArrayList<HandlerChecker> blockedCheckers;
            final String subject;
            final boolean allowRestart;
            int debuggerWasConnected = 0;
            synchronized (this) {
                long timeout = CHECK_INTERVAL;
                // Make sure we (re)spin the checkers that have become idle within
                // this wait-and-check interval
                for (int i=0; i<mHandlerCheckers.size(); i++) {
                    HandlerChecker hc = mHandlerCheckers.get(i);
                    hc.scheduleCheckLocked();
                }

                if (debuggerWasConnected > 0) {
                    debuggerWasConnected--;
                }

                // NOTE: We use uptimeMillis() here because we do not want to increment the time we
                // wait while asleep. If the device is asleep then the thing that we are waiting
                // to timeout on is asleep as well and won't have a chance to run, causing a false
                // positive on when to kill things.
                long start = SystemClock.uptimeMillis();
                while (timeout > 0) {
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }
                    try {
                        wait(timeout);
                    } catch (InterruptedException e) {
                        Log.wtf(TAG, e);
                    }
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }
                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
                }

在這個(gè)run方法中,會(huì)開(kāi)啟一個(gè)死循環(huán),主要用于持續(xù)檢測(cè)ANR

while (true) {
 
 }

通過(guò)wait,設(shè)置每一次休眠時(shí)間,

 long start = SystemClock.uptimeMillis();
                while (timeout > 0) {
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }
                    try {
                        wait(timeout);
                    } catch (InterruptedException e) {
                        Log.wtf(TAG, e);
                    }
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }
                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
                }

當(dāng)timeout計(jì)算完畢之后,會(huì)嘗試獲取當(dāng)前各個(gè)的線程的狀態(tài)

final int waitState = evaluateCheckerCompletionLocked();
   private int evaluateCheckerCompletionLocked() {
        int state = COMPLETED;
        for (int i=0; i<mHandlerCheckers.size(); i++) {
            HandlerChecker hc = mHandlerCheckers.get(i);
            state = Math.max(state, hc.getCompletionStateLocked());
        }
        return state;
    }
public int getCompletionStateLocked() {
            if (mCompleted) {
                return COMPLETED;
            } else {
                long latency = SystemClock.uptimeMillis() - mStartTime;
                if (latency < mWaitMax/2) {
                    return WAITING;
                } else if (latency < mWaitMax) {
                    return WAITED_HALF;
                }
            }
            return OVERDUE;
        }

一旦有線程等待時(shí)間超過(guò)了最大等待時(shí)間,則表示當(dāng)前已經(jīng)有ANR。需要dump此時(shí)的堆棧信息。

if (waitState == COMPLETED) {
                    // The monitors have returned; reset
                    waitedHalf = false;
                    continue;
                } else if (waitState == WAITING) {
                    // still waiting but within their configured intervals; back off and recheck
                    continue;
                } else if (waitState == WAITED_HALF) {
                    if (!waitedHalf) {
                        // We've waited half the deadlock-detection interval.  Pull a stack
                        // trace and wait another half.
                        ArrayList<Integer> pids = new ArrayList<Integer>();
                        pids.add(Process.myPid());
                        ActivityManagerService.dumpStackTraces(true, pids, null, null,
                            getInterestingNativePids());
                        waitedHalf = true;
                    }
                    continue;
                }

此外,還有一個(gè)第三方庫(kù),ANRWatchDog,也是用來(lái)檢測(cè)Anr的,其實(shí)原理更加簡(jiǎn)單,

public void run() {
        setName("|ANR-WatchDog|");

        int lastTick;
        int lastIgnored = -1;
        while (!isInterrupted()) {
            lastTick = _tick;
            _uiHandler.post(_ticker);
            try {
                Thread.sleep(_timeoutInterval);
            }
            catch (InterruptedException e) {
                _interruptionListener.onInterrupted(e);
                return ;
            }

            // If the main thread has not handled _ticker, it is blocked. ANR.
            if (_tick == lastTick) {
                if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
                    if (_tick != lastIgnored)
                        Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
                    lastIgnored = _tick;
                    continue ;
                }

                ANRError error;
                if (_namePrefix != null)
                    error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
                else
                    error = ANRError.NewMainOnly();
                _anrListener.onAppNotResponding(error);
                return;
            }
        }
    }

它會(huì)在線程中,利用uiHanlder拋出一個(gè)計(jì)數(shù)器,然后wait指定時(shí)間,一旦等待時(shí)間到達(dá),那么它會(huì)檢查計(jì)數(shù)的值是否發(fā)生改變,如果沒(méi)有發(fā)生改變,表示uiHandler的計(jì)算方法并沒(méi)有執(zhí)行到。也就是出現(xiàn)了Anr,此時(shí)需要dump堆棧信息。

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

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

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