BlockCanary源码笔记

BlockCanary

核心原理离不开主线程ActivityThread,用到了Handler,Looper;在Looper循环方法,BlockCanary利用了handler原理,在 msg.target.dispatchMessage(msg);的上下方去分别打印方法执行时间,根据时间差去判断dispatchMessage是否产生了耗时的操作,是否有UI卡顿

//将打印类替换成自己的打印类,重写println就能获取消息执行前后的时间
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 {
//上方下方都是log输出
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

BlockCanary流程分析

初始化
在application中的oncreate方法中

BlockCanary.install(getApplicationContext(), new AppBlockCanaryContext()).start();
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
        //init方法仅仅做了些赋值操作
        BlockCanaryContext.init(context, blockCanaryContext);
      //根据用户消息栏通知消息配置是否开启关系来展示blockcanary界面
        setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
        return get();
    }

public static BlockCanary get() {
        if(sInstance == null) {
            Class var0 = BlockCanary.class;
            synchronized(BlockCanary.class) {
                if(sInstance == null) {
                    sInstance = new BlockCanary();
                }
            }
        }
        return sInstance;
    }

private BlockCanary() {
        //BlockCanary一些重要的实现放在这个内部内中实现
        BlockCanaryInternals.setContext(BlockCanaryContext.get());
//BlockCanaryInternals的单例
        this.mBlockCanaryCore = BlockCanaryInternals.getInstance();
        this.mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
//通知栏是否开启,release是不显示的
        if(BlockCanaryContext.get().displayNotification()) {
//展示页面
            this.mBlockCanaryCore.addBlockInterceptor(new DisplayService());
        }
    }

由上面的代码分析出核心的一些功能在BlockCanary的内部类BlockCanaryInternals中实现

public final class BlockCanaryInternals {
    LooperMonitor monitor;
    StackSampler stackSampler;
    CpuSampler cpuSampler;
    private static BlockCanaryInternals sInstance;
    private static BlockCanaryContext sContext;
    private List<BlockInterceptor> mInterceptorChain = new LinkedList();

    public BlockCanaryInternals() {
//dump出我们线程的stack信息,传入主线程以及dump的时间间隔
        this.stackSampler = new StackSampler(Looper.getMainLooper().getThread(), (long)sContext.provideDumpInterval());
//dump出CPU的信息
        this.cpuSampler = new CpuSampler((long)sContext.provideDumpInterval());
//LooperMonitor很重要,因为通过LooperMonitor来打印dispatchMessage时上
//下的时间
        this.setMonitor(new LooperMonitor(new BlockListener() {
          //当消息处理时间大于阈值,回调打印主线程调用栈,cpu的使用情况,内存情况等
            public void onBlockEvent(long realTimeStart, long realTimeEnd, long threadTimeStart, long threadTimeEnd) {
                ArrayList threadStackEntries = BlockCanaryInternals.this.stackSampler.getThreadStackEntries(realTimeStart, realTimeEnd);
                if(!threadStackEntries.isEmpty()) {
                    BlockInfo blockInfo = BlockInfo.newInstance().setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd).setCpuBusyFlag(BlockCanaryInternals.this.cpuSampler.isCpuBusy(realTimeStart, realTimeEnd)).setRecentCpuRate(BlockCanaryInternals.this.cpuSampler.getCpuRateInfo()).setThreadStackEntries(threadStackEntries).flushString();
                    LogWriter.save(blockInfo.toString());
                    if(BlockCanaryInternals.this.mInterceptorChain.size() != 0) {
                        Iterator var11 = BlockCanaryInternals.this.mInterceptorChain.iterator();

                        while(var11.hasNext()) {
                            BlockInterceptor interceptor = (BlockInterceptor)var11.next();
                            interceptor.onBlock(BlockCanaryInternals.sContext.provideContext(), blockInfo);
                        }
                    }
                }

            }
        }, (long)getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
//删除日志
        LogWriter.cleanObsolete();
    }
}
stacksampler/cpusampler/start方法
上面分析了install方法,现在分析下start方法
BlockCanary.install(getApplicationContext(), new AppBlockCanaryContext()).start();

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

//Looper类中
public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

    }

LooperMonitor类中有个重要的方法println,之后Looper中的日志打印将走这个方法。

class LooperMonitor implements Printer {
    public void println(String x) {
        if(!this.mStopWhenDebugging || !Debug.isDebuggerConnected()) {
            if(!this.mPrintingStarted) {
                this.mStartTimestamp = System.currentTimeMillis();
//当前线程运行的总时间,sleep跟wait不会记录这个时间当中
                this.mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
                this.mPrintingStarted = true;
//开始dump
                this.startDump();
            } else {
                long endTime = System.currentTimeMillis();
                this.mPrintingStarted = false;
//如果message执行后减去执行前的时间大于阈值
                if(this.isBlock(endTime)) {
                    //执行回调
                    this.notifyBlockEvent(endTime);
                }
    //停止dump
                this.stopDump();
            }
        }
    }

private void startDump() {
        if(null != BlockCanaryInternals.getInstance().stackSampler) {
//开启HandlerThread线程处理doSample()方法,doSample()主要是把当前时间
//(key)跟当前线程栈信息(value)存入linkedHashmap中
            BlockCanaryInternals.getInstance().stackSampler.start();
        }

        if(null != BlockCanaryInternals.getInstance().cpuSampler) {
//同stackSampler,开启HandlerThread线程处理doSample()方法,主要是读
//取/proc/stat文件跟"/proc/" + this.mPid + "/stat"文件中的信息
            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();
        }

    }

}

StackSampler类:

private static final LinkedHashMap<Long, String> sStackMap = new LinkedHashMap();

protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();
//获取当前线程的调用栈,mCurrentThread这里代表主线程
        StackTraceElement[] var2 = this.mCurrentThread.getStackTrace();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            StackTraceElement stackTraceElement = var2[var4];
            stringBuilder.append(stackTraceElement.toString()).append("\r\n");
        }

        LinkedHashMap var8 = sStackMap;
        synchronized(sStackMap) {
            if(sStackMap.size() == this.mMaxEntryCount && this.mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
//sStackMap为LinkedHashMap,能记录我们先后插入顺序
            sStackMap.put(Long.valueOf(System.currentTimeMillis()), stringBuilder.toString());
        }
    }

相关问题

一.ANR问题:Application Not Responding

在android中由Activity Manager和WindowManager来监控,当出现以下三种情况会报ANR
1.Service Timeout:服务在20s没执行
2.BroadcastQueue Timeout:前台广播在10s没执行
3.inputDispatching Timeout:输入事件在5s没执行(触屏,按键等)

主要原因:主线程在规定时间内没完成任务。具体分以下几类
1.主线程在做一些耗时工作
2.主线程被其他线程锁
3.cpu被其他进程占用

如何解决:
1.主线程读取数据,耗时的数据库读取
2.sharePreference的commit(同步),apply(异步)
3.不要在广播的onReceive中做耗时操作
4.Activity的生命周期不应该有太多耗时操作

watchdog-anr 如何检测ANR
使用:在Application中开启new ANRWatchDog().start();
原理:ANRWatchDog继承Thread
1.创建监测线程
2.该线程不断往UI线程post一个任务
3.睡眠固定时间
4.等该线程重新起来后检测之前post的任务是否执行了(任务执行的是++操作,判断只需要判断是否执行了++)

public class ANRWatchDog extends Thread {
      private final Handler _uiHandler = new Handler(Looper.getMainLooper());
      @Override
    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;
            }
        }
    }
}
二.线程,多线程问题

1.new Tread问题
调用start启动线程(就绪状态),实现了多线程;run方法表示一个普通的方法,不代表多线程(??)
1).多个耗时任务就会开启多个新线程,开销非常大
2).如果在线程中执行循环任务,只能通过一个Flag来控制它的停止
3).没有线程切换的接口(只能通过handler或者其他的线程通信方式)
4).如果从UI线程启动,则该线程优先级默认为Default(和UI线程相同,可能还是会阻塞主线程);Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

2.线程通信
多线程编程时有两大原则
1).不要阻塞UI线程
2).不要在UI线程之外访问UI组件

线程通信分两类
1).将任务从工作线程抛到主线程
2).将任务从主线程抛到工作线程

工作线程抛任务到主线程(不管是哪种方式,最终都是通过Handler)
1).通过Handler
2).activity.runOnUiThread(runnalbe);
3).AsynkTask

任务从主线程到工作线程
1).Thread/Runnable
2).HandlerThread
3).IntentService

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容