blockCanary

对于android里面的性能优化,最主要的问题就是UI线程的阻塞导致的,对于如何准确的计算UI的绘制所耗费的时间,是非常有必要的,blockCanary是基于这个需求出现的,同样的,也是基于LeakCanary,和LeakCanary有着显示页面和堆栈信息。

使用

首先在gradle引入

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

然后Application里面进行初始化和start

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

原理:

其中BlockCanaryContext表示的就是我们监测的某些参数,包括卡顿的阈值、输出文件的路径等等

//默认卡顿阈值为1000mspublic 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只是创建出BlockCanary实例。主要是start方法的操作。

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

其实就是给主线程的Looper设置一个monitor。
我们可以先看看主线程的looper实现。

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循环的代码中,msg.target.dispatchMessage就是我们UI线程收到每一个消息需要执行的操作,都在其内部执行。
系统也在其执行的前后都会执行logging类的print的方法,这个方法是我们可以自定义的。所以只要我们在运行的前后都添加一个时间戳,用运行后的时间减去运行前的时间,一旦这个时间超过了我们设定的阈值,那么就可以说这个操作卡顿,阻塞了UI线程,最后通过dump出此时的各种信息,来分析各种性能瓶颈。
那么接下来可以看看这个monitor的println方法。

@Override    public void println(String x) {        //如果当前是在调试中,那么直接返回,不做处理        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {            return;        }        if (!mPrintingStarted) {            //执行操作前            mStartTimestamp = System.currentTimeMillis();            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();            mPrintingStarted = true;            startDump();        } else {            //执行操作后            final long endTime = System.currentTimeMillis();            mPrintingStarted = false;            //是否卡顿            if (isBlock(endTime)) {                notifyBlockEvent(endTime);            }            stopDump();        }    }

在ui操作执行前,将会记录当前的时间戳,同时会startDump。
在ui操作执行后,将会计算当前是否卡顿了,如果卡顿了,将会回调到onBlock的onBlock方法。同时将会停止dump。
为什么操作之前就开启了startDump,而操作执行之后就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());    }

其实startDump的时候并没有马上start,而是会postDelay一个runnable,这个runnable就是执行dump的真正的操作,delay的时间就是我们设置的阈值的0.8
也就是,一旦我们的stop在设置的延迟时间之前执行,就不会真正的执行dump操作。

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

只有当stop操作在设置的延迟时间之后执行,才会执行dump操作。

 private Runnable mRunnable = new Runnable() {        @Override        public void run() {            doSample();            if (mShouldSample.get()) {                HandlerThreadFactory.getTimerThreadHandler()                        .postDelayed(mRunnable, mSampleInterval);            }        }    };

这个doSameple分别会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());        }    }

这样,整个blockCanary的执行过程就完毕了。

ANR

当卡顿时间大于一定值之后,将会造成ANR,那么Android系统的ANR是如何检测出来的呢?其实就是通过Watchdog来实现的,这个Watchdog是一个线程。

public class Watchdog extends Thread {}

我们主要看一下其中的run方法的实现。

 @Override    public void run() {        boolean waitedHalf = false;        while (true) {            final ArrayList 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 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);                }

在这个run方法中,会开启一个死循环,主要用于持续检测ANR

while (true) {  }

通过wait,设置每一次休眠时间,

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

当timeout计算完毕之后,会尝试获取当前各个的线程的状态

final int waitState = evaluateCheckerCompletionLocked();
   private int evaluateCheckerCompletionLocked() {        int state = COMPLETED;        for (int i=0; i
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;        }

一旦有线程等待时间超过了最大等待时间,则表示当前已经有ANR。需要dump此时的堆栈信息。

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 pids = new ArrayList();                        pids.add(Process.myPid());                        ActivityManagerService.dumpStackTraces(true, pids, null, null,                            getInterestingNativePids());                        waitedHalf = true;                    }                    continue;                }

此外,还有一个第三方库,ANRWatchDog,也是用来检测Anr的,其实原理更加简单,

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

它会在线程中,利用uiHanlder抛出一个计数器,然后wait指定时间,一旦等待时间到达,那么它会检查计数的值是否发生改变,如果没有发生改变,表示uiHandler的计算方法并没有执行到。也就是出现了Anr,此时需要dump堆栈信息。

更多相关文章

  1. 转载-runOnUIThread解析
  2. Android(安卓)进阶——代码插桩必知必会之初识ASM7字节码操作库
  3. android 有关毫秒转时间的方法,及时间间隔等
  4. Android(安卓)Service 详解(上)
  5. Android(安卓)Design in Action —— 编与集
  6. [android研究联系人之二]联系人Email/Nickname数据操作
  7. android service 例子(电话录音和获取系统当前时间)
  8. RxJava(十)switchIfEmpty 操作符实现 Android(安卓)检查本地缓存逻
  9. Android(安卓)studio 使用 ImageView 加载 gif 文件

随机推荐

  1. Android(安卓)代码画角标 offcutView
  2. Android(安卓)Bluetooth蓝牙名称修改
  3. android 模块化
  4. android获取短信验证码自动填入
  5. android 朗读tts_如何设置Android以大声
  6. Android(CM)源码国内镜像下载
  7. 【Android(安卓)Studio】Android(安卓)St
  8. Android(安卓)Context详解
  9. Android(安卓)UI 中 gravity 与 layout_g
  10. android开关控件使用(一)