Android 卡顿专题

目录

 

目录

BlockCanary源码分析:

1、入口

 

2、外观类BlockCanary的创建

3、接下来看核心类BlockCanaryInternals的初始化过程

4、start方法

5、卡顿的监控方法

6、采集器分析

6.1 Stack采集器

6.2 CPU采集器

功能

总结



BlockCanary源码分析:

1、入口

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

 

这里this, 代表的是applicationContext, AppContext继承的BlockCanaryContext,install函数中做了两个事情

将复写的AppContext对象复制给BlockCanaryContext,setEnable的作用如下

/**
     * setEnabled将根据用户的通知栏消息配置开启(displayNotification=true)
     * 或关闭(displayNotification=false)DisplayActivity
     * (DisplayActivity是承载通知栏消息的activity)
     * @param context
     * @param componentClass
     * @param enabled
     */
    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);
            }
        });
    }

setEnable是根据用户而配置的AppContext中的

displayNotification返回值,如果可以依据情况复写什么时候显示这个notification;默认的实现方式是debug版本的时候打开;

2、外观类BlockCanary的创建

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

a、外观类是单例模式的实现,setContext是设置用户复写的appContext的类型;

b、blockCanaryCore是核心类,在这个类中维护了一个

private List<BlockInterceptor> mInterceptorChain = new LinkedList<>();

可以用来添加实现了BlockInterceptor接口的拦截器;

mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());这里面的blockCanaryContext实现了BlockInterceptor接口,其中的onBlock方法可以自己根据业务需要实现;代表的含义是当拦截到了卡顿时间,想要做什么样子的操作

c、 mBlockCanaryCore.addBlockInterceptor(new DisplayService());这句是发生卡顿的时候,打开Notification的核心,界面是一个

DisplayActivity;

 

3、接下来看核心类BlockCanaryInternals的初始化过程

public BlockCanaryInternals() {
    // 初始化stack的采集器
    stackSampler = new StackSampler(
            Looper.getMainLooper().getThread(),
            sContext.provideDumpInterval());
    // 初始化cpu的采集器
    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<String> 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();
}

创建了两个采样类StackSampler和CpuSampler,即线程堆栈采样和CPU采样。
随后创建一个LooperMonitor,LooperMonitor实现了android.util.Printer接口。
随后通过调用setMonitor把创建的LooperMonitor赋值给BlockCanaryInternals的成员变量monitor。


    public BlockCanaryInternals() {

        // 在这里采集的是主线程的堆栈信息
        stackSampler = new StackSampler(
                Looper.getMainLooper().getThread(),
                sContext.provideDumpInterval());
        // 这里采集的是cpu的信息
        cpuSampler = new CpuSampler(sContext.provideDumpInterval());

        /**
         * 这里是整个的核心函数,设置成员变量monitor用作BlockCanary中start函数中set的那个            
         * monitor
         */
        setMonitor(new LooperMonitor(new MyBlockListener(),
                getContext().provideBlockThreshold(),
                getContext().stopWhenDebugging()));
        LogWriter.cleanObsolete();
    }

    /**
     * 这里就是回调函数。发生卡顿之后用于记录卡顿的信息
     */
    private class MyBlockListener implements LooperMonitor.BlockListener {

        @Override
        public void onBlockEvent(long realStartTime, long realTimeEnd, long threadTimeStart, long threadTimeEnd) {
            // Get recent thread-stack entries and cpu usage
            ArrayList<String> threadStackEntries = stackSampler
                    .getThreadStackEntries(realStartTime, realTimeEnd);

            if (!threadStackEntries.isEmpty()) {
                BlockInfo blockInfo = BlockInfo.newInstance()
                        //这里打出来的是卡顿的耗时
                        .setMainThreadTimeCost(realStartTime, realTimeEnd, threadTimeStart, threadTimeEnd)
                        .setCpuBusyFlag(cpuSampler.isCpuBusy(realStartTime, realTimeEnd))
                        .setRecentCpuRate(cpuSampler.getCpuRateInfo())
                        .setThreadStackEntries(threadStackEntries)
                        .flushString();
                // 保存文件
                LogWriter.save(blockInfo.toString());

                if (mInterceptorChain.size() != 0) {
                    // 在这里回调实现的拦截器接口的onBlock方法(BlockCanaryContext + DisplayService)
                    for (BlockInterceptor interceptor : mInterceptorChain) {
                        interceptor.onBlock(getContext().provideContext(), blockInfo);
                    }
                }
            }
        }
    }

4、start方法

即调用BlockCanary的start方法

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

将在BlockCanaryInternals中创建的LooperMonitor给主线程Looper的mLogging变量赋值。这样主线程Looper就可以消息分发前后使用LooperMonitor#println输出日志。

 

5、卡顿的监控方法

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

 

/**
 * 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); 
    } 
}
  • 如果消息是通过Handler.post(runnable)方式投递到MQ中的,那么就回调runnable#run方法;
  • 如果消息是通过Handler.sendMessage的方式投递到MQ中,那么回调handleMessage方法;

不管是哪种回调方式,回调一定发生在UI线程。因此如果应用发生卡顿,一定是在dispatchMessage中执行了耗时操作。我们通过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,如果超出阀值,表示发生卡顿,则dump出各种信息,提供开发者分析性能瓶颈。

我们通过给printer设置自定义的monitor即可完成卡顿的打点工作,通过loop.java中提供的setMessageLogging方法更改系统的monitor为我们上面自定义的LooperMonitor;

  /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
      ......
        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
            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);
            }

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


主线程的所有消息都在这里调度(msg.target.dispatchMessage即 handle的dispatchMessage方法)!!
每从MQ中取出一个消息,由于我们设置了Printer为LooperMonitor,因此在调用dispatchMessage前后都可以交由我们LooperMonitor接管。
我们再次从下面这段代码入手。


    @Override
    public void println(String x) {
        Log.e("LooperMonitor", "####println: x" + x);
        // 注意哈,mStopWhenDebugging 代表的是debug的时候不会打印
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        // 默认mPrintingStarted为false
        if (!mPrintingStarted) {
            // 记录开始的时间
            mStartTimestamp = System.currentTimeMillis();
            Log.e("LooperMonitor", "####println: mStartTimestamp" + mStartTimestamp);
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            //开始dump卡顿信息
            startDump();
        } else {
            //记录结束时间(此处是dispatch之后logging.println调用)
            final long endTime = System.currentTimeMillis();
            Log.e("LooperMonitor", "####println: endTime" + endTime);
            mPrintingStarted = false;
            // 比较两次的时间间隔是否是卡顿阈值
            if (isBlock(endTime)) {
                // 回调blockListener的onBlockEvent方法
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }

接着我们再回顾下blockListener的onBlockEvent方法中做了啥;

这里是保存信息到blockInfo这个变量中,然后回调拦截器中方法,进行notifaction或日志输出;

 private class MyBlockListener implements LooperMonitor.BlockListener {

        @Override
        public void onBlockEvent(long realStartTime, long realTimeEnd, long threadTimeStart, long threadTimeEnd) {
            // Get recent thread-stack entries and cpu usage
            ArrayList<String> threadStackEntries = stackSampler
                    .getThreadStackEntries(realStartTime, realTimeEnd);

            if (!threadStackEntries.isEmpty()) {
                BlockInfo blockInfo = BlockInfo.newInstance()
                        //这里打出来的是卡顿的耗时
                        .setMainThreadTimeCost(realStartTime, realTimeEnd, threadTimeStart, threadTimeEnd)
                        .setCpuBusyFlag(cpuSampler.isCpuBusy(realStartTime, realTimeEnd))
                        .setRecentCpuRate(cpuSampler.getCpuRateInfo())
                        .setThreadStackEntries(threadStackEntries)
                        .flushString();
                // 保存文件
                LogWriter.save(blockInfo.toString());

                if (mInterceptorChain.size() != 0) {
                    // 在这里回调实现的拦截器接口的onBlock方法(BlockCanaryContext + DisplayService)
                    for (BlockInterceptor interceptor : mInterceptorChain) {
                        interceptor.onBlock(getContext().provideContext(), blockInfo);
                    }
                }
            }
        }
    }


6、采集器分析

在我们自定义的LooperMonitor中会在hanlder的dispatchMessage中于卡顿前后分别调用loopMonitor.print方法。在print方法中

第一次会调用startDump方法,第二次调用stopDump方法;

  /**
     * dump CPU & Stack的信息
     */
    private void startDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.start();
        }

        if (null != BlockCanaryInternals.getInstance().cpuSampler) {
            BlockCanaryInternals.getInstance().cpuSampler.start();
        }
    }

 

 private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            // doSample抽象方法,由StackSampler和CpuSampler实现不同的采集方法
            doSample();

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

    public AbstractSampler(long sampleInterval) {
        if (0 == sampleInterval) {
            sampleInterval = DEFAULT_SAMPLE_INTERVAL;
        }
        mSampleInterval = sampleInterval;
    }

    // 在LooperMonitor.startDump处调用启用子线程开启采样
    public void start() {
        if (mShouldSample.get()) {
            return;
        }
        mShouldSample.set(true);

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

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

    abstract void doSample();

6.1 Stack采集器

比较简单,直接通过mCurrentThread.getStackTrace方法获取到卡顿的堆栈信息;

 /**
     * 采集Stack的信息
     */
    @Override
    protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();
        /**
         * 获取当前线程的堆栈信息
         */
        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            //TODO 在这里可以过滤自己想要过滤的堆栈信息
            //TODO 比如在这里过滤自己比较关注的包名
            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());
        }
    }

6.2 CPU采集器

略,详见CpuSampler.java

 

以下是节选自http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/

功能

BlockCanary会在发生卡顿(通过MonitorEnv的getConfigBlockThreshold设置)的时候记录各种信息,输出到配置目录下的文件,并弹出消息栏通知(可关闭)。

简单的使用如在开发、测试、Monkey的时候,Debug包启用

  • 开发可以通过图形展示界面直接看信息,然后进行修复
  • 测试可以把log丢给开发,也可以通过卡慢详情页右上角的更多按钮,分享到各种聊天软件(不要怀疑,就是抄的LeakCanary)
  • Monkey生成一堆的log,找个专人慢慢过滤记录下重要的卡慢吧

还可以通过Release包用户端定时开启监控并上报log,后台匹配堆栈过滤同类原因,提供给开发更大的样本环境来优化应用。

本项目提供了一个友好的展示界面,供开发测试直接查看卡慢信息(基于LeakCanary的界面修改)。

dump的信息包括:

  • 基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等
  • 耗时信息:实际耗时、主线程时钟耗时、卡顿开始时间和结束时间
  • CPU信息:时间段内CPU是否忙,时间段内的系统CPU/应用CPU占比,I/O占CPU使用率
  • 堆栈信息:发生卡慢前的最近堆栈,可以用来帮助定位卡慢发生的地方和重现路径

sample如下图,可以精确定位到代码中哪一个类的哪一行造成了卡慢。
blockcanary log sample

总结

BlockCanary作为一个Android组件,目前还有局限性,因为其在一个完整的监控系统中只是一个生产者,还需要对应的消费者去分析日志,比如归类排序,以便看出哪些卡慢更有修复价值,需要优先处理(TODO 这里可以实现耗时的打印日志排序,已经过滤和应用相关的信息);又比如需要过滤机型,有些奇葩机型的问题造成的卡慢,到底要不要去修复是要斟酌的。扯远一点的话,像是埋点除了统计外,完全还能用来做链路监控,比如一个完整的流程是A -> B -> D -> E, 但是某个时间节点突然A -> B -> D后没有到达E,这时候监控平台就可以发出预警,让开发人员及时定位。很多监控方案都需要C/S两端的配合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值