惊爆!Android RecyclerView 性能优化全解析

惊爆!Android RecyclerView 性能优化全解析

一、RecyclerView 性能优化概述

1.1 性能优化的重要性

在 Android 应用开发中,RecyclerView 是一个广泛使用的组件,用于展示大量数据列表。其性能表现直接影响用户体验,若性能不佳,会出现卡顿、响应慢等问题。比如在新闻类应用中,用户快速滚动新闻列表时,若 RecyclerView 性能差,会让用户等待加载,降低用户对应用的好感度。因此,对 RecyclerView 进行性能优化是提升应用质量的关键环节。

1.2 影响 RecyclerView 性能的因素

影响 RecyclerView 性能的因素众多。首先是视图创建和绑定的效率,频繁创建新视图和重复绑定数据会消耗大量资源和时间。其次是数据处理,如数据量过大、数据更新方式不当等都会影响性能。再者,缓存机制不合理也会导致性能下降,不能有效复用视图会增加内存开销和视图创建时间。另外,布局的复杂度、动画效果的使用等也会对性能产生影响。

二、视图创建与绑定优化

2.1 减少视图创建开销

2.1.1 合理使用 ViewHolder 模式

ViewHolder 模式是 RecyclerView 中复用视图的核心。通过将视图的引用存储在 ViewHolder 中,避免每次都调用 findViewById 方法查找视图,从而提高视图绑定的效率。以下是使用 ViewHolder 模式的示例代码:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 将数据设置到 TextView 上
        holder.textView.setText(data);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}

在上述代码中,MyViewHolder 类存储了 TextView 的引用,在 onBindViewHolder 方法中可以直接使用,避免了重复查找视图的开销。

2.1.2 优化布局文件

布局文件的复杂度会影响视图的创建时间。尽量减少嵌套层级,使用 ConstraintLayout 等高效布局。例如,将原本复杂的嵌套布局改为使用 ConstraintLayout 实现:

xml 复制代码
<!-- 使用 ConstraintLayout 的布局文件 -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

使用 ConstraintLayout 可以通过约束关系来布局视图,减少嵌套层级,提高布局的性能。

2.2 优化数据绑定逻辑

2.2.1 避免在 onBindViewHolder 中进行耗时操作

onBindViewHolder 方法会在视图绑定数据时频繁调用,应避免在该方法中进行耗时操作,如网络请求、大量计算等。可以提前处理好数据,只在 onBindViewHolder 中进行简单的数据绑定。例如:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}

在上述代码中,onBindViewHolder 方法只进行了简单的文本设置操作,避免了耗时操作。

2.2.2 批量数据绑定

如果需要绑定大量数据,可以考虑批量绑定。例如,在 onBindViewHolder 方法中,一次绑定多个数据项,减少方法调用次数。以下是一个简单的示例:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 批量绑定数据,这里假设一次绑定 3 个数据项
        for (int i = 0; i < 3 && position + i < dataList.size(); i++) {
            String data = dataList.get(position + i);
            // 假设布局中有多个 TextView 用于显示数据
            if (i == 0) {
                holder.textView1.setText(data);
            } else if (i == 1) {
                holder.textView2.setText(data);
            } else if (i == 2) {
                holder.textView3.setText(data);
            }
        }
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义多个 TextView 用于显示数据
        TextView textView1;
        TextView textView2;
        TextView textView3;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView1 = itemView.findViewById(R.id.text_view_1);
            textView2 = itemView.findViewById(R.id.text_view_2);
            textView3 = itemView.findViewById(R.id.text_view_3);
        }
    }
}

通过批量绑定数据,可以减少 onBindViewHolder 方法的调用次数,提高性能。

三、缓存机制优化

3.1 理解 RecyclerView 缓存机制

RecyclerView 有四级缓存,分别是 mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool

3.1.1 mAttachedScrap

mAttachedScrap 是一级缓存,用于存储当前正在屏幕上显示的视图。当进行布局刷新时,这些视图会被暂时存储在 mAttachedScrap 中,布局完成后再重新使用。以下是相关源码分析:

java 复制代码
// 在 RecyclerView 的 LayoutManager 中布局子视图时
void onLayoutChildren(Recycler recycler, State state) {
    // 存储当前屏幕上的视图到 mAttachedScrap
    detachAndScrapAttachedViews(recycler);
    // 后续进行布局操作
    // ...
}

// 存储视图到 mAttachedScrap 的方法
void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
    final int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View v = getChildAt(i);
        // 将视图从布局中分离
        detachViewAt(i);
        // 将视图添加到 mAttachedScrap 中
        recycler.scrapView(v);
    }
}
3.1.2 mCachedViews

mCachedViews 是二级缓存,用于存储刚刚移出屏幕的视图。当用户快速滚动列表时,这些视图可以直接复用,而不需要重新创建和绑定数据。其默认大小为 2。以下是相关源码分析:

java 复制代码
// 在 RecyclerView 的 Recycler 类中回收视图时
void recycleViewHolderInternal(@NonNull ViewHolder holder) {
    // 判断是否可以添加到 mCachedViews
    if (mViewCacheMax > 0
            && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
            | ViewHolder.FLAG_REMOVED
            | ViewHolder.FLAG_UPDATE
            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
        if (mCachedViews.size() < mViewCacheMax
                && holder.getScrapContainer() == null) {
            // 将视图添加到 mCachedViews 中
            mCachedViews.add(holder);
            return;
        }
    }
    // 如果不能添加到 mCachedViews,添加到 mRecyclerPool
    addViewHolderToRecycledViewPool(holder, true);
}
3.1.3 mViewCacheExtension

mViewCacheExtension 是三级缓存,是一个自定义的缓存扩展接口。开发者可以根据自己的需求实现该接口,提供额外的缓存逻辑。以下是使用示例:

java 复制代码
// 自定义 mViewCacheExtension
RecyclerView.ViewCacheExtension viewCacheExtension = new RecyclerView.ViewCacheExtension() {
    @Nullable
    @Override
    public View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position, int type) {
        // 实现自定义的缓存逻辑
        // 这里简单返回 null
        return null;
    }
};

// 设置 mViewCacheExtension 到 RecyclerView
recyclerView.setViewCacheExtension(viewCacheExtension);
3.1.4 mRecyclerPool

mRecyclerPool 是四级缓存,用于存储不同类型的视图。当 mCachedViews 满了或者视图不符合 mCachedViews 的条件时,视图会被添加到 mRecyclerPool 中。以下是相关源码分析:

java 复制代码
// 在 RecyclerView 的 Recycler 类中添加视图到 mRecyclerPool
void addViewHolderToRecycledViewPool(@NonNull ViewHolder viewHolder, boolean force) {
    final int viewType = viewHolder.getItemViewType();
    // 获取对应视图类型的缓存列表
    ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType);
    if (mScrap.get(viewType) == null) {
        // 如果列表不存在,创建新的列表
        scrapHeap = new ArrayList<>();
        mScrap.put(viewType, scrapHeap);
    }
    if (force || scrapHeap.size() < DEFAULT_MAX_SCRAP) {
        // 将视图添加到缓存列表中
        scrapHeap.add(viewHolder);
        viewHolder.resetInternal();
    }
}

3.2 合理设置缓存大小

3.2.1 mCachedViews 缓存大小设置

mCachedViews 的默认缓存大小为 2。如果列表项的复用频率较高,可以适当增加 mCachedViews 的大小,以提高视图复用的效率。可以通过以下方式设置 mCachedViews 的大小:

java 复制代码
// 获取 RecyclerView 的 Recycler 对象
RecyclerView.Recycler recycler = recyclerView.getRecycler();
// 设置 mCachedViews 的大小为 5
recycler.setViewCacheSize(5);
3.2.2 mRecyclerPool 缓存大小设置

mRecyclerPool 的默认缓存大小为每个类型 5 个。如果列表项的类型较多或复用频率较高,可以适当增加 mRecyclerPool 的大小。可以通过以下方式设置 mRecyclerPool 的大小:

java 复制代码
// 创建 RecycledViewPool 对象
RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
// 设置某个视图类型的缓存大小为 10
recycledViewPool.setMaxRecycledViews(viewType, 10);
// 将 RecycledViewPool 设置给 RecyclerView
recyclerView.setRecycledViewPool(recycledViewPool);

3.3 利用缓存提高视图复用率

通过合理使用缓存机制,可以提高视图的复用率,减少视图的创建和销毁次数。例如,在 onBindViewHolder 方法中,可以先检查缓存中是否有可用的视图,若有则直接复用。以下是一个简单的示例:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }

    // 重写 getItemId 方法,用于支持缓存复用
    @Override
    public long getItemId(int position) {
        return position;
    }

    // 重写 hasStableIds 方法,用于支持缓存复用
    @Override
    public boolean hasStableIds() {
        return true;
    }
}

通过重写 getItemIdhasStableIds 方法,可以支持 RecyclerView 根据 item 的 ID 来复用视图,提高视图复用率。

四、数据处理优化

4.1 数据加载优化

4.1.1 分页加载数据

当数据量较大时,一次性加载所有数据会导致内存占用过高和加载时间过长。可以采用分页加载的方式,每次只加载部分数据。以下是一个简单的分页加载示例:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;
    // 每页加载的数据数量
    private static final int PAGE_SIZE = 20;
    // 当前页码
    private int currentPage = 0;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return Math.min(dataList.size(), (currentPage + 1) * PAGE_SIZE);
    }

    // 加载下一页数据
    public void loadNextPage() {
        currentPage++;
        notifyDataSetChanged();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}

// 在 Activity 中使用分页加载
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);

        // 监听 RecyclerView 的滚动事件,当滚动到底部时加载下一页数据
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
                int totalItemCount = layoutManager.getItemCount();
                if (lastVisibleItemPosition == totalItemCount - 1) {
                    adapter.loadNextPage();
                }
            }
        });
    }
}

在上述代码中,通过 PAGE_SIZE 控制每页加载的数据数量,currentPage 记录当前页码。当滚动到列表底部时,调用 loadNextPage 方法加载下一页数据。

4.1.2 异步加载数据

对于需要从网络或数据库加载数据的情况,应采用异步加载的方式,避免阻塞主线程。可以使用 AsyncTaskThreadExecutorService 等方式实现异步加载。以下是使用 AsyncTask 异步加载数据的示例:

java 复制代码
// 自定义 AsyncTask 用于异步加载数据
private class LoadDataTask extends AsyncTask<Void, Void, List<String>> {
    @Override
    protected List<String> doInBackground(Void... voids) {
        // 模拟从网络或数据库加载数据
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }
        return dataList;
    }

    @Override
    protected void onPostExecute(List<String> dataList) {
        super.onPostExecute(dataList);
        // 更新 RecyclerView 的数据源
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }
}

// 在 Activity 中使用 AsyncTask 加载数据
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // 启动 AsyncTask 加载数据
        new LoadDataTask().execute();
    }
}

在上述代码中,LoadDataTask 继承自 AsyncTask,在 doInBackground 方法中模拟从网络或数据库加载数据,在 onPostExecute 方法中更新 RecyclerView 的数据源。

4.2 数据更新优化

4.2.1 使用更细粒度的更新方法

在数据更新时,尽量使用更细粒度的更新方法,如 notifyItemInsertednotifyItemRemovednotifyItemChanged,而不是使用 notifyDataSetChangednotifyDataSetChanged 会导致整个列表重新布局和绑定数据,性能开销较大。以下是使用细粒度更新方法的示例:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 插入数据
    public void insertData(int position, String data) {
        dataList.add(position, data);
        // 使用 notifyItemInserted 通知 RecyclerView 插入了数据
        notifyItemInserted(position);
    }

    // 删除数据
    public void removeData(int position) {
        dataList.remove(position);
        // 使用 notifyItemRemoved 通知 RecyclerView 删除了数据
        notifyItemRemoved(position);
    }

    // 更新数据
    public void updateData(int position, String data) {
        dataList.set(position, data);
        // 使用 notifyItemChanged 通知 RecyclerView 更新了数据
        notifyItemChanged(position);
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}

在上述代码中,分别使用 notifyItemInsertednotifyItemRemovednotifyItemChanged 方法来处理数据的插入、删除和更新操作,避免了使用 notifyDataSetChanged 带来的性能开销。

4.2.2 DiffUtil 优化数据更新

DiffUtil 是 Android 提供的一个工具类,用于计算两个数据集之间的差异,并根据差异自动更新 RecyclerView。使用 DiffUtil 可以减少不必要的视图更新,提高性能。以下是使用 DiffUtil 的示例:

java 复制代码
// 自定义 DiffUtil.Callback
public class MyDiffCallback extends DiffUtil.Callback {
    private List<String> oldDataList;
    private List<String> newDataList;

    public MyDiffCallback(List<String> oldDataList, List<String> newDataList) {
        this.oldDataList = oldDataList;
        this.newDataList = newDataList;
    }

    @Override
    public int getOldListSize() {
        return oldDataList.size();
    }

    @Override
    public int getNewListSize() {
        return newDataList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldDataList.get(oldItemPosition).equals(newDataList.get(newItemPosition));
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldDataList.get(oldItemPosition).equals(newDataList.get(newItemPosition));
    }
}

// 在 Activity 中使用 DiffUtil 更新数据
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);

        // 模拟数据更新
        List<String> newDataList = new ArrayList<>(dataList);
        newDataList.add("New Item");

        // 使用 DiffUtil 计算差异
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(dataList, newDataList));
        // 更新数据源
        dataList = newDataList;
        // 应用差异更新 RecyclerView
        diffResult.dispatchUpdatesTo(adapter);
    }
}

在上述代码中,MyDiffCallback 继承自 DiffUtil.Callback,用于计算两个数据集之间的差异。使用 DiffUtil.calculateDiff 方法计算差异,然后使用 dispatchUpdatesTo 方法将差异应用到 RecyclerView 上。

五、布局优化

5.1 选择合适的 LayoutManager

不同的 LayoutManager 适用于不同的场景,选择合适的 LayoutManager 可以提高 RecyclerView 的性能。

5.1.1 LinearLayoutManager

LinearLayoutManager 用于线性布局,支持水平和垂直滚动。当列表项是线性排列时,使用 LinearLayoutManager 可以提供较好的性能。以下是使用 LinearLayoutManager 的示例:

java 复制代码
// 在 Activity 中使用 LinearLayoutManager
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        // 使用 LinearLayoutManager 进行垂直布局
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }
}
5.1.2 GridLayoutManager

GridLayoutManager 用于网格布局,可以将列表项排列成网格状。当需要显示网格布局的列表时,使用 GridLayoutManager 是一个不错的选择。以下是使用 GridLayoutManager 的示例:

java 复制代码
// 在 Activity 中使用 GridLayoutManager
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        // 使用 GridLayoutManager 进行 3 列的网格布局
        GridLayoutManager layoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }
}
5.1.3 StaggeredGridLayoutManager

StaggeredGridLayoutManager 用于瀑布流布局,列表项的高度可以不同。当需要显示瀑布流布局的列表时,使用 StaggeredGridLayoutManager 可以实现较好的效果。以下是使用 StaggeredGridLayoutManager 的示例:

java 复制代码
// 在 Activity 中使用 StaggeredGridLayoutManager
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        // 使用 StaggeredGridLayoutManager 进行 2 列的瀑布流布局
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }
}

5.2 优化布局文件

布局文件的复杂度会影响 RecyclerView 的性能。尽量减少嵌套层级,使用 ConstraintLayout 等高效布局。例如,将原本复杂的嵌套布局改为使用 ConstraintLayout 实现:

xml 复制代码
<!-- 使用 ConstraintLayout 的布局文件 -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

使用 ConstraintLayout 可以通过约束关系来布局视图,减少嵌套层级,提高布局的性能。

5.3 避免过度绘制

过度绘制会导致 CPU 和 GPU 资源的浪费,影响 RecyclerView 的性能。可以通过以下方法避免过度绘制:

5.3.1 去除不必要的背景

在布局文件中,去除不必要的背景设置。例如,如果某个视图的背景和其父视图的背景相同,可以将该视图的背景设置为透明。

xml 复制代码
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:background="@android:color/transparent" />

</LinearLayout>
5.3.2 使用 ViewStub 延迟加载

对于一些不常用的视图,可以使用 ViewStub 进行延迟加载。ViewStub 是一个轻量级的视图,只有在需要显示时才会加载其对应的布局。以下是使用 ViewStub 的示例:

xml 复制代码
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/stub_layout" />

</LinearLayout>
java 复制代码
// 在 Activity 中使用 ViewStub
public class MainActivity extends AppCompatActivity {
    private ViewStub viewStub;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewStub = findViewById(R.id.view_stub);
        // 在需要显示时加载布局
        if (needShowStub) {
            viewStub.inflate();
        }
    }
}

六、动画与交互优化

6.1 合理使用动画效果

动画效果可以增强用户体验,但过度使用会影响 RecyclerView 的性能。应合理使用动画效果,避免复杂的动画。

6.1.1 使用默认动画

RecyclerView 提供了默认的动画效果,如添加和删除列表项时的淡入淡出效果。可以直接使用这些默认动画,而不需要自定义复杂的动画。以下是使用默认动画的示例:

java 复制代码
// 在 Activity 中使用默认动画
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);

        // 设置默认动画
        recyclerView.setItemAnimator(new DefaultItemAnimator());
    }
}
6.1.2 优化自定义动画

如果需要自定义动画,应尽量简化动画逻辑,

避免复杂的计算和过度的绘制。例如,在自定义 ItemAnimator 时,减少不必要的动画帧和属性变化。以下是一个简单的自定义 ItemAnimator 示例:

java 复制代码
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView;

// 自定义 ItemAnimator 类,继承自 DefaultItemAnimator
public class CustomItemAnimator extends DefaultItemAnimator {

    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        // 获取视图
        View view = holder.itemView;
        // 设置视图初始透明度为 0
        view.setAlpha(0f);
        // 创建透明度从 0 到 1 的动画
        ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
        // 设置动画时长为 300 毫秒
        animator.setDuration(300);
        // 设置动画插值器为加速减速插值器
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        // 启动动画
        animator.start();
        // 返回 true 表示动画正在执行
        return true;
    }

    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        // 获取视图
        View view = holder.itemView;
        // 创建透明度从 1 到 0 的动画
        ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
        // 设置动画时长为 300 毫秒
        animator.setDuration(300);
        // 设置动画插值器为加速减速插值器
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        // 添加动画监听器
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                // 动画开始时的操作
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // 动画结束时,调用父类的 dispatchRemoveFinished 方法
                dispatchRemoveFinished(holder);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                // 动画取消时的操作
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                // 动画重复时的操作
            }
        });
        // 启动动画
        animator.start();
        // 返回 true 表示动画正在执行
        return true;
    }

    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
        // 结束动画时,调用父类的 endAnimation 方法
        super.endAnimation(item);
    }

    @Override
    public void runPendingAnimations() {
        // 运行待执行的动画时,调用父类的 runPendingAnimations 方法
        super.runPendingAnimations();
    }

    @Override
    public boolean isRunning() {
        // 判断动画是否正在运行,调用父类的 isRunning 方法
        return super.isRunning();
    }
}

Activity 中使用自定义 ItemAnimator

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);

        // 设置自定义动画
        recyclerView.setItemAnimator(new CustomItemAnimator());
    }
}

6.2 优化交互响应

RecyclerView 的交互响应性能也很重要,以下是一些优化交互响应的方法:

6.2.1 减少点击事件的处理时间

ViewHolder 中处理点击事件时,应尽量减少处理时间,避免在点击事件处理方法中进行耗时操作。例如:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
        // 设置点击事件监听器
        holder.itemView.setOnClickListener(v -> {
            // 简单的点击事件处理,避免耗时操作
            // 例如,这里可以显示一个 Toast 提示
            Toast.makeText(v.getContext(), "Clicked: " + data, Toast.LENGTH_SHORT).show();
        });
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}
6.2.2 优化滑动响应

在处理 RecyclerView 的滑动事件时,避免在滑动事件处理方法中进行大量的计算或数据操作。可以使用 OnScrollListener 监听滑动事件,并在合适的时机进行处理。例如:

java 复制代码
// 在 Activity 中监听 RecyclerView 的滑动事件
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);

        // 监听 RecyclerView 的滑动事件
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                // 简单的滑动事件处理,避免大量计算
                if (dy > 0) {
                    // 向下滑动
                } else {
                    // 向上滑动
                }
            }

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    // 滑动停止
                }
            }
        });
    }
}

七、内存管理优化

7.1 避免内存泄漏

在使用 RecyclerView 时,要注意避免内存泄漏。常见的内存泄漏情况包括:

7.1.1 静态变量引用

避免在静态变量中引用 RecyclerView 或其相关的视图和数据,因为静态变量的生命周期与应用程序的生命周期相同,会导致相关对象无法被垃圾回收。例如,不要这样写:

java 复制代码
public class MainActivity extends AppCompatActivity {
    // 错误示例:静态变量引用 RecyclerView
    private static RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recycler_view);
        // ...
    }
}
7.1.2 匿名内部类引用

在使用匿名内部类时,要注意其对外部类的引用。如果匿名内部类持有外部类的引用,并且生命周期比外部类长,会导致外部类无法被垃圾回收。例如,在使用 OnClickListener 时,可以使用静态内部类来避免这个问题:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> dataList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的数据
        String data = dataList.get(position);
        // 只进行简单的数据绑定
        holder.textView.setText(data);
        // 使用静态内部类处理点击事件
        holder.itemView.setOnClickListener(new MyClickListener(data));
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 TextView 用于显示数据
        TextView textView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 TextView
            textView = itemView.findViewById(R.id.text_view);
        }
    }

    // 静态内部类处理点击事件
    private static class MyClickListener implements View.OnClickListener {
        private String data;

        public MyClickListener(String data) {
            this.data = data;
        }

        @Override
        public void onClick(View v) {
            // 处理点击事件
            Toast.makeText(v.getContext(), "Clicked: " + data, Toast.LENGTH_SHORT).show();
        }
    }
}

7.2 优化图片加载

如果 RecyclerView 中包含图片,图片的加载和显示会占用大量内存。可以使用图片加载库(如 Glide、Picasso)来优化图片加载,这些库可以自动处理图片的缓存和缩放,减少内存占用。以下是使用 Glide 加载图片的示例:

java 复制代码
// 自定义 RecyclerView.Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    // 数据源
    private List<String> imageUrlList;

    // 构造函数,接收数据源
    public MyAdapter(List<String> imageUrlList) {
        this.imageUrlList = imageUrlList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        // 返回自定义的 ViewHolder
        return new MyViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        // 获取对应位置的图片 URL
        String imageUrl = imageUrlList.get(position);
        // 使用 Glide 加载图片
        Glide.with(holder.itemView.getContext())
               .load(imageUrl)
               .into(holder.imageView);
    }

    // 获取数据源的大小
    @Override
    public int getItemCount() {
        return imageUrlList.size();
    }

    // 自定义 ViewHolder 类
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // 定义 ImageView 用于显示图片
        ImageView imageView;

        // 构造函数,接收视图
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            // 初始化 ImageView
            imageView = itemView.findViewById(R.id.image_view);
        }
    }
}

7.3 及时释放资源

在 RecyclerView 不再使用时,要及时释放相关资源。例如,在 ActivityonDestroy 方法中,可以释放数据源和适配器:

java 复制代码
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> dataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据源
        dataList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            dataList.add("Item " + i);
        }

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 释放数据源
        if (dataList != null) {
            dataList.clear();
            dataList = null;
        }
        // 释放适配器
        if (adapter != null) {
            adapter = null;
        }
    }
}

八、总结与展望

8.1 总结

RecyclerView 作为 Android 开发中常用的组件,其性能优化对于提升应用的用户体验至关重要。通过对视图创建与绑定、缓存机制、数据处理、布局、动画与交互以及内存管理等方面的优化,可以显著提高 RecyclerView 的性能。

在视图创建与绑定方面,合理使用 ViewHolder 模式和优化布局文件可以减少视图创建开销,优化数据绑定逻辑可以提高绑定效率。缓存机制的优化可以通过理解 RecyclerView 的四级缓存,合理设置缓存大小,提高视图复用率。数据处理优化包括分页加载、异步加载数据以及使用更细粒度的更新方法和 DiffUtil 来优化数据更新。布局优化方面,选择合适的 LayoutManager、优化布局文件和避免过度绘制可以提高布局性能。动画与交互优化要求合理使用动画效果,优化交互响应。内存管理优化则需要避免内存泄漏、优化图片加载和及时释放资源。

8.2 展望

随着 Android 技术的不断发展,RecyclerView 的性能优化也将不断进步。未来可能会出现更智能的缓存机制,能够根据应用的使用场景和用户行为自动调整缓存策略,进一步提高视图复用率。在数据处理方面,可能会有更高效的分页加载和数据更新算法,减少数据加载和更新的时间。布局方面,可能会有更强大的布局管理器,能够更好地处理复杂的布局需求,同时保持高性能。动画与交互方面,会有更多的动画效果和交互方式,并且在性能上得到更好的平衡。内存管理方面,可能会有更智能的内存监测和优化工具,帮助开发者更轻松地避免内存泄漏和优化内存使用。总之,RecyclerView 的性能优化将持续发展,为 Android 应用的开发提供更好的支持。

相关推荐
企鹅侠客1 分钟前
简述删除一个Pod流程?
面试·kubernetes·pod·删除pod流程
_一条咸鱼_7 小时前
深度揭秘!Android HorizontalScrollView 使用原理全解析
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android RippleDrawable:深入解析使用原理
android·面试·android jetpack
_一条咸鱼_7 小时前
深入剖析:Android Snackbar 使用原理的源码级探秘
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android FloatingActionButton:从入门到源码深度剖析
android·面试·android jetpack
_一条咸鱼_7 小时前
深度剖析 Android SmartRefreshLayout:原理、源码与实战
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android GestureDetector:深入剖析使用原理
android·面试·android jetpack
_一条咸鱼_7 小时前
深入探秘 Android DrawerLayout:源码级使用原理剖析
android·面试·android jetpack
_一条咸鱼_7 小时前
深度揭秘:Android CardView 使用原理的源码级剖析
android·面试·android jetpack