
之后会慢慢补充一些菜鸡知识点.一、android兼容库Android 多年来一共出过 3 套兼容库v4 兼容库v7 兼容库AndroidX现在唯一官方推荐它们的作用让新版本控件如 RecyclerView、Toolbar能跑在老版本手机上。最早Android 版本碎片化太严重当年 Android 2.2、2.3、4.0、4.4、5.0 各种版本乱得一塌糊涂。谷歌新出的控件老手机跑不起来。于是谷歌做了v4 兼容库支持最低到 API 4Android 1.6里面全是老系统不支持的新功能包名android.support.v4.xxx后来v4 太老出了 v7 兼容库。Android 5.0 之后Android 1.6 几乎死光了。谷歌发布*之前V4做成了一个大包1MB左右从 2015 年开始谷歌把所有组件全部拆分成独立了于是RecyclerView 单独一个库ConstraintLayout 单独一个库CardView 单独一个库SwipeRefreshLayout 单独一个库Fragment 单独一个库等等……v7 兼容库最低支持 API 7Android 2.1包含更多新控件RecyclerView、CardView、Toolbar、Design 等包名android.support.v7.xxx最终v4 v7 太混乱谷歌彻底重构 → AndroidX谷歌2018 年直接宣布废弃所有 v4、v7 兼容库全部统一到 AndroidX老项目2018 年以前只能继续用 v7 / v4不能升级 AndroidX否则会炸开发最常用的库完整对照表 AppCompatActivity 旧项目:implementation com.android.support:appcompat-v7:28.0.0 AndroidX:implementation androidx.appcompat:appcompat:1.6.1 列表 RecyclerView 旧项目:implementation com.android.support:recyclerview-v7:28.0.0 AndroidX:implementation androidx.recyclerview:recyclerview:1.3.2 控件 旧项目:implementation com.android.support:design:28.0.0 AndroidX:implementation com.google.android.material:material:1.11.0 ConstraintLayout 旧项目:implementation com.android.support.constraint:constraint-layout:1.1.3 AndroidX:implementation androidx.constraintlayout:constraintlayout:2.1.4 图片加载Fragment、SwipeRefreshLayout 旧项目:implementation com.android.support:support-v4:28.0.0 AndroidX:// 已经内置在 androidx.appcompat 里不用单独加二RecyclerView 原理为什么要用 ViewHolder以前的 ListView 最烂的写法没有 ViewHolder每次滑动屏幕都会加载新的 item 布局inflate调用 findViewById 找控件设置数据滑动一次加载无数次布局,findViewById 非常慢.内存暴涨、卡顿、掉帧// 最烂写法没有 ViewHolder class MyAdapter(private val data: ListString) : BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { // 1. 每次都加载布局 val view LayoutInflater.from(parent.context) .inflate(R.layout.item_list, parent, false) // 2. 每次都 findViewById val tv view.findViewByIdTextView(R.id.tv_item) // 3. 设置数据 tv.text data[position] return view } override fun getItemCount() data.size override fun getItem(p: Int) data[p] override fun getItemId(p: Int) p.toLong() }inflate加载布局非常非常慢加载布局 读取 XML → 解析 → 创建控件对象 → 测量布局这是重量级操作非常耗时你滑一次做一次重量级操作 → 必然卡顿findViewById 也很慢它是遍历控件树查找时间复杂度 O (n)滑一次查一次 → 又慢一步不停创建新对象 → 内存抖动 GC 卡顿滑 100 次 创建 100 个 View 对象内存满了 → 系统触发 GC垃圾回收现在分析为什么ListVIew调用getView时convertView是否为空的问题伪代码模拟 ListView 源码工作// 这是 ListView 内部源码的逻辑你看不到但真实运行 class ListView{ // 内部缓存池你看不见 ArrayDequeView mScrapViews new ArrayDeque(); // 滑动时自动执行 void onScroll(){ // 1. 滑出去的View放进缓存 View outView 滑出屏幕的Item; mScrapViews.add(outView); // 2. 要显示新的Item int newPos 7; // 3. 从缓存拿旧View View convertView mScrapViews.poll(); // ← 这里来的 // 4. 调用你的 getView并把缓存的View传进去 adapter.getView(newPos, convertView, parent); } }滑出屏幕的 View → 进入 ListView 缓存池新条目要显示 → ListView 把缓存的旧 View 传给你 convertView所以第一次是 null滑动后永远不是 null所以结合Adapter的代码每次都会调用getView 从而初始化view。现在是有 ViewHolder 的 ListViewclass MyAdapter(private val data: ListString) : BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view: View val viewHolder: ViewHolder if (convertView null) { // ------------------------------ // 只执行 1 次 // ------------------------------ view LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false) viewHolder ViewHolder() viewHolder.tv view.findViewById(R.id.tv_item) view.tag viewHolder } else { // ------------------------------ // 复用不加载布局 // ------------------------------ view convertView viewHolder view.tag as ViewHolder } // 只做赋值超快 viewHolder.tv.text data[position] return view } class ViewHolder { lateinit var tv: TextView } }既然 convertView 已经是复用的布局了我直接给它设值不就行了ViewHolder 到底多干了啥convertView 负责复用布局ViewHolder 负责复用控件他俩是搭档少一个都不完美convertView 帮你省了「inflate 布局」ViewHolder 省了「findViewById」「只用 convertView、不用 ViewHolder」的代码// 只用 convertView不用 ViewHolder override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view: View if (convertView null) { // 只创建一次布局 ✅ view LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false) } else { // 复用布局 ✅ view convertView } // ❌❌❌ 致命问题每次滑动依然在不停 findViewById ❌❌❌ val tv view.findViewById(R.id.tv_item) tv.text data[position] return view }findViewById 不是轻量操作它是遍历控件树一层一层找你要的 id复杂度 O(n)你的 item 越复杂1 个 TextView → 找 1 次1 个图片 3 个文本 按钮 → 找 5 次滑动 100 次 → 找 500 次这就是卡顿的第二大元凶再看 ViewHolder 到底干了什么关键动作if (convertView null) { view inflate(...) viewHolder ViewHolder() viewHolder.tv view.findViewById(R.id.tv_item) // 只找一次 view.tag viewHolder // 把找到的控件绑在 View 身上永久保存 } else { view convertView viewHolder view.tag as ViewHolder // 直接拿缓存好的控件不用再找 } viewHolder.tv.text data[position] // 直接用核心秘密就在这一句view.tag viewHolderTag 是 View 自带的一个行李舱你第一次 findViewById 后把结果塞进 View 的行李舱里,以后复用这个 View 时,直接从行李舱拿现成的控件不再遍历查找结论convertView 的作用复用 View 布局避免重复 inflate加载 XMLViewHolder 的作用复用控件实例避免重复 findViewById遍历查找