为了别的一篇功用优化实战方案讲解博客的结构明晰和篇幅,
咱们“望文生义”,把结构的源码解析部分搬到这边哈~项目GitHub
目录 1. 监控周期的 界说 2. dump模块 / 关于.log文件 3. 搜集仓库周期的 设定 4. 结构的 装备存储类 以及 文件体系操作封装 5. 文件写入进程(生成.log文件的源码) 6. 上传文件 7. 规划形式、技巧 8. 结构中各个首要类的功用划分
1. 【监控周期的 界说】
blockCanary打印一轮信息的周期,
是从主线程一轮Message(使命)分发处理开端
,到这个Message(使命)分发处理完毕
完毕,为一轮信息;
这个周期咱们也能够成为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
结构装备类的超类,供给应外部进行集成和装备信息: