Android BlockCanary源码解析

BlockCanary是什么
非侵入式的性能监控组件

BlockCanary的作用
发现UI卡顿问题(平时ANR时候我们才能获取到当堆栈的信息)

UI卡顿原理
1.60fps---->16m/s帧
2.尽量保证每次在16m/s内处理完所有的CPU与GPU计算绘制,渲染等操作,否则就会造成丢帧卡顿问题

主线程的作用
1.主线程的作用(应用启动后,创建一个主线程,就是ActivityThread)
2.把事件(点击,触摸等)发给合适的View或者Widget
3.同时也是应用与应用UI交互的主线程

耗时操作常见的在子线程处理的方法,(耗时操作不能在主线程处理)
Handler
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable,long)

UI卡顿的常见原因
1.耗时操作
2.布局Layout过于复杂,无法在16m/s内完成渲染
3.View的过度绘制
4.View频繁的触发measure,layout
5.内存频繁触发GC过多(虚拟机在执行垃圾回收的时候,所有的线程,包括UI线程都会暂停,只有当GC垃圾回收完后,所有的线程才能继续执行工作)

先看下[ActivityThread](Android应用程序只有一个主线程,就是ActivityThread)
看Android源码网站地址

//创建一个Looper,Looper里又会关联消息队列MessageQueue
   final Looper mLooper = Looper.myLooper();
    public static void main(String[] args) {
               .
               .
               .
    //一个主线程创建MainLooper后,就会在我们的应用生命周期内,不断轮询,通过Looper.loop()方法,获取到消息队列中的Message,然后通知我们的主线程,去更新UI
   Looper.prepareMainLooper();  
    }

看下prepareMainLooper()方法

public static void prepareMainLooper() {
 prepare(false);
 synchronized (Looper.class) {
   if (sMainLooper != null) {
    throw new IllegalStateException("The main Looper has already been prepared.");
     }
     //创建一个主线程的Looper(不管整个应用的主线程,它有多少个子线程,主线程中只有这一个Looper,不管有多少Hanlder,在其它地方可以创建Handler,最终会会到这个sMainLooper 上)
     sMainLooper = myLooper();
   }
  }

看下Looper.loop()方法

//在msg.target.dispatchMessage(msg)方法上下方都有log输出,在dispatchMessage上下分别打印方法执行的时间,根据时间差,来判定dispatchMessage中有没有耗时操作,也就是dispatchMessage中,有没有UI卡顿,如果超过时间差的阈值,就要打印出信息,BlockCanary就是利了这个特点,这也是它的核心实现原理
public static void loop() {
             final Printer logging = me.mLogging;
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                      final long dispatchEnd;
                       try {
                       //分发message,target就是Handler
                           msg.target.dispatchMessage(msg);
                           dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                       } finally {
                           if (traceTag != 0) {
                               Trace.traceEnd(traceTag);
                           }
                       }
                       if (logSlowDelivery) {
                              if (slowDeliveryDetected) {
                                  if ((dispatchStart - msg.when) <= 10) {
                                      Slog.w(TAG, "Drained");
                                      slowDeliveryDetected = false;
                                  }
                              } else {
                                  if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                          msg)) {
                                      // Once we write a slow delivery log,                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         suppress until the queue drains.
                                      slowDeliveryDetected = true;
                                 }
                              }
                          }
                          if (logSlowDispatch) {
                              showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                          }
              
                       if (logging != null) {
                           logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);        .
                  
}

看下msg.target.dispatchMessage(msg)方法

//这都是执行在主线程中,如果产生了UI卡顿,必定是在这个方法当中
 public void dispatchMessage(Message msg) {
 //callback就是runnable
  if (msg.callback != null) {
  //handleCallback,就是把runnable放到方法中,调用它的run方法执行我们的子线程
   handleCallback(msg);
    } else {
    //相当于调用hanlder.sendMessage
     if (mCallback != null) {
         if (mCallback.handleMessage(msg)) {
          return;
            }    
          }
          //处理消息
           handleMessage(msg);
         }
       }

看下BlockCanary.install方法

public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
    //init方法就做了BlockCanaryContext的成员变量的赋值
    BlockCanaryContext.init(context, blockCanaryContext);
    //设置通知栏消息是否开启或关闭,BlockCanaryContext.get().displayNotification(),debug模式默开启(true),release默认关闭(false)
    setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
    return get();
}

看下get()方法

 public static BlockCanary get() {
       //单例,生成BlockCanary的实例
        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());

    }

看下BlockCanaryInternals的构造方法

public BlockCanaryInternals() {
         //dump出线程的stack信息,传入的参数是主线程,第二个参数是Dump的间隔    
        stackSampler = new StackSampler(
                Looper.getMainLooper().getThread(),
                sContext.provideDumpInterval());
        //dump出cpu的有关情况
        cpuSampler = new CpuSampler(sContext.provideDumpInterval());
       //LooperMonitor是在dispatchMessage上下分别打印方法执行的时间
        setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
            //主线程的调用栈,CPU的是使用情况,内存使用情况都会这个方法监听到,会回调
            @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()));
       //就是用来删除日志用的,因为在BlockCanary当中,默认情况下日志会保存两天,需要定期的删除  
        LogWriter.cleanObsolete();
    }

看下start() 方法

  public void start() {
        if (!mMonitorStarted) {
            mMonitorStarted = true;
// Looper.getMainLooper()得到主线程Looper,调用setMessageLogging打印时间           Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
        }
    }

看下LooperMonitor的println(String x)方法

//打印时间
  public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        //表示是否在dispatchMessage方法前
        if (!mPrintingStarted) {
          //系统时间
            mStartTimestamp = System.currentTimeMillis();
            //当前线程运行状态的总时间(sleep和wait不会记录到时间里去)
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            startDump();
        } else {  //表示是否在dispatchMessage方法后
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            //表示有卡顿,UI有阻塞
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }

看下startDump()方法

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

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

看下BlockCanaryInternals.getInstance().stackSampler.start()方法

  public void start() {
        if (mShouldSample.get()) {
            return;
        }
        mShouldSample.set(true);

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

看下mRunnable,在aabstract class AbstractSampler 里,AbstractSampler 是stackSampler和cpuSampler的抽象类

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

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

看下 doSample()抽象方法StackSampler的实现

p

//打印栈的信息
rotected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();
//mCurrentThread.getStackTrace()获取到当前线程的调用栈,mCurrentThread是主线程,因为前面调用的是getMainLooper
        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }

        synchronized (sStackMap) {
            if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
            //以当前时间为Key,放入到HashMap中,sStackMap是LinkedHashMap,这里为什么要定义为LinkedHashMap,LinkedHashMap会记录插入的键的顺序,所以遍历的时候是按插入顺序的,普通的HashMap无法做到,所以遍历顺序未知
            sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
        }
    }

看下 doSample()抽象方法CpuSampler的实现

@Override
protected void doSample() {
    BufferedReader cpuReader = null;
    BufferedReader pidReader = null;

    try {
       //就是读写
        cpuReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/stat")), BUFFER_SIZE);
        String cpuRate = cpuReader.readLine();
        if (cpuRate == null) {
            cpuRate = "";
        }
                     .
                     .
                     .               
       
    } } catch (Throwable throwable) {}
    }

看下isBlock(endTime)方法

  private boolean isBlock(long endTime) {
  //endTime是dispatchMessage后的时间,mStartTimestamp 是dispatchMessage前的时间,mBlockThresholdMillis时间为3000,如果大于就表示产生卡顿了,UI阻塞了
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }

看下 notifyBlockEvent(endTime)方法

  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);
            }
        });
    }
上一篇:Android由浅及深Handler消息机制(转自郭霖)


下一篇:Android:使用处理程序和postDelayed()发出问题