[源码]BlockCanary学习笔记

来源:互联网 发布:华中师范网络教育收费 编辑:程序博客网 时间:2024/05/17 09:33

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使用的设计模式有单例模式、外观模式、责任链模式。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 临工210挖掘机柴油进气怎么办 汽车抛光蜡干了怎么办 洗碗铁丝球吃了怎么办 牙套铁丝吃肚子怎么办 小铁丝吃到肚子怎么办 绿色抛光膏干了怎么办 不锈钢被盐酸弄黑了怎么办 不锈钢被草酸洗了怎么办 不锈钢洗菜盆被草酸腐蚀了怎么办 汽油发电机加了柴油怎么办 装载机发动机加入齿轮油怎么办 印尼的FromE错了怎么办 寄快递被弄坏了怎么办 福田口岸手表被扣怎么办? 网页显示与服务器连接失败怎么办 唯品会中发货无法清关怎么办 国际快递被海关扣了怎么办 我想开一家物流公司手续怎么办? 物流公司把我的货弄丢了怎么办 物流公司压司机工资怎么办 立元增压泵不出水怎么办 高浊度pac不沉怎么办 集水池中沉积大量污泥怎么办 电脑qq截图不好使怎么办 捡了个手机需要指纹怎么办 手机锁屏锁住了怎么办 三星屏锁忘记了怎么办 文字下面有蓝色直线怎么办 苹果x不能截屏了怎么办 u盘在电脑上打不开怎么办 电脑桌面上文档剪切了怎么办 苹果官网查不到保修日期怎么办 吃了发霉的花生怎么办 鸡吃大蒜多了怎么办 玖瑰花叶子黄怎么办 羊偷吃腥油和花生饼吃多了怎么办 肉牛眼睛有点变黄少吃东西怎么办 黑坑草鱼不开口怎么办 花生和瓜子受潮皮了怎么办? 菜叶上长了腻虫怎么办 磨辊耐磨层脱落怎么办