Android 性能优化:卡顿分析与布局优化

继上篇继续性能优化,本文概要:

  1. 卡顿分析
    1. 卡顿原因
    2. 卡顿分析工具
    3. APP层面监控
  2. 布局优化
    1. 层及优化
    2. 过度渲染
    3. 布局加载优化

卡顿分析

卡顿原因

大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。Android系统每隔大概16.6ms发出VSYNC信
号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,
这意味着程序的大多数操作都必须在16ms内完成。
在这里插入图片描述

如果某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那
么用户在32ms内看到的会是同一帧画面。
在这里插入图片描述
一般主线程过多的UI绘制、大量的IO操作或是大量的计算操作占用CPU,导致App界面卡顿。

卡顿分析工具

Systrace

Systrace 是Android平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了
Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。Systrace主要用来分析绘制性能方面的问
题。在发生卡顿时,通过这份报告可以知道当前整个系统所处的状态,从而帮助开发者更直观的分析系统瓶颈,改
进性能。

要使用Systrace,需要先安装 Python2.7。安装完成后配置环境变量 path ,随后在命令行输入 python -- version进行验证
在这里插入图片描述

其实Systrace对于应用开发者来说,能看的并不多。主要用于看是否丢帧,与丢帧时系统以及我们应用大致的一个
状态。

python systrace.py -t 5 -o F:\example\a.html gfx input view am dalvik sched wm
disk res -a com.miss.example

在这里插入图片描述

CPU profile

在这里插入图片描述
这个使用与上面类似

APP层面监控

systrace可以让我们了解应用所处的状态,了解应用因为什么原因导致的。若需要准确分析卡顿发生在什么函数,资源占用情况如何,目前业界两种主流有效的app监控方式如下:

  1. 利用UI线程的Looper打印的日志匹配
  2. 使用Choreographer.FrameCallback
Looper日志检测卡顿

Android主线程更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。简单来说,
Android使用消息机制进行UI更新,UI线程有个Looper,在其loop方法中会不断取出message,调用其绑定的
Handler在UI线程执行。如果在handler的dispatchMesaage方法里有耗时操作,就会发生卡顿

public static void loop() {
	//......
	for (;;) {
		//......
		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);
	}
	//......
	}
}

loop函数每个执行操作都会记录其开始与结束,根据这个可以得到这个操作用了多长时间,这是BlockCanary的原理
BlockCanary传送门在此

Choreographer.FrameCallback

Android系统每隔16ms发出VSYNC信号,来通知界面进行重绘、渲染,每一次同步的周期约为16.6ms,代表一帧
的刷新频率。通过Choreographer类设置它的FrameCallback函数,当每一帧被渲染时会触发回调
FrameCallback.doFrame (long frameTimeNanos) 函数。frameTimeNanos是底层VSYNC信号到达的时间戳 。

public class ChoreographerHelper {
	public static void start() {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
		Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
			long lastFrameTimeNanos = 0;
			@Override
			public void doFrame(long frameTimeNanos) {
				//上次回调时间
				if (lastFrameTimeNanos == 0) {
					lastFrameTimeNanos = frameTimeNanos;
					Choreographer.getInstance().postFrameCallback(this);
					return;
				}
				long diff = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000;
				if (diff > 16.6f) {
					//掉帧数
					int droppedCount = (int) (diff / 16.6);
				}
				lastFrameTimeNanos = frameTimeNanos;
				Choreographer.getInstance().postFrameCallback(this);
				}
			});
		}
	}
}

通过 ChoreographerHelper 可以实时计算帧率和掉帧数,实时监测App页面的帧率数据,发现帧率过低,还可以自动保存现场堆栈信息。

总结:Looper比较适合在发布前进行测试或者小范围灰度测试然后定位问题,ChoreographerHelper适合监控线上环境的 app 的掉帧情况来计算 app 在某些场景的流畅度然后有针对性的做性能优化。

布局优化

层级优化

measure、layout、draw这三个过程都包含自顶向下的View Tree遍历耗时,如果视图层级太深自然需要更多的时间来完成整个绘测过程,从而造成启动速度慢、卡顿等问题。而onDraw在频繁刷新时可能多次出发,因此onDraw更不能做耗时操作,同时需要注意内存抖动。对于布局性能的检测,依然可以使用systrace与traceview按照绘制流程检查绘制耗时函数

Layout Inspector

位置在这
在这里插入图片描述
在这里插入图片描述
可以很清晰的看到布局的层级。我们应该尽量减少其层级,可以使用 ConstraintLayout 约束布局使得布局尽量扁平化,移除非必需的UI组件。

使用 merge 标签

当LayoutInflater遇到这个标签时,它会跳过它,并将内的元素添加到的父元素里。

可以参考这篇大佬的文章

使用 ViewStub标签

viewstub是一个轻量级的view,它不可见,不用占用资源,只有设置viewstub为visible或者调用其inflater()方法时,其对应的布局文件才会被初始化。

过度渲染

过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。

GPU 过度绘制检查

手机开发者选项中能够显示过度渲染检查功能,通过对界面进行彩色编码来帮我们识别过度绘制。开启步骤如下:

  1. 进入开发者选项 (Developer Options)。
  2. 找到调试 GPU 过度绘制(Debug GPU overdraw)。
  3. 在弹出的对话框中,选择显示过度绘制区域(Show overdraw areas)。

Android 将按如下方式为界面元素着色,以确定过度绘制的次数:

  • 真彩色:没有过度绘制
  • 蓝色:过度绘制 1 次
  • 绿色:过度绘制 2 次
  • 粉色:过度绘制 3 次
  • 红色:过度绘制 4 次或更多次
解决过度绘制问题
  • 移除布局中不需要的背景。
    默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容。但是,当布局具有背景时,其有可能会导致过度绘制。移除不必要的背景可以快速提高渲染性能。不必要的背景可能永远不可见,因为它会被应用在该视图上绘制的任何其他内容完全覆盖。例如,当系统在父视图上绘制子视图时,可能会完全覆盖父视图的背景。
  • 使视图层次结构扁平化。
    可以通过优化视图层次结构来减少重叠界面对象的数量,从而提高性能。
  • 降低透明度。
    对于不透明的 view ,只需要渲染一次即可把它显示出来。但是如果这个 view 设置了 alpha 值,则至
    少需要渲染两次。这是因为使用了 alpha 的 view 需要先知道混合 view 的下一层元素是什么,然后再
    结合上层的 view 进行Blend混色处理。透明动画、淡入淡出和阴影等效果都涉及到某种透明度,这就会
    造成了过度绘制。可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制。例如,如需
    获得灰色文本,可以在 TextView 中绘制黑色文本,再为其设置半透明的透明度值。但是,简单地通过
    用灰色绘制文本也能获得同样的效果,而且能够大幅提升性能。

布局加载优化

异步加载

LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。

dependencies {
	implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
}
new AsyncLayoutInflater(this)
	.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
	@Override
	public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
		setContentView(view);
		//......
	}
});
  1. 使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的
  2. 所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare )
  3. AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2
  4. 不支持加载包含 Fragment 的 layout
  5. 如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值