之前一篇文章简单整理了Android 性能问题,这里的卡顿问题,是其中一部分,想来想去 还是单独整理这一部分,如果想了解Android 性能问题,请移步到 准备☞Android 性能优化
背景介绍
之前开发项目的时候,总是遇到性能问题,其中卡顿问题最为突出,项目庞大,优化起来总是望而却步。后来一位同事介绍可以通过 BlockCanary 这个工具(一位名为MarkZhai的大神,利用业余时间开发的)简单直白的分析这类问题。如获至宝,在此研究学习一下其中的原理总结一下。
BlockCanary有什么特点?
- 操作简单,两行代码就可以打开监控,并能够对主线程操作进行完全透明的监控
- 直观明了,能到直接输出有效 trace 信息,并精确定位问题所在哪一行
基于什么原理?
需要对消息分发机制有简单了解。下面会涉及到 Handler & Looper & MessageQueue 运行机制,如有不懂 或想深入了解,请
点击准备☞Android 异步消息分发机制
Android 应用程序有一个主线程(UI线程)ActivityThread,主线程被创建的时候,会默认初始化Looper(Looper.prepar()),创建Looper 对象,Looper 又会关联 MessageQueue ,Handler 发送消息 插入 MessageQueue 消息链表中,Looper的Loop() 方法不断从 MessageQueue 中获取消息更新UI。
由于整个应用的主线程只有一个Looper 对象,不管有多少 Handler ,都会回到这里。
接着看 Looper 的 Loop 方法
public static void loop() {
...
for (;;) {
...
// 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);
}
...
}
}
接着看一下,msg.target.dispatchMessage(msg),其中msg.target 就是 发送消息的 Handler 对象,查看 dispatchMessage() 方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果通过Handler.post(Runnable)方法 发送消息,会回调 Runnable 的 run 方法
如果通过 Handler.sendMessage 方法发送消息, 会回调 handleMessage 方法,最后都会调用 dispatchMessage 方法。
主线程更新UI,回调一定发生在主线程,由此可以看出 如果发生卡顿,一定发在 dispatchMessage 方法执行了耗时操作。通过统计 dispatchMessage 方法前后时间差,获取dispatchMessage 执行时间,是否查过阈值。
@Override
public void println(String x) {
if (!mStartedPrinting) {
mStartTimeMillis = System.currentTimeMillis();
mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();
mStartedPrinting = true;
} else {
final long endTime = System.currentTimeMillis();
mStartedPrinting = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimeMillis > mBlockThresholdMillis;
}
流程图
如何使用?
- 配置
1.在build.gradle 里添加
compile 'com.github.markzhai:blockcanary-android:1.5.0'
//或者仅在debug包启用BlockCanary进行卡顿监控和提示
debugCompile 'com.github.markzhai:blockcanary-android:1.5.0 '
releaseCompile com.github.markzhai:blockcanary-android-no-op:1.5.0'
2.AndroidManifest.xml 添加权限
<!--SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--向SDCard写入数据权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.在代码中动态添加权限
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
public void checkPermissions(Activity activity) {
try {
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
Toast.makeText(this, "请开通权限,否则无法使用", Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
在Activity 的 onCreate()方法里调用 checkPermission(this);
4.继承Application 在OnCreate() 方法里添加
BlockCanary.install(this, new BlockCanaryContext(){
@Override
public int provideBlockThreshold() {
// 默认是10000毫秒
return 500;
}
}).start();
demo演示
DevApplication.java
public class DevApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
BlockCanary.install(this, new BlockCanaryContext(){
@Override
public int provideBlockThreshold() {
// 默认是10000毫秒
return 500;
}
}).start();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermissions(this);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//模拟一个长时间操作,产生ANR
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Toast.makeText(getApplicationContext(), "点击完成", Toast.LENGTH_LONG).show();
}
});
}
}
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
public void checkPermissions(Activity activity) {
try {
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
Toast.makeText(this, "请开通权限,否则无法使用", Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
PS: 在最新8.0 版本验证不支持,Notification 的弹出,但是可以正常检测卡顿问题。