在Android系统中,我们知道UI线程负责我们所有视图的布局,渲染工作,UI在更新期间,如果UI线程的执行时间超过16ms,则会产生丢帧的现象,而大量的丢帧就会造成卡顿,影响用户体验。
从上可知,大部分的卡顿原因都产生于代码编写不当导致,而这类问题都可以通过各种优化方案进行优化,所以我们需要做的就是尽可能准确的找到卡顿的原因,定位到准确的代码模块,最好是能定位到哪个方法导致卡顿,这样我们APP的性能就能得到很大的提升。
在开发阶段我们可以借助开发工具为我们提供的各种便利来有效的识别卡顿,如下:
具体使用可以看blog.csdn.net/u011578734/… 写的文章。
具体使用方法可以参考 blog.csdn.net/u011578734/…
线上检测方案比较流行的是BlockCanary和WatchDog,下面我们就看看它们是怎么做到检测UI卡顿的并反馈给开发人员。
BlockCanary的使用很简单,只要在Application中进行设置一下就可以如下:
BlockCanary.install(this, new AppBlockCanaryContext()).start();
可配置参数如下:
//卡顿阀值
int getConfigBlockThreshold();
boolean isNeedDisplay();
String getQualifier();
String getUid();
String getNetworkType();
Context getContext();
String getLogPath();
boolean zipLogFile(File[] src, File dest);
//可将卡顿日志上传到自己的服务
void uploadLogFile(File zippedFile);
String getStackFoldPrefix();
int getConfigDumpIntervalMillis();
熟悉Android的Handler机制的同学一定知道,Handler中重要的组成部分,looper,并且应用的主线程只有一个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);
}
...
}
}
注意到两个很关键的地方是logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
和logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
这两行代码,它调用的时机正好在dispatchMessage(msg)
的前后,而主线程卡也就是在dispatchMessage(msg)
卡住了。
BlockCanary就是通过替换系统的Printer来增加了一些我们想要的堆栈信息,从而满足我们的需求。
替换原有的Printer是通过以下方法:
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
并在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。如下所示:
@Override
public void println(String x) {
if (!mStartedPrinting) {
mStartTimeMillis = System.currentTimeMillis();
mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();
mStartedPrinting = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mStartedPrinting = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimeMillis > mBlockThresholdMillis;
}
基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等
耗时信息:实际耗时、主线程时钟耗时、卡顿开始时间和结束时间
CPU信息:时间段内CPU是否忙,时间段内的系统CPU/应用CPU占比,I/O占CPU使用率
堆栈信息:发生卡慢前的最近堆栈,可以用来帮助定位卡慢发生的地方和重现路径
threadStackSampler = new ThreadStackSampler(Looper.getMainLooper().getThread(),
sBlockCanaryContext.getConfigDumpIntervalMillis());
cpuSampler = new CpuSampler(sBlockCanaryContext.getConfigDumpIntervalMillis());
下面看一下ThreadStackSampler是怎么工作的?
protected void doSample() {
// Log.d("BlockCanary", "sample thread stack: [" + mThreadStackEntries.size() + ", " + mMaxEntryCount + "]");
StringBuilder stringBuilder = new StringBuilder();
// Fetch thread stack info
for (StackTraceElement stackTraceElement : mThread.getStackTrace()) {
stringBuilder.append(stackTraceElement.toString())
.append(Block.SEPARATOR);
}
// Eliminate obsolete entry
synchronized (mThreadStackEntries) {
if (mThreadStackEntries.size() == mMaxEntryCount && mMaxEntryCount > 0) {
mThreadStackEntries.remove(mThreadStackEntries.keySet().iterator().next());
}
mThreadStackEntries.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
直接去拿主线程的栈信息, 每半秒去拿一次, 记录下来, 如果发生卡顿就显之显示出来 拿CPU的信息较麻烦, 从/proc/stat
下面拿实时的CPU状态
, 再从/proc/" + mPid + "/stat
中读取进程时间, 再计算各CPU时间占比和CPU的工作状态.
public class WatchDog {
private final static String TAG = "budaye";
//一个标志
private static final int TICK_INIT_VALUE = 0;
private volatile int mTick = TICK_INIT_VALUE;
//任务执行间隔
public final int DELAY_TIME = 4000;
//UI线程Handler对象
private Handler mHandler = new Handler(Looper.getMainLooper());
//性能监控线程
private HandlerThread mWatchDogThread = new HandlerThread("WatchDogThread");
//性能监控线程Handler对象
private Handler mWatchDogHandler;
//定期执行的任务
private Runnable mDogRunnable = new Runnable() {
@Override
public void run() {
if (null == mHandler) {
Log.e(TAG, "handler is null");
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {//UI线程中执行
mTick++;
}
});
try {
//线程休眠时间为检测任务的时间间隔
Thread.sleep(DELAY_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当mTick没有自增时,表示产生了卡顿,这时打印UI线程的堆栈
if (TICK_INIT_VALUE == mTick) {
StringBuilder sb = new StringBuilder();
//打印堆栈信息
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement s : stackTrace) {
sb.append(s.toString() + "\n");
}
Log.d(TAG, sb.toString());
} else {
mTick = TICK_INIT_VALUE;
}
mWatchDogHandler.postDelayed(mDogRunnable, DELAY_TIME);
}
};
/**
* 卡顿监控工作start方法
*/
public void startWork(){
mWatchDogThread.start();
mWatchDogHandler = new Handler(mWatchDogThread.getLooper());
mWatchDogHandler.postDelayed(mDogRunnable, DELAY_TIME);
}
}
为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89
https://qr18.cn/FVlo89
启动优化
内存优化
UI优化
网络优化
Bitmap优化与图片压缩优化:https://qr18.cn/FVlo89
多线程并发优化与数据传输效率优化
体积包优化
https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J