- 布局可以说是APP最重要的一项了,用户感知极强,无论你的代码写的如何,用户也不知道,用户只能看到和操作APP,更漂亮合理的布局,更流畅的体验才是好APP。
- 比如微信,操作起来卡,用户只会觉得是手机不行,而不会是微信不行,但其他APP卡,用户就觉得是APP不行,而不是手机不行。┓( ´∀` )┏
- Android性能优化 - 启动速度优化 也可一起学习。
1.卡顿分析
1.1 刷新率
- 大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用户体验。但是Android系统很有可能无法及时完成那些复杂的界面渲染操作。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
- 上面那段话就是,APP做的越来越炫酷,动画和视频一大堆,启动就要显示视频广告,这些对于旗舰Android机是无压力的,但对于老手机,或者是入门手机,复杂的页面,计算量大,CPU、GPU处理不过来,也就无法流畅显示了。
- 如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
1.2 PerfDog
- 下图,在PerfDog,使用华为P30Pro,查看微博的刷新情况,静态的微博内容在不滑动的时候,刷新率就是0fps,快速滑动时,刷新率在60fps左右,还能查看CPU和内存是使用情况。
- 下图,而在微博播放视频时,刷新率一直就是60fps左右了。
- 下图,普通的APP都基本能达到60fps,相机就不是了,相机拍照稳定在30fps,而自拍时,开启美颜,降到24fps了,看来相机加AI美颜是比较吃性能的。
- 小知识,电影或者是网上看的视频一般是24帧/秒的速度播放的,即可以省性能,效果也不错,索尼A7M3相机可以录制120帧的慢动作,可以做4倍或者5倍升格。
1.3 CPU Profile
- Android性能优化 - 启动速度优化 里有讲怎么使用 Profile 看各个方法的耗时,布局的加载也是会显示的,也可以用来分析卡顿的可能情况。
2.布局优化
2.1 过度绘制
- Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
- 蓝色绿色是比较好的情况,红色就是层级较多了,为了实现好看的效果,就会套多层布局,过度绘制的多了消耗性能,对与入门机就会卡顿了。
- 手机进入开发人员选项,调试GPU过度绘制,打开显示过度绘制区域。
- 贝壳APP的布局大多是蓝色绿色的,说明他们APP就没什么过度绘制的情况,非常好。
- 到下面的列表就会有过度绘制的情况,但区域不大,只有内容的部分。
- 开发人员选项,显示布局边界,可以看到贝壳的布局层级确实不多,也非常的清晰工整。
- 微博APP的过度绘制区域基本占满整个屏幕了,除了微博还有微信淘宝等列表APP也是大多数红色的,原因可能是列表类的APP,除了父布局,里面还要套RecyclerView,再套itemview,无法避免的过度绘制;但整个item都过度绘制了,贝壳就会比较好一些。
- 微博的布局看起来就会复杂一些了。
2.2 解决过度绘制
- 1.上面的微博跟贝壳比较,微博的item是有过度绘制的情况,那么我们在写RecyclerView的时候,如果RecyclerView的父布局、RecyclerView、item三者的背景只要其中一个设置就可以了,没有设置背景就不会渲染,否则就会有过度绘制的情况。
- 2.父布局套子布局也是尽量只设置其中一个背景,除非没办法都需要背景。
- 3.子view一般绘制后是会覆盖父view,所以一般选择把背景设置在子view。
- 4.视图的层级结构能减少就减少,层级越多绘制速度越慢。
- 5.尽量少设置view的透明度,如果一个view设置了alpha,那他需要知道下面的view是什么内容,再绘制自己,就是过度绘制。如果是文字有透明度,可以在色号里就设置好。
2.3 层级优化
-
Android studiol有布局层级的工具,Layout Inspector,运行起来app后,可以看到每个页面的层级结构。层级太多,肯定就会造成卡顿,启动慢,在启动优化有说,
- 左边可以看到布局树的具体内容。 布局树
-
像ScrollView里面只能放一个ViewGroup,是不可缩减的,但 LinearLayout套LinearLayout 是可以通过ConstraintLayout解决的,约束布局可以说是结合了线性布局跟相对布局的优点,能有效减少层级。
2.4 使用merge
- 我们有一些布局是可以通用的,避免重复代码,就可以使用 include。
- 但是,如果使用 include,但里面的布局又是一个 ViewGroup 的话,就会造成层级过多,这个时候就可以使用 merge 标签了,里面的子view根据会外部include地方的ViewGroup来排列,从而减少层级。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/layout_head" />
</LinearLayout>
<!-- layout_head -->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_head"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="text1"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="text2"
android:textSize="16sp" />
</merge>
复制代码
- 可以看到使用merge 里面的 view 直接在 LinearLayout 的层级里。
2.5 ViewStub
- 我们有时根据需求,先把布局画好,然后把 android:visibility 设置成 "invisible" 或者是 "gone" ,invisible 和 gone 虽然看不见,但他们还是有初始化,占用这内存和资源,前者还占用着位置。
- 我们可以使用 ViewStub 来包裹这些需要隐藏显示的 view,它是一个轻量级的view,不可见不占用资源,只有当设置 inflate 时才初始化显示。
<ViewStub
android:id="@+id/viewStub_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout="@layout/layout_title"
app:layout_constraintTop_toTopOf="parent" />
<!-- layout_title -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/image_back"
android:padding="12dp"
android:src="@drawable/ic_back" />
<TextView
android:layout_width="0dp"
android:id="@+id/tv_title"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="title" />
</LinearLayout>
复制代码
- 在Activity中 viewStub.inflate() 即可显示,但不可重复调用inflate();否则报异常:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.ViewStub.inflate()' on a null object reference。
viewStub = findViewById(R.id.viewStub_title);
viewStub.inflate();
//之后可以初始化里面的view
ImageView ivBack = findViewById(R.id.image_back);
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
TextView tvTitle = findViewById(R.id.tv_title);
复制代码
3.其他优化
3.1 不要在onDraw里创建对象
- 我们经常需要自定义view,在 onDraw() 方法里不要创建对象,因为自定义view绘制,会非常频繁的调用 onDraw,虽然方法里的对象创建用完就被回收掉,但频繁的创建销毁对象会导致内存抖动和GC ,而 GC 多了就会卡顿。
3.2 异步加载布局
- LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。
- Asynclayoutinflater 注意的地方:
- 1、使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的。
- 2、所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare )。
- 3、AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;。
- 4、不支持加载包含 Fragment 的 layout。
- 5、如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局。
本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...
网友评论