You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中多次创建慢加载自定义View的优化方法咨询

优化重复创建自定义View(优惠券布局)的性能方案

我之前在做电商优惠券列表的时候,刚好碰到过几乎一模一样的问题——每个优惠券View inflate要40-60ms,列表滑动卡得不行。结合你提到的正反面翻转布局,给你整理几个亲测有效的优化方向:

1. 复用LayoutInflater实例,避免重复初始化

每次调用View.inflate(context, layoutId, parent)其实都会内部创建一个新的LayoutInflater实例,这会带来额外的初始化开销。你可以在Adapter或者管理View创建的类里提前初始化好LayoutInflater,之后反复复用:

// 全局持有,只初始化一次
private LayoutInflater inflater;

// 初始化(比如在构造方法里)
inflater = LayoutInflater.from(context);

// 后续创建View时直接复用
View couponView = inflater.inflate(R.layout.coupon_layout, parent, false);

2. 正确使用inflate的attachToRoot参数

很多人习惯直接用View.inflate(context, layoutId, parent),但第三个参数attachToRoot设为true时,inflate过程中会立即执行View的measure和layout操作,这部分耗时可以延后到列表布局阶段处理。改成:

// 第三个参数传false,只解析布局不立即添加到父布局
View couponView = inflater.inflate(R.layout.coupon_layout, parent, false);
// 之后手动添加到父布局
parent.addView(couponView);

这样能省掉首次inflate时的measure/layout开销,尤其对复杂布局效果明显。

3. 用ViewStub延迟加载优惠券的反面布局

因为优惠券的正反面不会同时显示,完全没必要在首次创建时就inflate两面的布局。把反面的LinearLayout用ViewStub包裹,需要翻转时再加载:

<!-- 优惠券主布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <!-- 正面布局,直接加载 -->
    <LinearLayout
        android:id="@+id/coupon_front"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!-- 正面内容 -->
    </LinearLayout>

    <!-- 反面布局,用ViewStub延迟加载 -->
    <ViewStub
        android:id="@+id/coupon_back_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/coupon_back_layout" />
</LinearLayout>

代码中翻转时再初始化反面:

ViewStub backStub = couponView.findViewById(R.id.coupon_back_stub);
if (backStub != null) {
    View couponBack = backStub.inflate();
    // 初始化反面内容
}

这样首次创建View时,只需要加载正面布局,耗时直接减半。

4. 改用RecyclerView的ViewHolder复用机制(重中之重)

如果你的列表还在用普通LinearLayout或者ListView,赶紧换成RecyclerView。它的核心就是复用已创建的View实例,而不是每次滚动都重新inflate新的View。比如:

public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponViewHolder> {

    @NonNull
    @Override
    public CouponViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 这里只在View不够用时才创建,后续都是复用
        View view = inflater.inflate(R.layout.coupon_layout, parent, false);
        return new CouponViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull CouponViewHolder holder, int position) {
        // 只更新数据,不用重新创建View
        Coupon coupon = couponList.get(position);
        holder.bindData(coupon);
    }

    static class CouponViewHolder extends RecyclerView.ViewHolder {
        // 缓存View引用
        private LinearLayout couponFront;
        private ViewStub backStub;

        public CouponViewHolder(@NonNull View itemView) {
            super(itemView);
            couponFront = itemView.findViewById(R.id.coupon_front);
            backStub = itemView.findViewById(R.id.coupon_back_stub);
        }

        public void bindData(Coupon coupon) {
            // 更新正面数据,反面需要时再加载
        }
    }
}

这个优化能彻底解决重复inflate的问题,列表滑动时几乎不会有创建View的开销。

5. 用AsyncLayoutInflater异步inflate布局(可选)

如果必须要一次性创建大量View(比如初始化时加载几十张优惠券),可以用AndroidX提供的AsyncLayoutInflater在子线程异步inflate,避免阻塞主线程:

AsyncLayoutInflater asyncInflater = new AsyncLayoutInflater(context);
asyncInflater.inflate(R.layout.coupon_layout, parent, new AsyncLayoutInflater.OnInflateFinishedListener() {
    @Override
    public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
        // 回到主线程添加View
        parent.addView(view);
        // 初始化View数据
    }
});

注意:异步inflate的View不能直接操作UI,必须在回调里(主线程)完成添加和数据绑定。

6. 简化布局层级,减少解析耗时

检查你的优惠券布局,有没有多层嵌套的LinearLayout?比如用ConstraintLayout代替多层LinearLayout嵌套,减少View树的层级,这样LayoutInflater解析XML的速度会更快,后续的measure/layout也更高效。比如把原来的3层LinearLayout嵌套改成一层ConstraintLayout,能显著降低inflate时间。

总结

优先尝试RecyclerView复用+ViewStub延迟加载,这两个组合能解决90%的问题;再配合LayoutInflater复用和布局简化,基本能把每个View的创建耗时降到10ms以内。如果还有特殊场景,再考虑异步inflate方案。

内容的提问来源于stack exchange,提问作者Andrey Rankov

火山引擎 最新活动