前言
UML (Unified Modeling Language) 是统一建模语言,它的核心就是图表。使用它可以帮助我们开发者设计和理解系统,也方便向他人阐述系统。
接下来,我们以实战的方式来了解 Android 开发中常用的 UML 图,用到的工具是 StarUML。
类图
类图中的六种关系
类图描述了系统中的类(接口)以及它们之间的关系。
在类图中,有以下几种关系:
-
泛化(Generalization):是一种继承关系,使用带有空心三角箭头的实线指向父类。
-
实现(Realization):是类与接口的关系,使用带有空心三角箭头的虚线指向接口。
以
BaseActivity和IBaseView接口为例:
参考代码kotlininterface IBaseView { fun initView() fun initData() } abstract class BaseActivity : IBaseView { fun showToast(s: String) {} } class MainActivity : BaseActivity() { override fun initView() {} override fun initData() {} } -
依赖(Dependency):是临时使用的关系,一个类使用到了另一个类,使用带箭头的虚线指向被使用者。
以
Handler发送消息为例:
-
关联(Association):是一种拥有的关系,即一个类知道另一个类的属性和方法,使用带箭头的实线指向被拥有者。
注意:它和依赖很容易搞混,依赖强调的是临时使用(形式是方法参数 / 局部变量 / 静态方法调用),而关联强调长期持有(即作为类的成员持有引用)。
比如
Message内部会保存target(Handler),这就是关联关系。
这里的箭头是 Message 指向 Handler。此外,关联可以是双向的,使用双向箭头的实线连接。
-
聚合(Aggregation):是一种整体与部分的关系,部分可以单独存在,也是关联关系的一种。在整体的一端是空心菱形,实线连接部分。
-
组合(Composition):也是整体与部分的关系,但部分不能离开整体而单独存在,它也是关联关系的一种。在整体的一端是实心菱形,实线连接部分。
例如:Fragment 可以脱离 Activity 存在,Activtiy 销毁,Fragment 对象不一定立即被销毁;ViewGroup 销毁,子 View 没有依附,也会销毁,不能同时加到另一个父 ViewGroup 。
关键在于生命周期是否受整体的掌控。

结合 RecyclerView 实战解析
现在,我们以 RecyclerView 相关类为例,巩固一下前面学到的知识。
kotlin
data class ItemBean(val title: String)
class NewsSource {
private val items = mutableListOf<ItemBean>()
fun addItem(item: ItemBean) {
items.add(item)
}
fun getAll(): List<ItemBean> = items.toList()
}
interface IViewHolder<T> {
fun bind(data: T)
}
interface IAdapter<T> {
fun setData(list: List<T>)
}
class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), IViewHolder<ItemBean> {
val tvTitle: TextView = itemView.findViewById(android.R.id.text1)
override fun bind(data: ItemBean) {
tvTitle.text = data.title
}
}
class MyNewsAdapter(
private val newsSource: NewsSource?
) : RecyclerView.Adapter<NewsViewHolder>(), IAdapter<ItemBean> {
private val dataList: MutableList<ItemBean> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(android.R.layout.simple_list_item_1, parent, false)
return NewsViewHolder(view)
}
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
val item = dataList[position]
holder.bind(item)
}
override fun getItemCount(): Int = dataList.size
@SuppressLint("NotifyDataSetChanged")
override fun setData(list: List<ItemBean>) {
dataList.clear()
dataList.addAll(list)
notifyDataSetChanged()
}
fun refreshFromSource() {
newsSource?.let {
setData(it.getAll())
}
}
}
class MainActivity : AppCompatActivity() {
private val originData = List(3){
ItemBean("News $it")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val newsRecyclerView = findViewById<RecyclerView>(R.id.recycler_view)
newsRecyclerView.layoutManager = LinearLayoutManager(this)
val newsSource = NewsSource().apply {
repeat(10) {
addItem(ItemBean("News $it from NewsSource"))
}
}
val newsAdapter = MyNewsAdapter(newsSource)
newsAdapter.setData(originData)
newsRecyclerView.adapter = newsAdapter
lifecycleScope.launch {
delay(2000.milliseconds)
val newList = originData.toMutableList().apply {
add(ItemBean("News 4"))
add(ItemBean("News 5"))
}
newsAdapter.setData(newList)
delay(3000.milliseconds)
newsAdapter.refreshFromSource()
}
}
}
布局:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
关于泛化和实现,我就不说了,因为一眼看得出,无非就是继承和实现。
接着看依赖关系,NewsViewHolder 依赖了 ItemBean,因为它是 bind 方法的参数 ,如果它的结构改变,NewsViewHolder 也要跟着变。又比如 MainActivity onCreate 方法中的 NewsSource、MyNewsAdapter、ItemBean 局部变量,都是它所依赖的。
最后是最难辨别的关联关系,找很容易,成员变量就是了。其中 val tvTitle: TextView 是 NewsViewHolder 的成员变量,所以 NewsViewHolder 关联 TextView。
val title: String 是 ItemBean 的成员变量,所以 ItemBean 关联 String。val originData: List<ItemBean> 是 MainActivity 的成员变量,所以 MainActivity 关联 List<ItemBean>。
然后 MyNewsAdapter 中的 newsSource,也是关联关系,不过因其是从外部传入的,生命周期不受 MyNewsAdapter 的控制,所以属于特殊的聚合关系。
MyNewsAdapter 中的 dataList 也是关联关系,因为是内部直接创建的,当自己被清理时,这个实例也会死亡,所以是组合关系 。NewsSource 中的 items 也是组合关系。
怎么辨认 A 和 B 是否平等呢?(是普通还是特殊的关联关系?)
如果 A 可以知道 B,B 也可以知道 A,那么就属于平等关系,不存在谁包含谁。例如 NewsViewHolder 和 TextView,ViewHodler 只是借用 TextView 来保留了组件引用,方便自己更新组件文字,ViewHodler 不由 TextView 组成,TextView 也不归 NewsViewHolder 管理。
UML 图:

在标准的 UML 类图规范中,通常不会把基本数据类型和内置的基础类画出来,这会导致整张图变乱,失去展示核心业务和架构的意义。
像一些成员可以直接放到类框的属性栏里,也可以表达关系,同时可以保持图的整洁。
总结一下这六种关系的强弱关系:泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖,关系越强,耦合度往往越高。
序列图 (时序图)
它也被称为时序图,主要用于以时间顺序描述多个对象之间如何协作。
以 Activity 启动为例:当我们调用 startActivity() 时,底层其实经历了复杂的跨进程调用。以核心的对象为例,来了解交互顺序:
MainActivity发起启动请求。Instrumentation拦截请求,并通过 Binder 发送给系统服务ActivityManagerService(AMS)。- AMS 检查完权限和栈信息后,通知应用进程的
ApplicationThread。 ApplicationThread通过Handler切换到主线程,最终由ActivityThread真正执行新 Activity 的onCreate。

通常在阅读源码或业务开发中,想要知道某个操作经历了哪些步骤,回调是怎么传的,会画序列图来清晰地展示。
用例图
用例图根本不关心代码,只想知道"谁"在使用系统的"什么"功能,用来描述整个系统的功能,它是从用户视角出发的。
这里有几个核心概念:
- 参与者 (Actor):一个小人,代表着使用者。
- 用例 (Use Case):一个椭圆,代表一个可以使用的具体功能。
以新闻业务为例:
-
用户拥有"浏览新闻列表"、"查看新闻详情"这两个核心用例。
-
同时可以在详情页中点赞、收藏,"点赞新闻"、"收藏新闻"就是扩展用例(
<<extend>>)。 -
而无论是点赞还是收藏,都必须先完成登录,故"用户登录"就是一个被包含的公共用例(
<<include>>)。

活动图
我们之前画的流程图,其实在 UML 标准规范中对应的是活动图,描述了具体业务的实现流程。
比如加载新闻列表数据的逻辑:
- 进入新闻列表页面。
- 首先检查本地是否有缓存数据。
- 如果有缓存,直接渲染列表 UI。
- 如果没缓存,通过
NewsSource发起网络请求获取数据。 - 获取成功后保存到本地,同时汇合去渲染 UI。
- 流程结束。
可以用活动图画出来:
