一、布局优化
Android 系统对View的绘制是一层一层进行的,层数越多,绘制过程就会越复杂。所以布局优化的一个主要手段就是减少布局的嵌套层级。减少布局的层级可以从以下几个方面入手:
- 删除布局中无用的层级和控件
- 根据场景使用最合适的布局结构
- 在不增加层级的前提下,优先使用LinearLayout而不是RelativeLayout,这是因为RelativeLayout 的绘制过程更复杂
- 使用标签<include>、<merge>减少重复的布局,减少UI的层级;使用ViewStub 实现控件的按需加载
简单介绍一下<include>、<merge>和ViewStub的使用
1、<include>标签
使用场景:如果两个页面存在相同的重复布局,那这部分重复的布局就应该被提取出来作为一个单独的布局文件供其他布局文件使用。
例如通用的标题栏title_bar.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/ToolbarStyle"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/fanhui" />
<TextView
android:id="@+id/tvLine1"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@color/gray"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</LinearLayout>
在其他布局内使用include 标签直接引用该布局文件。
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.MainActivity">
<include
android:id="@+id/titlebar_layout"
layout="@layout/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
如果布局文件和include都指定了id属性,则以include 指定的id为准。
使用include 标签提取重复的布局有两点好处:
- 避免产生大量相同的布局代码
- 如果需要更改标题栏样式,只需修改include 布局即可
2、<merge>标签
使用场景:如果被包含的布局文件的根布局是多余的,那么可以使用merge 标签替换根布局来达到减少层级的目的。举个例子:
重复布局 include.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
布局文件parent.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/include"/>
</LinearLayout>
我们可以看到,被包含的布局和它的父布局都是使用竖直的LinearLayout,因此被包含的布局中的LinearLayout是多余的,这个时候就可以使用merge 标签代替LinearLayout。即:
优化后的include.xml
<merge xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</merge>
3、ViewStub 控件
和上面两个标签不同,ViewStub本身就是一个View,默认情况下它是不可见的invisible、宽高都为0的特殊View,所以它本身不会参与布局的绘制过程。
它的主要作用是根据需要加载特殊的布局内容,这样可以避免在界面初始化过程直接绘制所有的控件导致卡顿,提高程序的初始化效率。
例如网络异常时的提示布局,应该在网络请求异常的时候才初始化显示在界面上。
<ViewStub
android:id="@+id/network_error"
android:inflatedId="@+id/id_network_error"
android:layout="@layout/layout_network_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
-
layout_network_error
指定显示的布局文件 -
id_network_error
布局文件的根元素id -
network_error
ViewStub自身的id
加载ViewStub 中的布局有两种:
第一种:((ViewStub)viewStub).setVisibility(View.VISIBLE);
第二种:((ViewStub)viewStub).inflate();
只要调用了这两个方法中的其中一个,ViewStub就会被它的内部布局替换掉,ViewStub也就不再是整个布局的一部分。
二、View绘制优化
正常情况下,Android系统每隔16ms会对UI进行一次渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps。
为了保证在16ms内完成绘制任务,自定义View时,尽量避免在onDraw() 方法内执行耗时操作。
- 不要在onDraw 方法内创建局部对象,因为onDraw 方法可能会被频繁调用,这样就可能会产生大量临时对象,从而导致系统频繁GC造成程序卡顿。
- 不要在onDraw 方法内执行耗时操作,如果在下一次渲染到来之前绘制任务还未完成,就会发生掉帧。
三、避免内存泄漏
四、RecyclerView优化
-
onBindViewHolder()
方法是在主线程进行的,因此它的主要任务是负责绑定数据,尽量避免在方法内部执行其他耗时操作 - 如果列表上只有小部分数据需要刷新,尽量使用局部刷新的方式,而不是任何时候都全部刷新。(利用
onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)
的payloads参数来完成局部刷新) - 简化ItemView的布局层级和组成结构,重复的布局可以尝试使用自定义View的方式替代。
- 如果ItemView高度是固定的,可以调用
RecyclerView.setHasFixedSize(true);
方法。当hasFixedSize设置为true,适配器内容的变化不会导致整个RecyclerView的尺寸发生变化。这样可以减少因重复计算每个Item的高度而造成的性能损耗。 - 如果RecyclerView的ItemView也是RecyclerView(列表里面嵌套列表),并且子RecyclerView具有相同的Adapter,那么可以为子RecyclerView设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool,这样子RecyclerView对ItemView的回收和重用都可以在同一个对象池进行。
class ParentAdapter extends RecyclerView.Adapter<ParentAdapter.PViewHolder> {
RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
@NonNull
@Override
public PViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
RecyclerView childRecyclerView = new RecyclerView(parent.getContext());
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(parent.getContext());
linearLayoutManager.setRecycleChildrenOnDetach(true); //子View在detach之后被重用
childRecyclerView.setLayoutManager(linearLayoutManager);
childRecyclerView.setHasFixedSize(true); //item高度固定
childRecyclerView.setRecycledViewPool(recycledViewPool); //共用RecycledViewPool对象池
return new PViewHolder(childRecyclerView);
}
@Override
public void onBindViewHolder(@NonNull PViewHolder holder, int position, @NonNull List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
//payloads不为空时局部刷新列表
}
……
class PViewHolder extends RecyclerView.ViewHolder {
public PViewHolder(@NonNull RecyclerView itemView) {
super(itemView);
}
}
}
- 如果是一个图片列表,应该考虑当列表正在滑动时暂停图片的加载,等到滑动停止时才开始加载图片。可以通过
addOnScrollListener(@NonNull OnScrollListener listener)
方法监听列表的滑动状态。
childRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
//停止滑动
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
//正在拖动
break;
case RecyclerView.SCROLL_STATE_SETTLING:
//惯性滑动
break;
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
- 使用DiffUtil 配合RecyclerView 刷新列表
Android DiffUtil
[Android]RecyclerView的好伴侣:详解 DiffUtil
[Android] DiffUtil在RecyclerView中的使用详解
五、线程管理优化
为了不影响主线程运行,Android应用程序中通常都会开启新线程去执行耗时任务,任务执行完毕后再把结果传递给主线程展示在UI上。
我们使用线程池来管理线程,线程池可以重用内部的线程,从而避免大量线程的创建和销毁带来的性能开销,同时也能控制线程的最大并发数,防止大量的线程互相抢占系统资源导致系统卡顿。因此我们应该尽量使用线程池来创建新线程,而不是直接创建一个Thread对象来执行任务。
关于线程池的相关知识可以参考Android 线程池的实现和分类
六、其他优化建议
- 尽量避免在短时间内创建和销毁大量的对象,如果有必要,可以使用对象池来管理对象的创建和销毁,参考线程池实现。
Java对象池技术的原理及其实现 - 避免使用Enum类型,Android中推荐使用@IntDef代替枚举类型。
android特有的魔术#变量注解替代Enum - 使用Android特有的数据结构替代容器类,例如使用SparseArray、ArrayMap替代HashMap可以更好地节省内存。
面试必备:SparseArray源码解析
面试必备:ArrayMap源码解析 - 内部类尽量指定为静态static,这样可以避免持有外部类的引用而导致的内存泄漏。
- 加载图片时使用内存和磁盘缓存机制,提高图片加载的速度,帮助用户节省流量资源。
网友评论