Android在效率这一块是一个需要长期优化的点,那么就需要依赖很多的第三方库以及工具,这次就从BlockCanary这个卡顿监测工具开始

1、BlockCanary的使用

引用

dependencies {    compile 'com.github.markzhai:blockcanary-android:1.5.0'}

在应用的application中完成初始化

public class DemoApplication extends Application {     @Override    public void onCreate() {        super.onCreate();        BlockCanary.install(this, new AppContext()).start();    }}  //参数设置public class AppContext extends BlockCanaryContext {    private static final String TAG = "AppContext";     @Override    public String provideQualifier() {        String qualifier = "";        try {            PackageInfo info = DemoApplication.getAppContext().getPackageManager()                    .getPackageInfo(DemoApplication.getAppContext().getPackageName(), 0);            qualifier += info.versionCode + "_" + info.versionName + "_YYB";        } catch (PackageManager.NameNotFoundException e) {            Log.e(TAG, "provideQualifier exception", e);        }        return qualifier;    }     @Override    public int provideBlockThreshold() {        return 500;    }     @Override    public boolean displayNotification() {        return BuildConfig.DEBUG;    }     @Override    public boolean stopWhenDebugging() {        return false;    }}

2、核心原理

我们都知道Android应用程序只有一个主线程ActivityThread,这个主线程会创建一个Looper(Looper.prepare),而Looper又会关联一个MessageQueue,主线程Looper会在应用的生命周期内不断轮询(Looper.loop),从MessageQueue取出Message 更新UI。

那么我们需要监听所有UI的地方就在这个Lopper.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.");        }        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();        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);            }            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;            final long traceTag = me.mTraceTag;            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));            }            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();            final long end;            try {                msg.target.dispatchMessage(msg);                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            if (slowDispatchThresholdMs > 0) {                final long time = end - start;                if (time > slowDispatchThresholdMs) {                    Slog.w(TAG, "Dispatch took " + time + "ms on "                            + Thread.currentThread().getName() + ", h=" +                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);                }            }            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();        }    }

我们可以看到,主要的执行主线程逻辑的代码都在logging.println两句的中间,那么我们是不是只需要重写了logging.println方法,是不是就可以监测到他这一次花了多少事件了?

那么如何来重写呢?重点就在这里了

Looper.getMainLooper().setMessageLogging(new Printer() {            @Override            public void println(String x) {                            }});

知道了这个之后,其实就只剩两件事了

1、写好计算使用了多少事件执行完毕的逻辑

2、写好获取一系列信息的放啊

1这个很好写,我们甚至不需要看BlockCanary的源码最粗暴的方式就是

        Looper.getMainLooper().setMessageLogging(new Printer() {            @Override            public void println(String x) {                if(x.contains(">>>>> Dispatching")){                    //这是开始                }                if(x.contains("<<<<< Finished to")){                    //这是结束                }            }        });

我们来看看BlockCanary是怎么做的

从他的初始化方法开始

1、install

public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {    BlockCanaryContext.init(context, blockCanaryContext);    setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());    return get();} private static void setEnabled(Context context,                               final Class<?> componentClass,                               final boolean enabled) {    final Context appContext = context.getApplicationContext();    executeOnFileIoThread(new Runnable() {        @Override        public void run() {            setEnabledBlocking(appContext, componentClass, enabled);        }    });} private static void setEnabledBlocking(Context appContext,Class<?> componentClass,boolean enabled) {    ComponentName component = new ComponentName(appContext, componentClass);    PackageManager packageManager = appContext.getPackageManager();    int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;    // Blocks on IPC.    packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);}
  • BlockCanaryContext.init会将保存应用的applicationContext和用户设置的配置参数;
  • setEnabled将根据用户的通知栏消息配置开启(displayNotification=true)或关闭(displayNotification=false)DisplayActivity (DisplayActivity是承载通知栏消息的activity)
public static BlockCanary get() {    if (sInstance == null) {        synchronized (BlockCanary.class) {            if (sInstance == null) {                sInstance = new BlockCanary();            }        }    }    return sInstance;}//私有构造函数private BlockCanary() {    BlockCanaryInternals.setContext(BlockCanaryContext.get());    mBlockCanaryCore = BlockCanaryInternals.getInstance();    mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());    if (!BlockCanaryContext.get().displayNotification()) {        return;    }    mBlockCanaryCore.addBlockInterceptor(new DisplayService()); }
  • 单例创建BlockCanary
  • 核心处理类为BlockCanaryInternals
  • 为BlockCanaryInternals添加拦截器(责任链)
  • BlockCanaryContext对BlockInterceptor是空实现,可以忽略;
  • DisplayService只在开启通知栏消息的时候添加,当卡顿发生时将通过DisplayService发起通知栏消息

 

看看核心类BlockCanaryInternals的初始化过程

public BlockCanaryInternals() {     stackSampler = new StackSampler(            Looper.getMainLooper().getThread(),            sContext.provideDumpInterval());     cpuSampler = new CpuSampler(sContext.provideDumpInterval());     setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {         @Override        public void onBlockEvent(long realTimeStart, long realTimeEnd,                                 long threadTimeStart, long threadTimeEnd) {            // Get recent thread-stack entries and cpu usage            ArrayList threadStackEntries = stackSampler                    .getThreadStackEntries(realTimeStart, realTimeEnd);            if (!threadStackEntries.isEmpty()) {                BlockInfo blockInfo = BlockInfo.newInstance()                        .setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)                        .setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))                        .setRecentCpuRate(cpuSampler.getCpuRateInfo())                        .setThreadStackEntries(threadStackEntries)                        .flushString();                LogWriter.save(blockInfo.toString());                 if (mInterceptorChain.size() != 0) {                    for (BlockInterceptor interceptor : mInterceptorChain) {                        interceptor.onBlock(getContext().provideContext(), blockInfo);                    }                }            }        }    }, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));     LogWriter.cleanObsolete();}

这里找到了我们需要的继承了Printer接口的对象LooperMonitor,以及两个采样类StackSampler和CpuSampler,即线程堆栈采样和CPU采样,通过调用setMonitor把创建的LooperMonitor赋值给BlockCanaryInternals的成员变量monitor

先来看看LooperMonitor的print方法

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

嗯,的确是跟我们自己写的差不多的意思,

不过还没有设置到Looper对象中,那么继续看第二个方法

2、start

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

好吧,这个方法直接就看到了设置

 

 

然后剩下的就是收集当前卡顿的点的一些细节信息了,比如当前执行的是什么方法等等,不过这一块目前就先不看了,因为主要的逻辑已经理解了,其他的细节的之后再说

更多相关文章

  1. IOS多线程开发之GCD
  2. 数据更新后让ListView自动滚动到底部
  3. Android(安卓)使用Intent传递对象
  4. Android(安卓)模拟器之 Market 安装
  5. android开发教程之listview使用方法
  6. 生命周期组件 Lifecycle 源码解析(一)
  7. Android(安卓)setTag方法定义key的问题
  8. Android(安卓)8.0 dexopt记录
  9. Android(安卓)4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程

随机推荐

  1. 详解MySQL 外键约束
  2. MySQL数据库使用规范总结
  3. MySQL 常用函数总结
  4. 如何选择合适的MySQL日期时间类型来存储
  5. mysql 时间戳的用法
  6. 详细分析mysql MDL元数据锁
  7. MySQL OOM(内存溢出)的解决思路
  8. MySQL MGR 有哪些优点
  9. mysql数据库中字符集乱码问题原因及解决
  10. MySql如何实现远程登录MySql数据库过程解