Android 性能优化之界面优化
- FrameTime 两帧画面间隔耗时(也可简单认为是单帧渲染耗时)
- Frame Rate 帧率:指的是 1 秒钟播放多少帧
- FPS 每秒帧数(Frames Per Second):就是 1 秒内画面刷新次数,其实 FPS 就是帧率的简称
- Frame Dropped 掉帧:因性能不足(渲染耗时过长)或网络延迟等问题导致实际帧率低于目标预期值,无法按目标时间绘制渲染完所有的帧,从而导致部分帧被丢弃了,表现为界面卡顿或画面不流畅
停滞
- Frame Lost 丢帧(Frame Discard 抛帧、 Frame Throttled 压帧):主动丢帧,由于性能限制,主动地丢弃一些帧以保持画面的流畅性,可能帧率不及预期,但能够满足界面不卡顿
- Frame Skipped 跳帧:主动有策略地跳过某些帧的显示(可能是连续或非关键帧),但可能帧率能够维持预期值(比如视频倍速播放)地
- Jank 卡顿次数(精确卡顿率):帧率波动、画面不连续,卡顿通常是由掉帧引起的
优化策略
- 在 onDraw 方法中避免创建新对象,减少内存分配和垃圾回收
- 减少布局层级嵌套
- 优先使用 ConstraintLayout 约束布局、RelativeLayout相对布局等减少布局嵌套
- 使用
<include> 标签复用布局,使用 <merge> 和 <ViewStub> 标签优化布局层级
- 通过打开开发者选项 -> 调试 GPU 过度绘制,通过显示的不同颜色来区分是存在过度绘制
- 通过 Tools -> Layout Inspector 布局检查器工具查看布局层级,排查是否存在多层无用的嵌套
- 利用 dumpsys 系统抓取器辅助分析卡顿原因,通过 adb shell dumpsys gfxinfo <package_name> 获取系统界面性能状态信息,进行排查优化
- Systrace 系统跟踪工具是一个强大的系统级性能数据采样和分析工具,记录设备一段时间内的用户交互、CPU 活动和系统事件等信息数据,可用于识别应用中的卡顿,通过执行命令生成 HTML 报告供查看分析,也可以在代码中使用 Trace#traceBegin 和 Trace#traceEnd 记录应用内自定义日志
- 利用 Android Studio Profiler 分析应用性能,排查问题原因
Looper 消息循环日志监控
- 替换主线程 Looper 的 Printer 日志打印,通过在消息执行前后的打印信息计算消息执行时间,从而判断是否存在卡顿,若 dispatchMessage 执行时间异常,则判定为卡顿
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if (x.startsWith(">>>>>")) {
startTime = System.currentTimeMillis();
} else if (x.startsWith("<<<<<")) {
long duration = System.currentTimeMillis() - startTime;
}
}
});
Choreographer 编舞者进行帧监控
- 进行掉帧检测,在每一帧回调时计算帧间隔时间,若间隔时间超过 16 毫秒,就认为可能存在掉帧的情况
public class FrameMonitor implements Choreographer.FrameCallback {
private static final long FRAME_TIME_THRESHOLD = 16L;
private long lastFrameTimeNanos = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTimeNanos != 0) {
long jitterMillis = (frameTimeNanos - lastFrameTimeNanos) / 1000000;
if (jitterMillis > FRAME_TIME_THRESHOLD) {
System.out.println("帧间隔过长: " + jitterMillis + " ms");
}
}
lastFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
}
public void start() {
Choreographer.getInstance().postFrameCallback(this);
}
public void stop() {
Choreographer.getInstance().removeFrameCallback(this);
}
}
FrameMetrics
public class MainActivity extends Activity {
private Window.OnFrameMetricsAvailableListener frameMetricsListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
frameMetricsListener = new Window.OnFrameMetricsAvailableListener() {
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
}
};
getWindow().addOnFrameMetricsAvailableListener(frameMetricsListener, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (frameMetricsListener != null) {
getWindow().removeOnFrameMetricsAvailableListener(frameMetricsListener);
}
}
}
BlockCanary
- 基于 Looper 的 Printer 日志打印,内部也是使用了 Looper.getMainLooper().setMessageLogging 通过替换主线程 Looper 打印的日志方案实现
腾讯 Matrix
- 结合了 Choreographer 和 Looper 监控方案,分析 UI 线程的 Message 执行耗时
JankStats 卡顿统计(Metrics 指标库)
- Android 高版本采用 FrameMetrics,Android 低版本采用 Choreographer
dependencies {
implementation "androidx.metrics:metrics-performance:1.0.0-beta02"
}