BlockCanary 源码分析

481 阅读3分钟

1.使用

debug模式依赖blockcanary

debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'

安装并启动BlockCanary

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

AppBlockCanaryContext

public class AppBlockCanaryContext  extends BlockCanaryContext {

     /..省略大量代码../
     
    //检测超过500毫秒的方法
    public int provideBlockThreshold() {
        return 500;
    }
    //卡顿发生自定义操作, blockInfo为卡顿信息
    public void onBlock(Context context, BlockInfo blockInfo) {

    }
}

配置读写权限 ,不然卡顿检测文件无法读写

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2.源码分析

2.1 install 方法分析

BlockCanary#install 方法 , 先初始化BlockCanaryContext,然后设置是否显示通知

    public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
        BlockCanaryContext.init(context, blockCanaryContext);
        setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
        return get();
    }

get方法

双重校验锁单例模式

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

BlockCanary私有构造方法中 ,添加卡顿发生之后的相关操作处理

 private BlockCanary() {
        BlockCanaryInternals.setContext(BlockCanaryContext.get());
        mBlockCanaryCore = BlockCanaryInternals.getInstance();
        //添加用于发生卡顿之后的拦截器
        mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
        if (!BlockCanaryContext.get().displayNotification()) {
            return;
        }
        //用于发生卡顿之后卡顿信息的显示
        mBlockCanaryCore.addBlockInterceptor(new DisplayService());

    }
public BlockCanaryInternals() {
        //栈采样
        stackSampler = new StackSampler(
                Looper.getMainLooper().getThread(),
                sContext.provideDumpInterval());
        //cpu采样
        cpuSampler = new CpuSampler(sContext.provideDumpInterval());
        //LooperMonitor继承自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()) {
                    if (mInterceptorChain.size() != 0) {
                        for (BlockInterceptor interceptor : mInterceptorChain) {
                            interceptor.onBlock(getContext().provideContext(), blockInfo);
                        }
                    }
                }
            }
        }, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
    }
方法调用栈采样

方法调用栈采样通过 HandlerThread 在后台线程进行采样 ,获取发生卡顿时间的栈调用信息

//com.github.moduth.blockcanary.StackSampler

  private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            //进行采样
            doSample();

            if (mShouldSample.get()) {
                HandlerThreadFactory.getTimerThreadHandler()
                        .postDelayed(mRunnable, mSampleInterval);
            }
        }
    };
    //发送handler消息 ,用于获取当前线程堆栈信息
    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);
    }

StackSampler#doSample 方法中 , 获取方法调用栈信息,并按照当前时间保存在map中

  for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }
  sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());        

LooperMonitor 继承自Printer , println 方法中统计Handler#handleMessage方法执行的时间差,如果超过配置的阀值则发生卡顿 , 显示 调用栈cpu等信息

 public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if (!mPrintingStarted) {
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            //开始调用栈 cpu 采样
            startDump();
        } else {
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            //停止调用栈 cpu 采样
            stopDump();
        }
    }

卡顿信息展示
 <activity
            android:name="com.github.moduth.blockcanary.ui.DisplayActivity"
            android:enabled="false"
            android:icon="@drawable/block_canary_icon"
            android:label="@string/block_canary_display_activity_label"
            //配置单独任务栈
            android:taskAffinity="com.github.moduth.blockcanary"
            android:theme="@style/block_canary_BlockCanary.Base" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

DisplayService#onBlock 中显示通知,然后点击通知启动DisplayActivity

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

DisplayActivity#updateUi 拿到卡顿信息 BlockInfo 然后渲染界面

    private void updateUi() {
        final BlockInfoEx blockInfo = getBlock(mBlockStartTime);
        if (blockInfo == null) {
            mBlockStartTime = null;
        }

        // Reset to defaults
        mListView.setVisibility(VISIBLE);
        mFailureView.setVisibility(GONE);

        if (blockInfo != null) {
            //渲染界面
            renderBlockDetail(blockInfo);
        } else {
            renderBlockList();
        }
    }

2.2 start 方法分析

给Looper设置Printer

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

Looper#setMessageLogging 方法中给mLogging赋值

public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

Looper#loop方法在handler handleMessage 方法执行前后打印

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
             //此处logging为start方法中设置的
            final Printer logging = me.mLogging;
            //handler的handleMessage方法执行前打印
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                //执行handler的handleMessage方法
                msg.target.dispatchMessage(msg);
            }
             //handler的handleMessage方法执行后打印 , 统计handleMessage执行时间
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
    }

3.总结

大致原理就是调用Looper#setMessageLogging 统计每个message执行的时间 ,判断方法执行时间是否超过设置的阀值,使用Thread.currentThread().getStackTrace() 获取当前线程的发生卡顿时的堆栈信息 ,然后进行界面的渲染