天天看点

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

为了另外一篇性能优化实战方案讲解博客的结构清晰和篇幅,

我们“断章取义”,把框架的源码解析部分搬到这边哈~

项目GitHub

目录

1. 监控周期的 定义

2. dump模块 / 关于.log文件

3. 采集堆栈周期的 设定

4. 框架的 配置存储类 以及 文件系统操作封装

5. 文件写入过程(生成.log文件的源码)

6. 上传文件

7. 设计模式、技巧

8. 框架中各个主要类的功能划分

1. 【监控周期的 定义】

blockCanary打印一轮信息的周期,

是从

主线程一轮阻塞的开始

开始,到

阻塞的结束

结束,为一轮信息;

这个周期我们也可以成为

BlockCanary

监控周期/监控时间段

2. 【dump模块 / 关于.log文件】

这一个周期的信息,除了展现在

通知

处,还会展示在

logcat

处,

同时框架封装了

dump模块

即框架会把我们这一轮信息,在手机(移动终端)内存中,

输出成一个

.log

文件;

【当然,前提是在终端需要给这个APP

授权

,允许APP

读写内存

存放

.log

文件的目录名,我们可以在上面提到的

配置类

自定义

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

如这里定义成

blockcanary

在终端生成的文件与目录便是这样:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

3. 【采集堆栈周期的 设定】

我们说过

配置类

中,这个函数可以指定

认定为卡顿的阈值时间

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

这里指定为

500ms

,使得刚刚那个

2s

的阻塞被确定为

卡顿问题

其实还有一个函数,

用于指定在一个

监控周期

内,

采集数据的周期

!!!:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

这里返回的同样是

500ms

即从

线程阻塞

开始,每

500ms

采集一次数据,

给出一个

阻塞问题出现的根源

而刚刚那个

卡顿问题

阻塞的时间是

2s

那毫无疑问我们可以猜到,刚刚那个

.log

文件的内容里边,

2s/500ms = 4

采集的堆栈信息

!!

但是一个

监控周期/log文件

只打印一次

现场的详细信息

:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

如果设置为

250ms

,那便是有

2s/250ms = 8

采集的堆栈信息

了:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

4. 【框架的 配置存储类 以及 文件系统操作封装】

框架准备了一个存储配置的类,用于存储响应的配置:

配置存储类:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

-

getPath()

:拿到sd卡根目录到存储log文件夹的

目录路径

;!!!!!!!!

-

detectedBlockDirectory()

:返回

file类型

存储log文件

文件夹

目录

(如果没有这个文件夹,就创建文件夹,再返回

file类型

的这个文件夹);!!!!!!!!!!

-

getLogFiles()

如果

detectedBlockDirectory()

返回的那个

存储log文件

文件夹

目录

存在的话,

就把这个目录下所有的

.log

文件过滤提取出来,

并存储在一个

File[](即File数组)

里边,最后返回这个

File数组

;!!!!!!!

-

getLogFiles()

中的

listFiles()

是JDK中的方法,

用来返回

文件夹类型的File类实例

对应文件夹中(对应目录下)

所有的文件,

这里用的是它的重载方法,

就是传入一个过滤器,可以过滤掉不需要的文件;!!!!!!!

-

BlockLogFileFilter

是过滤器,用于过滤出

.log

文件;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

###下面稍微实战一下这个文件封装:

呐我们在MainActivity的onCreate中,使用

getLogFiles()

功能是刚说的获取BlockCanary生成的所有

.log

文件,以

.log

文件的形式返回,

完了我们把它打印出来:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

运行之后,呐,毫无悬念,BlockCanary生成的所有

.log

文件都被打印出来了:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

拿到了文件,

意味着我们可以在适当的时机,

将之上传到服务器处理!!!

5. 【文件写入过程(生成.log文件的源码)】

  • 一切要从框架的初始化开始说起:
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)
  • install()

    做了什么,

    install()

    里边,初始化了BlockCanaryContext和Notification等的一些对象,

    重要的,最后

    return

    调用了,

    get()

    有点单例的味道哈,

    BlockCanary

    的构造方法是私有的(下图可以见得),

    get()

    正是返回一个

    BlockCanary

    实例,

    当然new这一下也就调用了

    BlockCanary

    的构造方法;

    哦~

    BlockCanary

    的构造方法中,

    调用了

    BlockCanaryInternals.getInstance();

    拿到一个

    BlockCanaryInternals

    实例,赋给类中的全局变量!
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

BlockCanaryInternals.getInstance();

同样是使用了单例模式,

返回一个

BlockCanaryInternals

实例:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

同样也是new时候调用了

BlockCanaryInternals

的构造方法:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

可以看到

BlockCanaryInternals

的构造方法中

出现了关于

配置信息存储类

以及

文件的写入逻辑

了;

LogWriter.save(blockInfo.toString());

注意这里传入的是

配置信息的字符串

,接着是

LogWriter.save()

,这里的str便是刚刚的

blockInfo.toString()

,即配置信息;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

往下还有一层

save(一参对应刚刚的字符串"looper",二参为Block字符串信息【最早是来自BlockCanaryInternals中的LogWriter.save(blockInfo.toString());中的 blockInfo.toString() 】)

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

可以看到

.log

文件名的命名规则的就是定义在这里了,

.log文件

写入的输入流逻辑,也都在这里了;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

对比一下刚刚实验的结果,也就是实际生成的

.log文件

文件名

可见文件名跟上面

save()

方法中定义好的规则是一样的,无误;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

这两个在表头的字符串格式化器,

第一个是用来给

.log文件

命名的,

.log文件名

中的时间序列来自这里;

第二个是在save()函数中,用来写入文件的,

用时间来区分堆栈信息的每一次收集:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

下面这个方法是用来构造zip文件实例的,

给出一个文件名,再构造一个成对应的File实例;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

这个则是用来删除本框架生成的所有log文件的:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

其他的很容易看懂,就不多说了;

6.【上传文件】

首先框架想得很周到哈,它已经为我们封装了一个

Uploader

类,源码如下:

/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
...
final class Uploader {

    private static final String TAG = "Uploader";
    private static final SimpleDateFormat FORMAT =
            new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);

    private Uploader() {
        throw new InstantiationError("Must not instantiate this class");
    }

    private static File zip() {
        String timeString = Long.toString(System.currentTimeMillis());
        try {
            timeString = FORMAT.format(new Date());
        } catch (Throwable e) {
            Log.e(TAG, "zip: ", e);
        }
        File zippedFile = LogWriter.generateTempZip("BlockCanary-" + timeString);
        BlockCanaryInternals.getContext().zip(BlockCanaryInternals.getLogFiles(), zippedFile);
        LogWriter.deleteAll();
        return zippedFile;
    }

    public static void zipAndUpload() {
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                final File file = zip();
                if (file.exists()) {
                    BlockCanaryInternals.getContext().upload(file);
                }
            }
        });
    }
}           

复制

都封装成zip文件了,想得很周到很齐全吼,

点一下这个

upload

,又回到

配置类BlockCanaryContext

这儿来,

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

或者可以参考一下 这篇博客!!!!!!!,

可以在后台开启一个线程,定时扫描并上传。

或者

可以利用一下刚刚提到的 框架的文件系统操作封装 ,

再结合 自定义网络请求逻辑,

把文件上传到服务器也是ok的!

7. 设计模式、技巧:

7.1 单例模式,不用多说,

刚刚提到

BlockCanary

BlockCanaryInternals

里边都用到了;

7.2 回调机制设计:

内部接口,供给回调:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

定义内部接口的类,“抽象调用”回调接口方法:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

接口暴露给外部,在外部实现回调:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

8 .框架中各个主要类的功能划分

-

BlockCanary

提供给外部使用的,负责框架整体的方法调度;整体的、最顶层的调度;

-

BlockCanaryInternals

 封装控制

周期性采集堆栈信息

打印、输入

的关键逻辑;

(卡顿判定

阈值

采集信息周期

的配置,都在这里首先被使用)

(注意这里的

onBlockEvent() 回调方法

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

 封装文件操作模块(创建文件、创建文件目录、获取相关路径等等 这些

从SD卡根目录到存储

.log

文件目录 这个级别的处理,往下的目录下文件单位级别的处理,交给

LogWriter

)等核心逻辑;

 调用了

LogWriter.save()

进行log文件存储等;

 创建

CpuSampler

StackSample

实例,用于协助完成

周期性采集

-

LogWriter

封装了

文件流

写入、处理

等逻辑;

-

LooperMonitor

协助完成

周期性采集

【主要是阻塞任务始末的各种调度,即面向

卡顿阈值

当然,调度的内容也包括对

周期性采集

启闭调度

!!!!】;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

如上,

&

println()

有点像闹钟的角色,

它在主线程的任务分发

dispatchMessage前后

分别

被调用一次

它在采集周期

开始的时候

,就记录下

开始时间

在阻塞

任务完成之后

,会再次被调用,记录下

结束时间

&

isBlock()

:借助

println()

中记录的关于

主线程任务分发

开始时间

结束时间

来判断

阻塞的时间

是不是大于

我们设定

的或者

默认

卡顿判定时间

如果是,调用

notifyBlockEvent()

,间接调用到

回调方法 onBlockEvent()

这个方法上面说了,在

BlockCanaryInternals

的构造器中被具体实现了,

可以调用

LogWriter

最终输出

.log

文件;

&

startDump()

stopDump()

我们可以看到在

println()

中还有

startDump()

stopDump()

这两个方法,

分别也是在

主线程任务分发

开始

结束

时,随着

println()

被调用而被调用;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

startDump()

stopDump()

的内容正是控制两个

Sample

类的

启闭

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

-

CpuSampler

StackSample

同样负责协助完成

周期性采集

CpuSampler

的逻辑主要是面向

CPU信息

的处理,而

StackSample

的逻辑主要是对

堆栈信息

的收集;

他们都继承自

AbstractSample

首先在上面的源码我们可以看到,

BlockCanaryInternals

的构造器中,

就分别创建了一个

CpuSampler

(参数正为

采集堆栈信息周期

属性)和一个

StackSample

实例(参数为

采集堆栈信息周期

属性):

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

这个参数一路上走,经过

CpuSampler

的构造器,

最终是在

CpuSampler

的父类

AbstractSampler

中被使用!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

我们可以看到在

AbstractSampler

中,

AbstractSampler

构造器接收了

采集堆栈信息周期

属性,

同时准备了一个Runnable任务单元,

任务run()中做了两件事,

第一件事是调用抽象方法

doSample()

第二件事是基于这个

采集堆栈周期

属性这个

Runnable单元

创建一个

循环定时任务

!!!!!!!!!!!!!!!!!!!!!!!!!

即,

这个

Runnable单元

start()

之后,

将会每隔一个

采集周期

,就执行一次

run()

和其中的

doSample()

进行

堆栈信息

CPU信息

的周期性采集工作;

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

这便是

BlockCanary

能够

周期采集堆栈信息

的根源!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

那接下来我们可以展开三个点,

解决这个三个疑问点,脉络就理得差不多了:

【1. 由哪个

Handler

来处理这个

Runnable

我们知道,

Android中的

多线程任务单元

可以由一个

Handler

去post或者postDelayed一个

Runnable

来开启;

而这里处理这个

Runnable

Handler

正是

HandlerThreadFactory.getTimerThreadHandler()

HandlerThreadFactory

是框架的提供的一个内部线程类,

源码解析如下,使用了工厂模式吼:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

如此便可以获得,绑定了

工作线程(子线程)的Looper

Handler

有了这个

Handler

就可以处理刚刚说的

Runnable

任务单元了;

【2.

Handler

Runnable任务单元

启闭

是在哪个地方?】

当然是在

AbstractSampler

提供的

start()

stop()

里边了;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

CpuSampler

StackSample

都会继承自

AbstractSampler

,自然也就继承了这

start()

stop()

最后上面讲过了,

LooperMonitor

println()

中,

startDump()

stopDump()

会被调用,

而在

startDump()

stopDump()

中,

CpuSampler

StackSample

实例的

start()

stop()

也会被调用了,

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

从而控制了

周期采集信息

工作线程(子线程)任务单元

【上述的

Runnable实例

】的

启闭

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

【3.

doSample()

的实现】

CpuSampler

StackSample

都继承自

AbstractSample

使用的都是从

AbstractSample

继承过来的

Runnable实例

前面说过这个

Runnable单元

start()

之后,

将会每隔一个

采集周期

,就执行一次

run()

和其中的

doSample()

进行

堆栈信息

CPU信息

的周期性采集工作;

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

是这样的,

然后

CpuSampler

StackSample

通过对父类抽象方法

doSample()

做了不同的实现,

使得各自循环处理的任务内容不同罢了;

CpuSampler

的面向

CPU信息

的处理,

StackSample

则对

堆栈信息

的收集;】

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)
Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)

-

BlockCanaryContext

框架配置类的超类,提供给外部进行集成和配置信息:

Android卡顿优化 | AndroidPerformanceMonitor(BlockCanary)源码详析(真的很详细哦!)