为了另外一篇性能优化实战方案讲解博客的结构清晰和篇幅, 我们“断章取义”,把框架的源码解析部分搬到这边哈~ 项目GitHub 目录 1. 监控周期的 定义 2. dump模块 / 关于.log文件 3. 采集堆栈周期的 设定 4. 框架的 配置存储类 以及 文件系统操作封装 5. 文件写入过程(生成.log文件的源码) 6. 上传文件 7. 设计模式、技巧 8. 框架中各个主要类的功能划分
1. 【监控周期的 定义】
blockCanary打印一轮信息的周期,
是从**主线程一轮阻塞的开始
**开始,到**阻塞的结束
**结束,为一轮信息;
这个周期我们也可以成为**BlockCanary
**的**监控周期/监控时间段
**;
2. 【dump模块 / 关于.log文件】
这一个周期的信息,除了展现在**通知
**处,还会展示在**logcat
**处,
同时框架封装了**dump模块
**,
即框架会把我们这一轮信息,在手机(移动终端)内存中,
输出成一个**.log
**文件;
【当然,前提是在终端需要给这个APP**授权
**,允许APP**读写内存
**】
存放**.log
**文件的目录名,我们可以在上面提到的**配置类
**中**自定义
**:
如这里定义成**blockcanary
**,
在终端生成的文件与目录便是这样:
3. 【采集堆栈周期的 设定】
我们说过**配置类
**中,这个函数可以指定**认定为卡顿的阈值时间
**:
这里指定为**500ms
**,使得刚刚那个**2s
**的阻塞被确定为**卡顿问题
**;
其实还有一个函数,
用于指定在一个**监控周期
**内,**采集数据的周期
**!!!:
这里返回的同样是**500ms
**,
即从**线程阻塞
**开始,每**500ms
**采集一次数据,
给出一个**阻塞问题出现的根源
**;
而刚刚那个**卡顿问题
**阻塞的时间是**2s
**,
那毫无疑问我们可以猜到,刚刚那个**.log
**文件的内容里边,
有**2s/500ms = 4
**次**采集的堆栈信息
**!!
但是一个**监控周期/log文件
**只打印一次**现场的详细信息
**:
如果设置为**250ms
**,那便是有**2s/250ms = 8
**次**采集的堆栈信息
**了:
4. 【框架的 配置存储类 以及 文件系统操作封装】
框架准备了一个存储配置的类,用于存储响应的配置:
配置存储类:
-**getPath()
**:拿到sd卡根目录到存储log文件夹的**目录路径
**;!!!!!!!!
-**detectedBlockDirectory()
**:返回**file类型
**的 存储log文件
**的**文件夹
**的**目录
**(如果没有这个文件夹,就创建文件夹,再返回**file类型
**的这个文件夹);!!!!!!!!!!**
-**getLogFiles()
**:
如果**detectedBlockDirectory()
**返回的那个**存储log文件
**的**文件夹
**的**目录
**存在的话,
就把这个目录下所有的**.log
**文件过滤提取出来,
并存储在一个**File[](即File数组)
**里边,最后返回这个**File数组
**;!!!!!!!
-**getLogFiles()
**中的**listFiles()
**是JDK中的方法,
用来返回**文件夹类型的File类实例
**其 对应文件夹中(对应目录下)
**所有的文件,**
这里用的是它的重载方法,
就是传入一个过滤器,可以过滤掉不需要的文件;!!!!!!!
-**BlockLogFileFilter
**是过滤器,用于过滤出**.log
**文件;
###下面稍微实战一下这个文件封装:
呐我们在MainActivity的onCreate中,使用**getLogFiles()
**,
功能是刚说的获取BlockCanary生成的所有**.log
**文件,以**.log
**文件的形式返回,
完了我们把它打印出来:
运行之后,呐,毫无悬念,BlockCanary生成的所有**.log
**文件都被打印出来了:
拿到了文件, 意味着我们可以在适当的时机, 将之上传到服务器处理!!!
5. 【文件写入过程(生成.log文件的源码)】
一切要从框架的初始化开始说起:
install()
**做了什么,**
install()
**里边,初始化了BlockCanaryContext和Notification等的一些对象,**
重要的,最后**return
**调用了,**get()
**; 有点单例的味道哈,**BlockCanary
**的构造方法是私有的(下图可以见得),
get()
**正是返回一个**BlockCanary
**实例,**
当然new这一下也就调用了**BlockCanary
**的构造方法;
哦~ BlockCanary
**的构造方法中,**
调用了**BlockCanaryInternals.getInstance();
**,
拿到一个**BlockCanaryInternals
**实例,赋给类中的全局变量!
BlockCanaryInternals.getInstance();
**同样是使用了单例模式,**
返回一个**BlockCanaryInternals
**实例:
同样也是new时候调用了**BlockCanaryInternals
**的构造方法:
可以看到**BlockCanaryInternals
**的构造方法中
出现了关于**配置信息存储类
**以及**文件的写入逻辑
**了;
LogWriter.save(blockInfo.toString());
**注意这里传入的是**配置信息的字符串
**,接着是**LogWriter.save()
**,这里的str便是刚刚的**blockInfo.toString()
**,即配置信息;**
往下还有一层**save(一参对应刚刚的字符串"looper",二参为Block字符串信息【最早是来自BlockCanaryInternals中的LogWriter.save(blockInfo.toString());中的 blockInfo.toString() 】)
**:
可以看到**.log
**文件名的命名规则的就是定义在这里了,
往**.log文件
**写入的输入流逻辑,也都在这里了;
对比一下刚刚实验的结果,也就是实际生成的**.log文件
**的**文件名
**,
可见文件名跟上面**save()
**方法中定义好的规则是一样的,无误;
这两个在表头的字符串格式化器,
第一个是用来给**.log文件
**命名的,**.log文件名
**中的时间序列来自这里;
第二个是在save()函数中,用来写入文件的,
用时间来区分堆栈信息的每一次收集:
下面这个方法是用来构造zip文件实例的,
给出一个文件名,再构造一个成对应的File实例;
这个则是用来删除本框架生成的所有log文件的:
其他的很容易看懂,就不多说了;
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
**这儿来,
或者可以参考一下 这篇博客!!!!!!!,
可以在后台开启一个线程,定时扫描并上传。
或者
可以利用一下刚刚提到的 框架的文件系统操作封装 ,
再结合 自定义网络请求逻辑,
把文件上传到服务器也是ok的!
7. 设计模式、技巧:
7.1 单例模式,不用多说,
刚刚提到**BlockCanary
**和**BlockCanaryInternals
**里边都用到了;
7.2 回调机制设计:
内部接口,供给回调:
定义内部接口的类,“抽象调用”回调接口方法:
接口暴露给外部,在外部实现回调:
8 .框架中各个主要类的功能划分
-**BlockCanary
** 提供给外部使用的,负责框架整体的方法调度;整体的、最顶层的调度;
-**BlockCanaryInternals
**
封装控制 周期性采集堆栈信息
**并**打印、输入
**的关键逻辑;**
(卡顿判定**阈值
**、**采集信息周期
** 的配置,都在这里首先被使用)
(注意这里的**onBlockEvent() 回调方法
**)
封装文件操作模块(创建文件、创建文件目录、获取相关路径等等 这些
从SD卡根目录到存储**.log
**文件目录 这个级别的处理,往下的目录下文件单位级别的处理,交给**LogWriter
**)等核心逻辑;
调用了**LogWriter.save()
**进行log文件存储等;
创建**CpuSampler
**、**StackSample
**实例,用于协助完成**周期性采集
**;
-**LogWriter
** 封装了**文件流
**的**写入、处理
**等逻辑;
-**LooperMonitor
**协助完成**周期性采集
**
【主要是阻塞任务始末的各种调度,即面向**卡顿阈值
**;
当然,调度的内容也包括对**周期性采集
**的**启闭调度
**!!!!】;
如上,
&**println()
**有点像闹钟的角色,
它在主线程的任务分发**dispatchMessage前后
**分别**被调用一次
**;
它在采集周期**开始的时候
**,就记录下**开始时间
**,
在阻塞**任务完成之后
**,会再次被调用,记录下**结束时间
**,
&**isBlock()
**:借助**println()
**中记录的关于**主线程任务分发
**的**开始时间
**和**结束时间
**,
来判断**阻塞的时间
**是不是大于**我们设定
**的或者**默认
**的**卡顿判定时间
**,
如果是,调用**notifyBlockEvent()
**,间接调用到**回调方法 onBlockEvent()
**,
这个方法上面说了,在**BlockCanaryInternals
** 的构造器中被具体实现了,
可以调用**LogWriter
** 最终输出**.log
**文件;
&**startDump()
**和**stopDump()
**:
我们可以看到在**println()
**中还有**startDump()
**和**stopDump()
**这两个方法,
分别也是在**主线程任务分发
**的**开始
**和**结束
**时,随着**println()
**被调用而被调用;
而**startDump()
**和**stopDump()
**的内容正是控制两个**Sample
**类的**启闭
**:
-**CpuSampler
**、**StackSample
**
同样负责协助完成**周期性采集
**
【**CpuSampler
**的逻辑主要是面向**CPU信息
**的处理,而
StackSample
**的逻辑主要是对**堆栈信息
**的收集;**
他们都继承自**AbstractSample
**】
首先在上面的源码我们可以看到,
在**BlockCanaryInternals
**的构造器中,
就分别创建了一个**CpuSampler
**(参数正为**采集堆栈信息周期
**属性)和一个**StackSample
**实例(参数为**采集堆栈信息周期
**属性):
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
这个参数一路上走,经过**CpuSampler
**的构造器,
最终是在**CpuSampler
**的父类**AbstractSampler
**中被使用!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
我们可以看到在**AbstractSampler
**中,
AbstractSampler
**构造器接收了**采集堆栈信息周期
**属性,**
同时准备了一个Runnable任务单元,
任务run()中做了两件事,
第一件事是调用抽象方法**doSample()
**;
第二件事是基于这个**采集堆栈周期
**属性这个**Runnable单元
**,
创建一个**循环定时任务
**!!!!!!!!!!!!!!!!!!!!!!!!!
即,
这个**Runnable单元
**被**start()
**之后,
将会每隔一个**采集周期
**,就执行一次**run()
**和其中的**doSample()
**;
进行**堆栈信息
**和**CPU信息
**的周期性采集工作;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
这便是**BlockCanary
**能够**周期采集堆栈信息
**的根源!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
那接下来我们可以展开三个点,
解决这个三个疑问点,脉络就理得差不多了:
【1. 由哪个**Handler
**来处理这个**Runnable
**】
我们知道,
Android中的**多线程任务单元
**可以由一个**Handler
**去post或者postDelayed一个**Runnable
**来开启;
而这里处理这个**Runnable
**的**Handler
**正是
HandlerThreadFactory.getTimerThreadHandler()
**,**
HandlerThreadFactory
**是框架的提供的一个内部线程类,**
源码解析如下,使用了工厂模式吼:
如此便可以获得,绑定了**工作线程(子线程)的Looper
**的 Handler
**;**
有了这个**Handler
**就可以处理刚刚说的**Runnable
**任务单元了;
【2. Handler
**对**Runnable任务单元
**的**启闭
**是在哪个地方?】**
当然是在**AbstractSampler
**提供的**start()
**和**stop()
**里边了;
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信息
**的周期性采集工作;
是这样的,
然后**CpuSampler
**、**StackSample
**通过对父类抽象方法**doSample()
**做了不同的实现,
使得各自循环处理的任务内容不同罢了;
【**CpuSampler
**的面向**CPU信息
**的处理,
而**StackSample
**则对**堆栈信息
**的收集;】
-**BlockCanaryContext
** 框架配置类的超类,提供给外部进行集成和配置信息: