耗时任务
StrictMode
帮助开发者检查代码不规范问题 严苛模式:Android 提供的一种运行检查机制 方便强大,容易被忽视,包含线程策略与虚拟机检测策略
线程策略
- 自定义耗时调用:detectCustomSlowCalls()
- 磁盘读取策略:detectDiskReads()
- 网络操作:detectNetwork()
虚拟机策略
- Activity泄漏:detectActivityLeaks()
- Sqlite泄漏:detectLeakedSqlitebjects()
- 检测实例数量:setClassInstanceLimit()
通过日志进行输出不规范的信息,在日志输出窗口中使用StrictMode进行过滤输出的信息
//在release版本中不建议开启严格模式
private boolean DEV_MODE = true;
/**
* StrictMode 用来检测代码规范问题,有两种测量,一种是线程策略,一种是虚拟机策略
*/
private void initStrictMode() {
if (DEV_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls()//检测执行缓慢的代码或者潜在的缓慢代码
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyDialog()//弹窗违规提示框
.penaltyLog()//进行违规日志打印
.penaltyFlashScreen()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectActivityLeaks()//activity 泄漏
.detectLeakedClosableObjects()//未关闭的closeable对象泄漏
.detectLeakedSqlLiteObjects()//泄漏SQlite对象
.penaltyLog()
// .penaltyDeath()//使用严格模式,系统会做出相应的反应,比如打印日志,弹出对话框,以及崩溃,这里进行崩溃操作
.build());
}
}
注意
- 在线上环境即
Release
版本不建议开启严格模式。 - 严格模式无法监控
JNI
中的磁盘IO
和网络请求。 - 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行(Android中io操作在主线程中还是很常见的,比如sp的读写啊,从文件中读取图片啊,缓存啊之类的,并不是都是异步的,只要是不太耗时的就行,但是android中从3.0后就不允许UI线程中进行联网操作了,所以联网是必须独立了)。
数据少量且快速的IO操作是可以放在UI线程的,比如说少量的文件数据读取或者是写入之类的。。 但是如果涉及到数据量较大或者速度较慢的IO操作比如网络请求或者是蓝牙通信,避免放在UI线程中,这会阻塞UI线程,严重的时候甚至会导致app直接闪退报错。。 建议这些耗时的IO操作都放入到新开辟的线程中进行。 UI线程最好只需要负责UI界面的显示更新之类的操作。
Android 监控组件 AndroidPerformanceMonitor
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
AndroidPerformanceMonitor
是一个检测卡顿的开源库,前身是BlockCanary
,更前身则是LeakCanary
。而其使用与LeakCanary
也比较相似,可以自主设置卡顿检测时间,检测到的卡顿同样是以Notification
展示,在使用体验上也相当类似,与LeakCanary
可以说是孪生兄弟。
原理:
利用了Looper.loop()
中每个Message
被分发前后的Log打印,而我们设置自己的Printer
就可以根据Log
的不同的处理:
- Message
分发前,使用HandlerThread
延时发送一个Runnable
,这个时间可自己设置;
- Message
在规定的时间内完成分发,则会取消掉这个Runnable
;
- Message
没有在规定的时间内(实际上是规定时间的0.8)完成分发,那这个Runnable
就会被执行,可以获取到当前的堆栈信息;
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " msg.target " "
msg.callback ": " msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " msg.target " " msg.callback);
}
- 非侵入式
- 方便精准,定位到某一行代码
初始化操作:BlockCanary.install(this, new AppBlockCanaryContext()).start();
/**
* BlockCanary配置的各种信息
*/
public class AppBlockCanaryContext extends BlockCanaryContext {
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version flavor.
*/
@Override
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
@Override
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
@Override
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
@Override
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
@Override
public int provideBlockThreshold() {
return 500;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @return dump interval (in millis)
*/
@Override
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
@Override
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
@Override
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
@Override
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
@Override
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
@Override
public List<String> concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
@Override
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
@Override
public List<String> provideWhiteList() {
LinkedList<String> whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
@Override
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
Log.i("lz","blockInfo " blockInfo.toString());
}
}
ANR
AMS让应用组件做的操作,没有在规定的时间内完成,就会弹出这个AppNotRespondingDialog
弹框告诉该应用未响应。
1. KeyDispacthTimeOut 5s
2. BroadcastTimeout 前台10s, 后台60s
3. ServiceTimeout 前台20s,后台200s
在源码中appNotResponding
的mUIHandler
通过发送消息弹出一个Dialog
为AppNotRespondingDialog
,这里的mUIHandler
不是systemServer
的主线程,而是子线程
Service超时
在realStartServiceLocked
中进行启动service
,如果应用没有在规定时间内启动服务,就会触发一个超时机制,scheduleServiceTimeoutLocked
,就会触发serviceTimeout
,弹出AppNotRespondingDialog
。如果service正常启动完成就会在handleCreateService
中调用AMS的serviceDoneExecutingLocked
中移除超时消息
ANR 执行流程
- 发送ANR
- 进程接收异常终止信号,开始写入进程ANR信息
- 弹出ANR提示框(Room表现不一,有些手机厂商会把提示框给去掉)
ANR 解决方式
adb pull data/anr/traces.txt
存储路径,然后分析CPU、IO及锁
ANR 测试
代码语言:javascript复制 //给主线程造成卡顿,在子线程中获取锁,并让主线中等待20s,在让它获取锁
new Thread(new Runnable() {
@Override
public void run() {
synchronized (MainActivity.this){
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//申请这把锁
synchronized (MainActivity.this){
LogUtils.i("");
}
ANR 监控
- 通过
FileObserver
监控文件变化,高版本权限问题 - 使用
ANR-WatchDog
ANR-WatchDog
非侵入式ANR检测组件
https://github.com/SalomonBrys/ANR-WatchDog
使用:new ANRWatchDog().start();
原理
ANR-WatchDog
同样是一个检测卡顿的检测库,与AndroidPerformanceMonitor
不一样的是它的原理相对简单:
原理是开启一个线程,持续循环不断的往UI线程中Post一个Runnable(修改一个数的大小),然后在规定时间之后检测这个Runnable是否被执行(数的大小有没有 被修改过来)。没有被执行的话说明主线程执行上一个Message超时,然后获取当前堆栈信息;
ANR-WatchDog的原理更加简单,但是根据使用情况来看准确性不及AndroidPerformanceMonitor
高,而且可设置的配置不如AndroidPerformanceMonitor
丰富;
AndroidPerformanceMonitor与 ANR-WatchDog 区别
AndroidPerformanceMonitor
:监控Msg- ANR-WatchDog:看最终结果
- 前者适合监控卡顿,后者适合补充ANR监控
ANR-WatchDog优缺点
优点 1. 兼容性好,各个机型版本通用 2. 无需修改APP逻辑代码,非侵入式 3. 逻辑简单,性能影响不大
缺点 无法保证能捕捉所有ANR,对阈值的设置直接影响捕获概率