[源码]BlockCanary学习笔记

BlockCanary检查APP卡顿,相关使用方法以及源码学习

一、使用方法
1.添加依赖

dependencies {
    // most often used way, enable notification to notify block event
    compile 'com.github.markzhai:blockcanary-android:1.5.0'

    // this way you only enable BlockCanary in debug package
    // debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
    // releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}

2.在Application初始化并且启动

public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        // ...
        // Do it on main process
        BlockCanary.install(this, new AppBlockCanaryContext()).start();
    }
}

二、原理
1.原理概述:给主线程Looper设置一个Printer,记录dispatchMessage方法执行的耗时时间。如果耗时超过给定的阙值,则认为主线程卡顿了。如果发生卡顿,收集所有信息(CPU、设备、堆栈)并且格式化,debug时通知烂提示,release时上传服务器。

2.Looper源码片断

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 traceTag = me.mTraceTag;
    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
        Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    }
    try {
        msg.target.dispatchMessage(msg);
    } finally {
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }

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

3.BlockCanary原理图

三、源码分析
1.Application初始化以及启动

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

2.start过程

/**
 * Start monitoring.
 */
public void start() {
    //防止多次启动
    if (!mMonitorStarted) {
        mMonitorStarted = true;
        //给主线程设置Printer
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}

3.intsall过程

/**
 * Install {@link BlockCanary}
 *
 * @param context            Application context
 * @param blockCanaryContext BlockCanary context
 * @return {@link BlockCanary}
 */
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
    //初始化用户配置也即是用户传入的AppBlockCanaryContext
    BlockCanaryContext.init(context, blockCanaryContext);
    //显示/隐藏桌面Block图标,也就是是否禁用组建①
    setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
    //返回单例对象②
    return get();
}

①是否启动Activity组件

private static void setEnabled(Context context,
                                   final Class<?> componentClass,
                                   final boolean enabled) {
    final Context appContext = context.getApplicationContext();
    //单独提交到一个IO线程去执行,作者参考LeakCanary
    executeOnFileIoThread(new Runnable() {
        @Override
        public void run() {
            setEnabledBlocking(appContext, componentClass, enabled);
        }
    });
}
// these lines are originally copied from LeakCanary: Copyright (C) 2015 Square, Inc.
private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");

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);
}
// end of lines copied from LeakCanary

②install返回外观类BlockCanary单例对象

/**
 * Get {@link BlockCanary} singleton.
 * 单例模式DCL
 * @return {@link BlockCanary} instance
 */
public static BlockCanary get() {
    if (sInstance == null) {
        synchronized (BlockCanary.class) {
            if (sInstance == null) {
                sInstance = new BlockCanary();
            }
        }
    }
    return sInstance;
}

3.外观类BlockCanary类初始化时,会初始化核心内部类以及添加拦截器

public final class BlockCanary {

    private static final String TAG = "BlockCanary";
    //单例对象
    private static BlockCanary sInstance;
    //核心内部类对象
    private BlockCanaryInternals mBlockCanaryCore;
    //是否已经开启卡顿监测,防止多次开启
    private boolean mMonitorStarted = false;

    private BlockCanary() {
        //将配置赋值给内部核心类
        BlockCanaryInternals.setContext(BlockCanaryContext.get());
        //单例模式获取核心内部类②
        mBlockCanaryCore = BlockCanaryInternals.getInstance();
        //添加拦截器,用户实现的BlockInterceptor接口
        mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
        //如果用户禁用通知则不添加通知拦截器①
        if (!BlockCanaryContext.get().displayNotification()) {
            return;
        }
        mBlockCanaryCore.addBlockInterceptor(new DisplayService());

    }
......
}

①分析内部核心类之前先看下DisplayService类,这是一个拦截器,在发生卡顿时通知栏发送通知,代码作了版本适配

final class DisplayService implements BlockInterceptor {

    private static final String TAG = "DisplayService";

    @Override
    public void onBlock(Context context, BlockInfo blockInfo) {
        //发生卡顿时就显示通知,点击通知栏进入DisplayActivity界面
        Intent intent = new Intent(context, DisplayActivity.class);
        intent.putExtra("show_latest", blockInfo.timeStart);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
        String contentTitle = context.getString(R.string.block_canary_class_has_blocked, blockInfo.timeStart);
        String contentText = context.getString(R.string.block_canary_notification_message);
        show(context, contentTitle, contentText, pendingIntent);
    }
    //根据版本进行适配通知栏
    @TargetApi(HONEYCOMB)
    private void show(Context context, String contentTitle, String contentText, PendingIntent pendingIntent) {
        NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);

        Notification notification;
        if (SDK_INT < HONEYCOMB) {
            notification = new Notification();
            notification.icon = R.drawable.block_canary_notification;
            notification.when = System.currentTimeMillis();
            notification.flags |= Notification.FLAG_AUTO_CANCEL;
            notification.defaults = Notification.DEFAULT_SOUND;
            try {
                Method deprecatedMethod = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
                deprecatedMethod.invoke(notification, context, contentTitle, contentText, pendingIntent);
            } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                Log.w(TAG, "Method not found", e);
            }
        } else {
            Notification.Builder builder = new Notification.Builder(context)
                    .setSmallIcon(R.drawable.block_canary_notification)
                    .setWhen(System.currentTimeMillis())
                    .setContentTitle(contentTitle)
                    .setContentText(contentText)
                    .setAutoCancel(true)
                    .setContentIntent(pendingIntent)
                    .setDefaults(Notification.DEFAULT_SOUND);
            if (SDK_INT < JELLY_BEAN) {
                notification = builder.getNotification();
            } else {
                notification = builder.build();
            }
        }
        notificationManager.notify(0xDEAFBEEF, notification);
    }
}

②初始化了一个核心内部类对象,下面继续内部核心类分析跟踪过程

/**
 * Get BlockCanaryInternals singleton
 * 单例模式DCL
 * @return BlockCanaryInternals instance
 */
static BlockCanaryInternals getInstance() {
    if (sInstance == null) {
        synchronized (BlockCanaryInternals.class) {
            if (sInstance == null) {
                sInstance = new BlockCanaryInternals();
            }
        }
    }
    return sInstance;
}

4.内部核心类初始化过程:初始化堆栈采样器、初始化CPU采样器、初始化Printer、删除过期日志

public BlockCanaryInternals() {
        //初始化堆栈采样器①
        stackSampler = new StackSampler(
                Looper.getMainLooper().getThread(),
                sContext.provideDumpInterval());
        //初始化CPU采样器②
        cpuSampler = new CpuSampler(sContext.provideDumpInterval());
        //设置Printer③
        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();
    }

④先从软柿子捏起,先看删除日志

/**
     * Delete obsolete log files, which is by default 2 days.
     */
    public static void cleanObsolete() {
        //写线程执行删除日志操作
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                long now = System.currentTimeMillis();
                //获取所有日志文件
                File[] f = BlockCanaryInternals.getLogFiles();
                if (f != null && f.length > 0) {
                    synchronized (SAVE_DELETE_LOCK) {
                        for (File aF : f) {
                            //如果日志大于2天则删除
                            if (now - aF.lastModified() > OBSOLETE_DURATION) {
                                aF.delete();
                            }
                        }
                    }
                }
            }
        });
    }

如何获取所有日志文件

/**
 * 获取日志存储路径
 * 根据用户设置的文件名称,如果SD卡未挂在或者不可写则使用/data目录,否则使用根目录+用户设置路径
 */
static String getPath() {
    String state = Environment.getExternalStorageState();
    String logPath = BlockCanaryInternals.getContext()
            == null ? "" : BlockCanaryInternals.getContext().providePath();

    if (Environment.MEDIA_MOUNTED.equals(state)
            && Environment.getExternalStorageDirectory().canWrite()) {
        return Environment.getExternalStorageDirectory().getPath() + logPath;
    }
    return getContext().provideContext().getFilesDir() + BlockCanaryInternals.getContext().providePath();
}
/**
 * 获取日志路径文件夹,如果不存在则创建
 */
static File detectedBlockDirectory() {
    File directory = new File(getPath());
    if (!directory.exists()) {
        directory.mkdirs();
    }
    return directory;
}
/**
 * 获取所有日志文件,过滤后缀.log的文件
 */
public static File[] getLogFiles() {
    File f = detectedBlockDirectory();
    if (f.exists() && f.isDirectory()) {
        return f.listFiles(new BlockLogFileFilter());
    }
    return null;
}

/**
 * 文件过滤器
 */
private static class BlockLogFileFilter implements FilenameFilter {

    private String TYPE = ".log";

    BlockLogFileFilter() {

    }

    @Override
    public boolean accept(File dir, String filename) {
        return filename.endsWith(TYPE);
    }
}

③采样器是对信息的收集,稍后分析,直接看设置Printer时新建出来的LooperMonitor对象

/**
 * 实现Printer接口必须实现println()方法
 */
class LooperMonitor implements Printer {
    //默认卡顿阙值
    private static final int DEFAULT_BLOCK_THRESHOLD_MILLIS = 3000;

    private long mBlockThresholdMillis = DEFAULT_BLOCK_THRESHOLD_MILLIS;
    private long mStartTimestamp = 0;
    private long mStartThreadTimestamp = 0;
    private BlockListener mBlockListener = null;
    private boolean mPrintingStarted = false;
    //Debug时是否进行卡顿检测
    private final boolean mStopWhenDebugging;

    public interface BlockListener {
        void onBlockEvent(long realStartTime,
                          long realTimeEnd,
                          long threadTimeStart,
                          long threadTimeEnd);
    }

    public LooperMonitor(BlockListener blockListener, long blockThresholdMillis, boolean stopWhenDebugging) {
        if (blockListener == null) {
            throw new IllegalArgumentException("blockListener should not be null.");
        }
        mBlockListener = blockListener;
        mBlockThresholdMillis = blockThresholdMillis;
        mStopWhenDebugging = stopWhenDebugging;
    }

    @Override
    public void println(String x) {
        //Debug时用户设置是否停止检测并且是否Debug
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        //记录开始时间,也就是dispatchMessage之前的log打印时
        if (!mPrintingStarted) {
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            //启动采样器
            startDump();
        } else {
            //记录结束时间,也就是dispatchMessage之后的log打印时
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            //判断是否卡顿
            if (isBlock(endTime)) {
                //发生卡顿,回调
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }

    private boolean isBlock(long endTime) {
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }

    private void notifyBlockEvent(final long endTime) {
        final long startTime = mStartTimestamp;
        final long startThreadTime = mStartThreadTimestamp;
        final long endThreadTime = SystemClock.currentThreadTimeMillis();
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
            }
        });
    }

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

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

    private void stopDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.stop();
        }

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

回到③,在回调方法中,格式化了信息并写入文件中,启动拦截器

//设置Printer③
        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()));

①②是采样器,主要对信息的收集,其中startDump方法会启动采样器,延迟执行,延迟时间就是卡顿的阙值乘以0.8。

/**
 * {@link AbstractSampler} sampler defines sampler work flow.
 */
abstract class AbstractSampler {

    private static final int DEFAULT_SAMPLE_INTERVAL = 300;

    protected AtomicBoolean mShouldSample = new AtomicBoolean(false);
    protected long mSampleInterval;

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

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

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

    public void start() {
        if (mShouldSample.get()) {
            return;
        }
        mShouldSample.set(true);
        //延迟执行,延迟时间是阙值的0.8倍
        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();
}

四、BlockCanary使用的设计模式有单例模式、外观模式、责任链模式。

猜你喜欢

转载自blog.csdn.net/scau_zhangpeng/article/details/78796690