为什么 Android 要把数据、视图和适配器分开?RecyclerView解读

文章目录

    • 一、我们到底在开发什么?
    • [二、Android 里的三个世界](#二、Android 里的三个世界)
      • [1. 数据层(Data)](#1. 数据层(Data))
      • [2. 视图层(View)](#2. 视图层(View))
      • [3. 适配层(Adapter)](#3. 适配层(Adapter))
    • 三、为什么不直接揉在一起?
    • [四、RecyclerView 到底做了什么?](#四、RecyclerView 到底做了什么?)
    • [五、MVC 思想的清晰体现](#五、MVC 思想的清晰体现)
    • [六、为什么 View 要复用?------ 一个与解耦一脉相承的设计选择](#六、为什么 View 要复用?—— 一个与解耦一脉相承的设计选择)
    • [七、现代 UI 框架的演进:解耦思想的延续](#七、现代 UI 框架的演进:解耦思想的延续)
    • 结尾:一张图总结一切

我们每天写列表、调 Adapter、填 ViewHolder,有没有想过:

为什么数据不能直接塞进界面?

为什么 Android 要刻意把 数据、视图和适配器 拆成三个世界?

这篇文章,试着从 RecyclerView 这个"最熟悉的陌生人"开始,聊一聊 Android UI 背后真正重要的设计思想。


一、我们到底在开发什么?

先看一段代码:

java 复制代码
Fruit fruit = new Fruit(
    "Apple",
    R.drawable.apple_pic
);

这个 fruit 对象,能直接显示到屏幕上吗?

不能。

那换一个写法:

java 复制代码
TextView tv = new TextView(this);

这个 TextView,就一定能显示 "Apple" 吗?

也不能。

这说明一个非常简单、但很多人会下意识忽略的事实:

数据 ≠ 界面

数据是数据,界面是界面。

你创建了一个 Fruit,只意味着内存里有了一段描述水果的数据,而它和屏幕之间,还隔着一条巨大的鸿沟。

同样,你 new 出来的 TextView,只是一个"空壳",它不知道你的水果叫什么名字,也不知道该显示哪张图片。

所以,我们每天真正在做的开发,其实不是"写界面",也不是"处理数据"------

而是在维护"数据状态"与"界面表现"之间的映射关系。


二、Android 里的三个世界

为了系统地解决这个映射问题,Android 把 UI 架构拆成了三层:
#mermaid-svg-o2SMAzjeBugAOZz8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o2SMAzjeBugAOZz8 .error-icon{fill:#552222;}#mermaid-svg-o2SMAzjeBugAOZz8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o2SMAzjeBugAOZz8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o2SMAzjeBugAOZz8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o2SMAzjeBugAOZz8 .marker.cross{stroke:#333333;}#mermaid-svg-o2SMAzjeBugAOZz8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o2SMAzjeBugAOZz8 p{margin:0;}#mermaid-svg-o2SMAzjeBugAOZz8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster-label text{fill:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster-label span{color:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster-label span p{background-color:transparent;}#mermaid-svg-o2SMAzjeBugAOZz8 .label text,#mermaid-svg-o2SMAzjeBugAOZz8 span{fill:#333;color:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 .node rect,#mermaid-svg-o2SMAzjeBugAOZz8 .node circle,#mermaid-svg-o2SMAzjeBugAOZz8 .node ellipse,#mermaid-svg-o2SMAzjeBugAOZz8 .node polygon,#mermaid-svg-o2SMAzjeBugAOZz8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o2SMAzjeBugAOZz8 .rough-node .label text,#mermaid-svg-o2SMAzjeBugAOZz8 .node .label text,#mermaid-svg-o2SMAzjeBugAOZz8 .image-shape .label,#mermaid-svg-o2SMAzjeBugAOZz8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-o2SMAzjeBugAOZz8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o2SMAzjeBugAOZz8 .rough-node .label,#mermaid-svg-o2SMAzjeBugAOZz8 .node .label,#mermaid-svg-o2SMAzjeBugAOZz8 .image-shape .label,#mermaid-svg-o2SMAzjeBugAOZz8 .icon-shape .label{text-align:center;}#mermaid-svg-o2SMAzjeBugAOZz8 .node.clickable{cursor:pointer;}#mermaid-svg-o2SMAzjeBugAOZz8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o2SMAzjeBugAOZz8 .arrowheadPath{fill:#333333;}#mermaid-svg-o2SMAzjeBugAOZz8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o2SMAzjeBugAOZz8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o2SMAzjeBugAOZz8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o2SMAzjeBugAOZz8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o2SMAzjeBugAOZz8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o2SMAzjeBugAOZz8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster text{fill:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 .cluster span{color:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o2SMAzjeBugAOZz8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o2SMAzjeBugAOZz8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-o2SMAzjeBugAOZz8 .icon-shape,#mermaid-svg-o2SMAzjeBugAOZz8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o2SMAzjeBugAOZz8 .icon-shape p,#mermaid-svg-o2SMAzjeBugAOZz8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o2SMAzjeBugAOZz8 .icon-shape .label rect,#mermaid-svg-o2SMAzjeBugAOZz8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o2SMAzjeBugAOZz8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o2SMAzjeBugAOZz8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o2SMAzjeBugAOZz8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据
翻译
数据层 Data

Fruit / User / Message
适配层 Adapter

FruitAdapter
视图层 View

TextView / ImageView / RecyclerView

1. 数据层(Data)

这一层只负责一件事:保存状态

比如一个 Fruit 类:

java 复制代码
class Fruit {
    String name;
    int imageId;
}

它只知道自己的名字和图片资源 ID。

它不知道 TextView 是什么,不知道 RecyclerView 是什么,甚至不知道自己最终会被显示成方形还是圆形。

数据层是全宇宙最"纯粹"的地方------它不依赖 Android,不依赖 UI,只依赖业务。

2. 视图层(View)

这一层也只负责一件事:显示

TextView 负责显示文字,ImageView 负责显示图片,Button 负责让用户点击。

它们提供了 setText()setImageResource() 这样的接口,但从不关心传入的字符串是从 Fruit 来的,还是从网络 JSON 里解析出来的。

视图层不知道数据的存在,只知道"给我内容,我就渲染"。

3. 适配层(Adapter)

这一层是翻译官

它知道 Fruit 里有什么字段,也知道 TextView 需要什么字符串。

于是它把数据"翻译"成视图能懂的指令:

java 复制代码
holder.fruitName.setText(fruit.getName());
holder.fruitImage.setImageResource(fruit.getImageId());

适配层把数据世界和视图世界连接在了一起,同时又不让它们互相依赖。


三、为什么不直接揉在一起?

很多人初学时可能会想:何必这么麻烦,直接把 TextView 写进 Fruit 里不就行了?

java 复制代码
// ❌ 错误设计
class Fruit {
    String name;
    int imageId;
    TextView textView;  // 直接持有视图
}

这看起来省了一个 Adapter,实际上带来了灾难:

  • Fruit 强依赖 Android
    一旦脱离 Android 环境(比如在后台解析 JSON),TextView 就完全无用,甚至会导致代码无法运行。
  • 无法独立测试
    想对 Fruit 做单元测试,还得 mock 一个 TextView,荒谬。
  • 无法复用
    同一个 Fruit 对象既不能用于网络传输,也不能存数据库,因为它拖着一个沉重的视图包袱。
  • 数据无法脱离界面存在
    所有数据逻辑都和 UI 生命周期绑定,内存泄漏、状态混乱将是家常便饭。

而标准设计里:

java 复制代码
// ✅ 正确设计
class Fruit {
    String name;
    int imageId;
}

这个 Fruit 可以做什么?

  • 存到数据库
  • 转成 JSON 发送到服务器
  • 在单元测试里验证逻辑
  • 在 RecyclerView 里显示
  • 甚至在 Compose 或 Flutter 里复用

因为数据只包含状态,不包含任何 UI 细节。

这就是软件工程里最重要的两个词:

高内聚,低耦合

数据负责自己的状态(高内聚),适配器负责连接(低耦合),视图负责渲染(高内聚)。

三者各司其职,任何一个改变都不会引起雪崩式的连锁反应。


四、RecyclerView 到底做了什么?

RecyclerView 是这个三层架构的绝佳例证。

一个典型流程如下:
#mermaid-svg-gFTjLw2dLCLchWnm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gFTjLw2dLCLchWnm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gFTjLw2dLCLchWnm .error-icon{fill:#552222;}#mermaid-svg-gFTjLw2dLCLchWnm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gFTjLw2dLCLchWnm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gFTjLw2dLCLchWnm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gFTjLw2dLCLchWnm .marker.cross{stroke:#333333;}#mermaid-svg-gFTjLw2dLCLchWnm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gFTjLw2dLCLchWnm p{margin:0;}#mermaid-svg-gFTjLw2dLCLchWnm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gFTjLw2dLCLchWnm .cluster-label text{fill:#333;}#mermaid-svg-gFTjLw2dLCLchWnm .cluster-label span{color:#333;}#mermaid-svg-gFTjLw2dLCLchWnm .cluster-label span p{background-color:transparent;}#mermaid-svg-gFTjLw2dLCLchWnm .label text,#mermaid-svg-gFTjLw2dLCLchWnm span{fill:#333;color:#333;}#mermaid-svg-gFTjLw2dLCLchWnm .node rect,#mermaid-svg-gFTjLw2dLCLchWnm .node circle,#mermaid-svg-gFTjLw2dLCLchWnm .node ellipse,#mermaid-svg-gFTjLw2dLCLchWnm .node polygon,#mermaid-svg-gFTjLw2dLCLchWnm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gFTjLw2dLCLchWnm .rough-node .label text,#mermaid-svg-gFTjLw2dLCLchWnm .node .label text,#mermaid-svg-gFTjLw2dLCLchWnm .image-shape .label,#mermaid-svg-gFTjLw2dLCLchWnm .icon-shape .label{text-anchor:middle;}#mermaid-svg-gFTjLw2dLCLchWnm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gFTjLw2dLCLchWnm .rough-node .label,#mermaid-svg-gFTjLw2dLCLchWnm .node .label,#mermaid-svg-gFTjLw2dLCLchWnm .image-shape .label,#mermaid-svg-gFTjLw2dLCLchWnm .icon-shape .label{text-align:center;}#mermaid-svg-gFTjLw2dLCLchWnm .node.clickable{cursor:pointer;}#mermaid-svg-gFTjLw2dLCLchWnm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gFTjLw2dLCLchWnm .arrowheadPath{fill:#333333;}#mermaid-svg-gFTjLw2dLCLchWnm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gFTjLw2dLCLchWnm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gFTjLw2dLCLchWnm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gFTjLw2dLCLchWnm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gFTjLw2dLCLchWnm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gFTjLw2dLCLchWnm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gFTjLw2dLCLchWnm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gFTjLw2dLCLchWnm .cluster text{fill:#333;}#mermaid-svg-gFTjLw2dLCLchWnm .cluster span{color:#333;}#mermaid-svg-gFTjLw2dLCLchWnm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gFTjLw2dLCLchWnm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gFTjLw2dLCLchWnm rect.text{fill:none;stroke-width:0;}#mermaid-svg-gFTjLw2dLCLchWnm .icon-shape,#mermaid-svg-gFTjLw2dLCLchWnm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gFTjLw2dLCLchWnm .icon-shape p,#mermaid-svg-gFTjLw2dLCLchWnm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gFTjLw2dLCLchWnm .icon-shape .label rect,#mermaid-svg-gFTjLw2dLCLchWnm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gFTjLw2dLCLchWnm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gFTjLw2dLCLchWnm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gFTjLw2dLCLchWnm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Fruit 数据
Adapter
ViewHolder 缓存
View 界面对象
用户看到

  • Fruit:纯数据,只包含 name、imageId。
  • View :真正的界面对象,比如 TextViewImageView,占用内存大,创建成本高。
  • ViewHolder缓存 View 的引用 ,避免每次 findViewById,但它本身不是界面,只是一个普通 Java 类。
  • Adapter :建立映射,把 fruit.getName() 翻译成 holder.fruitName.setText(...)

因此,ViewHolder 不是界面,View 才是界面

这个区分非常重要,它让 RecyclerView 可以在性能上做极致的优化------复用。


五、MVC 思想的清晰体现

RecyclerView 的设计不是拍脑袋想出来的,它是 MVC 架构 在 Android 世界里的一次优雅落地。
#mermaid-svg-W1WxdY7WOLSHRKhi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W1WxdY7WOLSHRKhi .error-icon{fill:#552222;}#mermaid-svg-W1WxdY7WOLSHRKhi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W1WxdY7WOLSHRKhi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W1WxdY7WOLSHRKhi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W1WxdY7WOLSHRKhi .marker.cross{stroke:#333333;}#mermaid-svg-W1WxdY7WOLSHRKhi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W1WxdY7WOLSHRKhi p{margin:0;}#mermaid-svg-W1WxdY7WOLSHRKhi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster-label text{fill:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster-label span{color:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster-label span p{background-color:transparent;}#mermaid-svg-W1WxdY7WOLSHRKhi .label text,#mermaid-svg-W1WxdY7WOLSHRKhi span{fill:#333;color:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi .node rect,#mermaid-svg-W1WxdY7WOLSHRKhi .node circle,#mermaid-svg-W1WxdY7WOLSHRKhi .node ellipse,#mermaid-svg-W1WxdY7WOLSHRKhi .node polygon,#mermaid-svg-W1WxdY7WOLSHRKhi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W1WxdY7WOLSHRKhi .rough-node .label text,#mermaid-svg-W1WxdY7WOLSHRKhi .node .label text,#mermaid-svg-W1WxdY7WOLSHRKhi .image-shape .label,#mermaid-svg-W1WxdY7WOLSHRKhi .icon-shape .label{text-anchor:middle;}#mermaid-svg-W1WxdY7WOLSHRKhi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W1WxdY7WOLSHRKhi .rough-node .label,#mermaid-svg-W1WxdY7WOLSHRKhi .node .label,#mermaid-svg-W1WxdY7WOLSHRKhi .image-shape .label,#mermaid-svg-W1WxdY7WOLSHRKhi .icon-shape .label{text-align:center;}#mermaid-svg-W1WxdY7WOLSHRKhi .node.clickable{cursor:pointer;}#mermaid-svg-W1WxdY7WOLSHRKhi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W1WxdY7WOLSHRKhi .arrowheadPath{fill:#333333;}#mermaid-svg-W1WxdY7WOLSHRKhi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W1WxdY7WOLSHRKhi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W1WxdY7WOLSHRKhi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1WxdY7WOLSHRKhi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W1WxdY7WOLSHRKhi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1WxdY7WOLSHRKhi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster text{fill:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi .cluster span{color:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W1WxdY7WOLSHRKhi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W1WxdY7WOLSHRKhi rect.text{fill:none;stroke-width:0;}#mermaid-svg-W1WxdY7WOLSHRKhi .icon-shape,#mermaid-svg-W1WxdY7WOLSHRKhi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1WxdY7WOLSHRKhi .icon-shape p,#mermaid-svg-W1WxdY7WOLSHRKhi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W1WxdY7WOLSHRKhi .icon-shape .label rect,#mermaid-svg-W1WxdY7WOLSHRKhi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1WxdY7WOLSHRKhi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W1WxdY7WOLSHRKhi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W1WxdY7WOLSHRKhi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Model

Fruit 数据
View

TextView / ImageView
Controller

Adapter / Activity

  • Model(数据):Fruit 类,只包含状态。
  • View(视图) :item 布局里的 TextViewImageView,只负责渲染。
  • Controller(控制器):Adapter 承担核心控制逻辑------决定数据如何绑定到视图,以及何时更新;Activity/Fragment 负责更高层的页面控制和生命周期管理。

很多人以为 MVC 里,Controller 是 Activity,View 是 XML。

实际上在 RecyclerView 这一层,Adapter 才是真正的"C"

它接受数据(Model),操作视图(View),但自己不持有状态,也不负责显示。

理解这一点,就能理解为什么 Adapter 是 RecyclerView 的灵魂,也能理解为什么 解耦的本质是让每一层只做自己该做的事。


六、为什么 View 要复用?------ 一个与解耦一脉相承的设计选择

假设现在有 10000 条水果数据。

问:需要创建 10000 个 TextViewImageView 吗?

答案:完全不需要。

因为用户同时只能看到十几个 item。

如果真的创建 10000 个 View,内存会直接爆炸,滑动也会卡成 PPT。

所以,RecyclerView 名字里就藏了答案:

Recycle + View = 回收并复用 View
#mermaid-svg-y0w3kqxQQHd909ty{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-y0w3kqxQQHd909ty .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-y0w3kqxQQHd909ty .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-y0w3kqxQQHd909ty .error-icon{fill:#552222;}#mermaid-svg-y0w3kqxQQHd909ty .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-y0w3kqxQQHd909ty .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-y0w3kqxQQHd909ty .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-y0w3kqxQQHd909ty .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-y0w3kqxQQHd909ty .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-y0w3kqxQQHd909ty .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-y0w3kqxQQHd909ty .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-y0w3kqxQQHd909ty .marker{fill:#333333;stroke:#333333;}#mermaid-svg-y0w3kqxQQHd909ty .marker.cross{stroke:#333333;}#mermaid-svg-y0w3kqxQQHd909ty svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-y0w3kqxQQHd909ty p{margin:0;}#mermaid-svg-y0w3kqxQQHd909ty .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-y0w3kqxQQHd909ty .cluster-label text{fill:#333;}#mermaid-svg-y0w3kqxQQHd909ty .cluster-label span{color:#333;}#mermaid-svg-y0w3kqxQQHd909ty .cluster-label span p{background-color:transparent;}#mermaid-svg-y0w3kqxQQHd909ty .label text,#mermaid-svg-y0w3kqxQQHd909ty span{fill:#333;color:#333;}#mermaid-svg-y0w3kqxQQHd909ty .node rect,#mermaid-svg-y0w3kqxQQHd909ty .node circle,#mermaid-svg-y0w3kqxQQHd909ty .node ellipse,#mermaid-svg-y0w3kqxQQHd909ty .node polygon,#mermaid-svg-y0w3kqxQQHd909ty .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-y0w3kqxQQHd909ty .rough-node .label text,#mermaid-svg-y0w3kqxQQHd909ty .node .label text,#mermaid-svg-y0w3kqxQQHd909ty .image-shape .label,#mermaid-svg-y0w3kqxQQHd909ty .icon-shape .label{text-anchor:middle;}#mermaid-svg-y0w3kqxQQHd909ty .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-y0w3kqxQQHd909ty .rough-node .label,#mermaid-svg-y0w3kqxQQHd909ty .node .label,#mermaid-svg-y0w3kqxQQHd909ty .image-shape .label,#mermaid-svg-y0w3kqxQQHd909ty .icon-shape .label{text-align:center;}#mermaid-svg-y0w3kqxQQHd909ty .node.clickable{cursor:pointer;}#mermaid-svg-y0w3kqxQQHd909ty .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-y0w3kqxQQHd909ty .arrowheadPath{fill:#333333;}#mermaid-svg-y0w3kqxQQHd909ty .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-y0w3kqxQQHd909ty .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-y0w3kqxQQHd909ty .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y0w3kqxQQHd909ty .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-y0w3kqxQQHd909ty .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y0w3kqxQQHd909ty .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-y0w3kqxQQHd909ty .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-y0w3kqxQQHd909ty .cluster text{fill:#333;}#mermaid-svg-y0w3kqxQQHd909ty .cluster span{color:#333;}#mermaid-svg-y0w3kqxQQHd909ty div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-y0w3kqxQQHd909ty .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-y0w3kqxQQHd909ty rect.text{fill:none;stroke-width:0;}#mermaid-svg-y0w3kqxQQHd909ty .icon-shape,#mermaid-svg-y0w3kqxQQHd909ty .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y0w3kqxQQHd909ty .icon-shape p,#mermaid-svg-y0w3kqxQQHd909ty .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-y0w3kqxQQHd909ty .icon-shape .label rect,#mermaid-svg-y0w3kqxQQHd909ty .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y0w3kqxQQHd909ty .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-y0w3kqxQQHd909ty .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-y0w3kqxQQHd909ty :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 旧 View 滚出屏幕
回收至缓存池
重新绑定新数据
再次显示为新 item

这就是 ViewHolder 机制的底层逻辑:

ViewHolder 把 View 引用暂存起来,当新数据需要显示时,直接复用旧 View,只替换里面的文字、图片等数据。

这种"复用"思想之所以能成立,恰恰是因为数据和视图是分离的

如果数据里直接持有 View,那每个数据就不得不持有一份独立的 View,永远无法复用。

所以,解耦不仅是为了代码漂亮,更是为了性能上获得质的飞跃。


七、现代 UI 框架的演进:解耦思想的延续

RecyclerView 已经是 Android 里解耦思想的标志性实现。这里的关键词是 "手动"

Controller 必须明确知道:

  • 什么时候 数据变了(网络请求完成、用户输入......)
  • 怎么更新 界面(调哪个 Adapter 方法、刷新哪部分 View)

这就是 命令式编程:一步一步告诉框架"先去把数据换了,再去刷新列表"。

在小项目里这没问题。但一旦界面逻辑复杂起来:

  • 多个数据源(本地数据库、网络、用户操作)都可能改变同一个界面
  • 数据之间存在依赖(购物车总价 = 每个商品价格 × 数量)
  • 界面状态有加载中、空数据、错误等多种情况

手动维护这些 "何时更新、更新什么" 会迅速变成一场噩梦。忘记调用一次

notifyDataSetChanged(),界面就"卡"在了旧状态上。

但它不是终点。

现代 UI 架构开始走向 响应式
#mermaid-svg-7xWLZNYHRCr8hAPT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7xWLZNYHRCr8hAPT .error-icon{fill:#552222;}#mermaid-svg-7xWLZNYHRCr8hAPT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7xWLZNYHRCr8hAPT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7xWLZNYHRCr8hAPT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7xWLZNYHRCr8hAPT .marker.cross{stroke:#333333;}#mermaid-svg-7xWLZNYHRCr8hAPT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7xWLZNYHRCr8hAPT p{margin:0;}#mermaid-svg-7xWLZNYHRCr8hAPT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster-label text{fill:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster-label span{color:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster-label span p{background-color:transparent;}#mermaid-svg-7xWLZNYHRCr8hAPT .label text,#mermaid-svg-7xWLZNYHRCr8hAPT span{fill:#333;color:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT .node rect,#mermaid-svg-7xWLZNYHRCr8hAPT .node circle,#mermaid-svg-7xWLZNYHRCr8hAPT .node ellipse,#mermaid-svg-7xWLZNYHRCr8hAPT .node polygon,#mermaid-svg-7xWLZNYHRCr8hAPT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7xWLZNYHRCr8hAPT .rough-node .label text,#mermaid-svg-7xWLZNYHRCr8hAPT .node .label text,#mermaid-svg-7xWLZNYHRCr8hAPT .image-shape .label,#mermaid-svg-7xWLZNYHRCr8hAPT .icon-shape .label{text-anchor:middle;}#mermaid-svg-7xWLZNYHRCr8hAPT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7xWLZNYHRCr8hAPT .rough-node .label,#mermaid-svg-7xWLZNYHRCr8hAPT .node .label,#mermaid-svg-7xWLZNYHRCr8hAPT .image-shape .label,#mermaid-svg-7xWLZNYHRCr8hAPT .icon-shape .label{text-align:center;}#mermaid-svg-7xWLZNYHRCr8hAPT .node.clickable{cursor:pointer;}#mermaid-svg-7xWLZNYHRCr8hAPT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7xWLZNYHRCr8hAPT .arrowheadPath{fill:#333333;}#mermaid-svg-7xWLZNYHRCr8hAPT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7xWLZNYHRCr8hAPT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7xWLZNYHRCr8hAPT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7xWLZNYHRCr8hAPT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7xWLZNYHRCr8hAPT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7xWLZNYHRCr8hAPT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster text{fill:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT .cluster span{color:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7xWLZNYHRCr8hAPT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7xWLZNYHRCr8hAPT rect.text{fill:none;stroke-width:0;}#mermaid-svg-7xWLZNYHRCr8hAPT .icon-shape,#mermaid-svg-7xWLZNYHRCr8hAPT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7xWLZNYHRCr8hAPT .icon-shape p,#mermaid-svg-7xWLZNYHRCr8hAPT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7xWLZNYHRCr8hAPT .icon-shape .label rect,#mermaid-svg-7xWLZNYHRCr8hAPT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7xWLZNYHRCr8hAPT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7xWLZNYHRCr8hAPT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7xWLZNYHRCr8hAPT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据 / State
声明式 UI
自动刷新界面

比如:

  • LiveData / StateFlow:当数据变化时,自动通知 UI 更新。
  • Jetpack Compose:彻底用 Kotlin 代码写 UI,界面由状态驱动。

响应式的核心思想就一句话:

界面是状态的函数

UI = f(State)

用伪代码表示:

kotlin 复制代码
// 状态
val fruits: State<List<Fruit>>

// 界面自动由状态驱动
Text(fruits[0].name)
Image(fruits[0].image)

fruits 这个状态发生变化时,引用它的 UI 自动刷新。你不需要再写"数据变了,请刷新那个 TextView"的指令。

在 Android 里:

  • LiveData / StateFlow 负责持有可观察的状态
  • Jetpack Compose 直接让 UI 成为 Kotlin 代码,能响应状态重组

viewModel.fruits 的值发生变化(比如从网络加载完成),FruitList 会自动重组,无需手动通知任何 Adapter

  • 但本质上,这仍然是:

数据 → 映射 → 界面

只不过映射关系从"命令式"变成了"声明式",由框架自动处理。

这说明:当解耦做到底层,无论上层 API 怎么变,架构思想都能平滑过渡。


结尾:一张图总结一切

#mermaid-svg-8RKKhmvooTNIuCnE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8RKKhmvooTNIuCnE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8RKKhmvooTNIuCnE .error-icon{fill:#552222;}#mermaid-svg-8RKKhmvooTNIuCnE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8RKKhmvooTNIuCnE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8RKKhmvooTNIuCnE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8RKKhmvooTNIuCnE .marker.cross{stroke:#333333;}#mermaid-svg-8RKKhmvooTNIuCnE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8RKKhmvooTNIuCnE p{margin:0;}#mermaid-svg-8RKKhmvooTNIuCnE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8RKKhmvooTNIuCnE .cluster-label text{fill:#333;}#mermaid-svg-8RKKhmvooTNIuCnE .cluster-label span{color:#333;}#mermaid-svg-8RKKhmvooTNIuCnE .cluster-label span p{background-color:transparent;}#mermaid-svg-8RKKhmvooTNIuCnE .label text,#mermaid-svg-8RKKhmvooTNIuCnE span{fill:#333;color:#333;}#mermaid-svg-8RKKhmvooTNIuCnE .node rect,#mermaid-svg-8RKKhmvooTNIuCnE .node circle,#mermaid-svg-8RKKhmvooTNIuCnE .node ellipse,#mermaid-svg-8RKKhmvooTNIuCnE .node polygon,#mermaid-svg-8RKKhmvooTNIuCnE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8RKKhmvooTNIuCnE .rough-node .label text,#mermaid-svg-8RKKhmvooTNIuCnE .node .label text,#mermaid-svg-8RKKhmvooTNIuCnE .image-shape .label,#mermaid-svg-8RKKhmvooTNIuCnE .icon-shape .label{text-anchor:middle;}#mermaid-svg-8RKKhmvooTNIuCnE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8RKKhmvooTNIuCnE .rough-node .label,#mermaid-svg-8RKKhmvooTNIuCnE .node .label,#mermaid-svg-8RKKhmvooTNIuCnE .image-shape .label,#mermaid-svg-8RKKhmvooTNIuCnE .icon-shape .label{text-align:center;}#mermaid-svg-8RKKhmvooTNIuCnE .node.clickable{cursor:pointer;}#mermaid-svg-8RKKhmvooTNIuCnE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8RKKhmvooTNIuCnE .arrowheadPath{fill:#333333;}#mermaid-svg-8RKKhmvooTNIuCnE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8RKKhmvooTNIuCnE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8RKKhmvooTNIuCnE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8RKKhmvooTNIuCnE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8RKKhmvooTNIuCnE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8RKKhmvooTNIuCnE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8RKKhmvooTNIuCnE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8RKKhmvooTNIuCnE .cluster text{fill:#333;}#mermaid-svg-8RKKhmvooTNIuCnE .cluster span{color:#333;}#mermaid-svg-8RKKhmvooTNIuCnE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8RKKhmvooTNIuCnE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8RKKhmvooTNIuCnE rect.text{fill:none;stroke-width:0;}#mermaid-svg-8RKKhmvooTNIuCnE .icon-shape,#mermaid-svg-8RKKhmvooTNIuCnE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8RKKhmvooTNIuCnE .icon-shape p,#mermaid-svg-8RKKhmvooTNIuCnE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8RKKhmvooTNIuCnE .icon-shape .label rect,#mermaid-svg-8RKKhmvooTNIuCnE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8RKKhmvooTNIuCnE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8RKKhmvooTNIuCnE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8RKKhmvooTNIuCnE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据 Data

Fruit / User / Message
适配 Adapter

RecyclerView.Adapter
视图 View

TextView / ImageView / Button
RecyclerView

负责管理和复用
Activity

负责承载界面

Android 开发表面上是在写按钮、列表和页面,

实际上,你一直在维护"数据状态"与"界面表现"的映射关系。

RecyclerView 只是这个思想最经典、最容易观察到的例子。

一旦你真正理解这一点,无论是写自定义 View、设计 App 架构,还是迁移到 Compose 等新框架,

你会发现自己一直在一个稳固的根基上前进。


本文定位为设计思想分享,不深入源码细节,力求让每一位 Android 开发者都能透过现象看到架构的本质。

如果你也曾在一堆 Adapter 代码里感到迷茫,希望这篇文章能帮你看清方向。

相关推荐
三少爷的鞋2 小时前
别再让业务层裸奔 CarPropertyManager 了!谈谈汽车车载核心服务的架构封装
android
-SOLO-14 小时前
备份apk 工具
android
私人珍藏库18 小时前
【Android】BotHub-多模型AI机器人聚合库-内置免费模型
android·人工智能·智能手机·app·工具·多功能
普马萨特19 小时前
Wi-Fi 扫描频率限制与 Android 演进全解析
android
张拭心19 小时前
Android 17 新特性:后台音频交互限制加强
android·前端
张拭心19 小时前
Android 17 新特性:ProfilingManager 新触发器
android·前端
张拭心20 小时前
Android 17 新特性:MessageQueue 无锁实现
android·前端
brycegao20 小时前
如何搭建标准化 Git 工具流,保障 Android 团队代码质量
android·ci/cd
AI科技星20 小时前
数术江湖·全卷合集 - 硬核江湖・数理史诗
android·人工智能·架构·概率论·学习方法