0%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
在安卓开发中,内存管理是应用稳定性和性能的关键。了解安卓内存结构、垃圾回收机制和内存优化技巧,可以帮助开发者写出高效、低耗的应用,避免 ANR(应用无响应)和 OOM(内存溢出)问题。

1️⃣ 安卓内存结构概览

安卓应用运行在 Android Runtime (ART) 上,每个应用进程独立,内存主要分为以下几部分:

内存区域 描述
Dalvik/ART 堆(Heap) 存储应用对象,受垃圾回收管理
栈(Stack) 存储方法调用、局部变量,随线程创建和销毁
Native 内存 C/C++ 层分配的内存,如 Bitmap、OpenGL 资源
共享库/内存映射 加载系统库和 APK 资源
内存映射文件(Mapped Files) APK、dex、so 文件映射内存

💡 小技巧:使用 Android Studio Profiler → Memory 可以实时查看各区域内存占用情况。

2️⃣ 垃圾回收机制(GC)

安卓内存管理的核心是 ART 的垃圾回收:

2.1 垃圾回收策略

ART 主要使用 分代 + 并发 GC:

年轻代(Young Generation):

存储短生命周期对象

使用 Copying GC,清理效率高

老年代(Old Generation):

存储长生命周期对象

使用 Mark-Sweep-Compact GC,减少碎片

大对象(如大 Bitmap):

直接分配在老年代或 Native 内存

2.2 GC 类型
类型 特点 适用场景
Serial GC 单线程,简单 小内存设备
Parallel GC 多线程 多核设备
Concurrent GC 并发执行,减少停顿 UI 主线程敏感应用
3️⃣ 内存泄漏与分析
3.1 内存泄漏常见场景

Activity/Fragment 持有 Context 导致无法回收

Handler/Thread 持有外部引用

静态集合存储对象

Bitmap 或 Drawable 未回收

3.2 内存泄漏工具

LeakCanary:实时检测内存泄漏

Memory Profiler:可视化对象分配、堆快照

MAT (Memory Analyzer Tool):分析复杂堆内存

示例使用 LeakCanary:

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) return
LeakCanary.install(this)
}
}


当 Activity 或对象未被回收时,LeakCanary 会弹出警告。

4️⃣ 内存优化实战
4.1 避免内存泄漏

使用 弱引用(WeakReference) 或 软引用(SoftReference)

避免在静态变量中持有 Context

Activity/Fragment 使用 ViewBinding 时注意解绑

4.2 控制对象创建

重复使用对象池(如 BitmapPool)

减少临时对象创建,尤其在 RecyclerView/循环里

4.3 图片/资源优化

使用 BitmapFactory.Options.inSampleSize 降低内存占用

Glide 或 Coil 自动处理缓存和复用

4.4 内存监控
val runtime = Runtime.getRuntime()
Log.d("Memory", "Max: ${runtime.maxMemory()/1024/1024}MB")
Log.d("Memory", "Total: ${runtime.totalMemory()/1024/1024}MB")
Log.d("Memory", "Free: ${runtime.freeMemory()/1024/1024}MB")


maxMemory:应用可用最大堆

totalMemory:已申请堆

freeMemory:堆中可用空间

5️⃣ 案例:Bitmap 内存优化
// 原始加载大图,容易 OOM
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image)

// 优化:采样缩放
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(resources, R.drawable.large_image, options)
options.inSampleSize = 4 // 缩小 4 倍
options.inJustDecodeBounds = false
val optimizedBitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image, options)

6️⃣ 总结

安卓内存管理核心点:

了解内存结构:Heap、Stack、Native 内存

掌握 GC 原理:年轻代、老年代、并发回收

避免内存泄漏:Context、Handler、静态引用注意

优化对象创建:复用、对象池、图片压缩

实时监控:Profiler、LeakCanary

内存优化不仅提升性能,还能提升用户体验,降低崩溃率。掌握这些原理,你的应用会更稳定、更流畅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Kotlin 协程与 Room 数据库异步操作:suspend 与 Flow 全解析

在 Android 开发中,Room 数据库操作不能在主线程执行耗时操作,否则会抛出 android.os.NetworkOnMainThreadException 或 ANR。Kotlin 协程提供了非常方便的方式来处理异步操作,suspend 与 Flow 是其中最常用的两种模式。

1. suspend:异步函数(一次性操作)

suspend 表示挂起函数,它本身不会创建新线程,但可以挂起当前协程,让协程切换到其他线程去执行耗时操作,然后恢复。

用法示例
@Dao
interface GasProjectDao {

@Insert
suspend fun insertGasProject(gasProject: GasProject)

@Query("SELECT * FROM GasProject WHERE id = :id")
suspend fun getGasProjectById(id: Int): GasProject?
}

调用方式
lifecycleScope.launch {
val project = GasProject(name = "测试项目")
gasProjectDao.insertGasProject(project) // 挂起函数在协程中调用

val savedProject = gasProjectDao.getGasProjectById(project.id)
textView.text = "项目名称: ${savedProject?.name}"
}


✅ 特点:

执行一次性操作(插入、查询单条数据)

挂起函数必须在协程中调用

可以和 Dispatchers.IO 配合,实现真正的后台线程执行

2. Flow:异步版可观察列表(持续更新)

Flow 是 Kotlin 协程提供的响应式流,用于表示数据的异步流,类似 RxJava 的 Observable。Room 与 Flow 结合,可以实现数据库表数据变化自动推送到 UI。

用法示例
@Dao
interface GasProjectDao {

@Query("SELECT * FROM GasProject")
fun getAllGasProject(): Flow<List<GasProject>>
}

调用方式
lifecycleScope.launch {
gasProjectDao.getAllGasProject()
.collect { projectList ->
// 每次数据库数据变化,这里都会收到最新列表
recyclerViewAdapter.submitList(projectList)
}
}


✅ 特点:

可观察数据库表的变化(自动推送 UI)

持续订阅,多次更新

可以和协程调度器一起使用,比如切换到 Dispatchers.Main 更新 UI

3. suspend 与 Flow 的区别
特性 suspend Flow
调用频率 一次操作 持续订阅,数据变化多次
线程 可挂起在协程中切换线程 也可挂起,支持调度线程切换
用途 插入、单条查询、一次性更新 列表查询、UI 自动刷新、响应式数据
返回值 具体类型 Flow<T> 流对象,需要 collect
示例 suspend fun insert(...) fun getAll(): Flow<List<...>>
4. 使用技巧与注意事项

主线程安全

suspend 或 Flow 都不能阻塞主线程

UI 更新需要 withContext(Dispatchers.Main) 或 collect 在主线程

一次性 vs 持续更新

插入、更新单条记录 → suspend

观察数据库表变化 → Flow

与 LiveData 结合
Room 支持直接返回 Flow 或 LiveData,可以配合 lifecycleScope 或 viewLifecycleOwner.lifecycleScope

gasProjectDao.getAllGasProject()
.asLiveData()
.observe(this) { list ->
adapter.submitList(list)
}


异常处理
协程可以使用 try/catch 捕获异常,也可以在 Flow 中使用 catch

lifecycleScope.launch {
gasProjectDao.getAllGasProject()
.catch { e -> Log.e("DB", "查询失败", e) }
.collect { list -> adapter.submitList(list) }
}

5. 总结

suspend = 一次异步操作,适合插入、更新、单条查询

Flow = 可观察数据流,适合列表查询、UI 自动刷新

配合 lifecycleScope 可以轻松实现协程 + Room 的异步、响应式编程

💡 技巧:如果你想同时处理一次性操作和持续观察数据,可以 suspend + Flow 结合使用,保持代码简洁、响应快速。

科普:协程使用情况

1️⃣ 默认情况:lifecycleScope.launch { ... }

默认线程:主线程(Dispatchers.Main)。

适合做的操作:

更新 UI(修改 View、TextView、RecyclerView 等)

处理用户交互事件(点击、滑动等)

调用不耗时的本地操作(小量内存计算、轻量对象操作)

示例:

lifecycleScope.launch {
textView.text = "Hello World" // UI操作,主线程
}

2️⃣ 切换到 IO 线程:lifecycleScope.launch(Dispatchers.IO) { ... }

适合操作:

数据库读写(Room 的非 suspend 方法或者耗时操作)

文件操作(读写本地文件)

网络请求(HTTP、Socket)

任何耗时、可能阻塞线程的操作

示例:

lifecycleScope.launch(Dispatchers.IO) {
val list = database.userDao().getAllUsers() // Room suspend 查询,会在IO线程执行
withContext(Dispatchers.Main) {
recyclerView.adapter = UserAdapter(list) // 更新UI要切回主线程
}
}

3️⃣ 混合使用

可以在 同一个协程里 通过 withContext 切换线程:

lifecycleScope.launch {
val list = withContext(Dispatchers.IO) {
database.userDao().getAllUsers() // 后台线程
}
recyclerView.adapter = UserAdapter(list) // 主线程
}


✅ 总结:


UI操作 主线程
Room suspend/数据库操作 IO线程
网络请求 IO线程
轻量计算/内存操作 主线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Android 视频与音频播放全解析:自带方案 + 第三方库实战
1️⃣ 技术背景

在移动端开发中,音视频播放是非常常见的功能,例如短视频、直播、音乐播放器等。Android 提供了内置的播放方案,但在复杂业务场景下,通常需要第三方播放器来实现更多功能,比如多格式支持、缓存、后台播放、字幕和流媒体播放。

全面理解 Android 视频音频播放,从基础方案到第三方库使用。

2️⃣ 内置播放方案
2.1 VideoView / MediaPlayer

VideoView 是 Android 提供的高层封装控件,快速上手。

MediaPlayer 是更底层的音视频播放类,支持更多自定义操作。

示例代码:VideoView 播放本地和网络视频
val videoView: VideoView = findViewById(R.id.videoView)
val uri = Uri.parse("http://example.com/video.mp4")
videoView.setVideoURI(uri)
videoView.setMediaController(MediaController(this))
videoView.start()

MediaPlayer 播放音频
val mediaPlayer = MediaPlayer()
mediaPlayer.setDataSource("http://example.com/audio.mp3")
mediaPlayer.prepareAsync()
mediaPlayer.setOnPreparedListener {
it.start()
}


优点:简单、快速、适合本地或单个视频
缺点:功能有限,不支持多码率、缓存或复杂格式

2.2 ExoPlayer

特点:由 Google 官方提供,功能丰富

支持 HLS、DASH、SmoothStreaming、RTMP 等流媒体

支持缓存、后台播放、字幕、速度调节

集成依赖
implementation "com.google.android.exoplayer:exoplayer:2.18.6"
implementation "com.google.android.exoplayer:exoplayer-ui:2.18.6"

示例代码
val player = ExoPlayer.Builder(this).build()
val playerView: PlayerView = findViewById(R.id.playerView)
playerView.player = player

val mediaItem = MediaItem.fromUri("http://example.com/video.mp4")
player.setMediaItem(mediaItem)
player.prepare()
player.play()

3️⃣ 第三方播放器推荐
3.1 IjkPlayer

特点:基于 FFmpeg,支持几乎所有音视频格式

适合:直播、RTMP、复杂视频格式

implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'

val ijkVideoView: IjkVideoView = findViewById(R.id.ijkVideoView)
ijkVideoView.setVideoPath("http://example.com/live/stream.m3u8")
ijkVideoView.start()

3.2 Vitamio

特点:兼容性好,支持多种视频格式、网络流和字幕

适合:旧项目或需要快速支持多格式

implementation 'io.vov.vitamio:vitamio:5.0.2'

val videoView: io.vov.vitamio.widget.VideoView = findViewById(R.id.videoView)
videoView.setVideoURI(Uri.parse("http://example.com/video.mp4"))
videoView.start()

3.3 ExoPlayer / Media3(音频播放)

特点:Google 官方支持,适合音频流应用

implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'

val player = ExoPlayer.Builder(this).build()
val playerView: PlayerView = findViewById(R.id.playerView)
playerView.player = player

val mediaItem = MediaItem.fromUri("https://example.com/audio.mp3")
player.setMediaItem(mediaItem)
player.prepare()
player.play()

4️⃣ 播放优化建议
优化点 说明
后台播放 使用 ForegroundService + MediaSession,保证音乐/视频在后台继续播放
缓存与预加载 ExoPlayer 提供 CacheDataSource 支持本地缓存和预加载
音频焦点 管理 AudioFocus 避免与电话或其他音频冲突
UI 不卡顿 解码在后台线程,渲染在 SurfaceView / TextureView
5️⃣ 实战技巧

播放列表:结合 RecyclerView + ExoPlayer 管理多视频

字幕和弹幕:Media3 或自定义 OverlayView 实现

多码率流:使用 ExoPlayer TrackSelector 自动切换清晰度

快进/慢放:ExoPlayer PlaybackParameters 可以控制播放速度

6️⃣ 播放流程图(示意)
[UI交互] -> [Player控件] -> [解码器/ExoPlayer] -> [渲染到SurfaceView/TextureView]

[缓存模块] -> [网络/本地数据源]

[后台服务 / MediaSession]

7️⃣ 总结

自带方案(VideoView / MediaPlayer)简单快速,适合本地播放或基础网络播放

ExoPlayer 功能丰富,适合流媒体播放、缓存、字幕和复杂播放需求

IjkPlayer / Vitamio 在直播、多格式和高兼容性项目中不可替代

关键优化点:后台播放、缓存管理、音频焦点、UI不卡顿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
目录

协程与调度器总览(Main / IO / Default / Unconfined)—— 为什么需要“切线程”

必备第三方库(Gradle 依赖)及作用说明

在协程中切换线程:withContext、launch 的调度器参数、flowOn 等详解 + 代码示例

常见集成示例:Retrofit(suspend)、Room(suspend / Flow)、OkHttp、Paging3、Lifecycle/Ktx 等

ViewModel + Repository + Retrofit + Room(完整代码片段)

异常处理、取消、Supervisor、结构化并发、性能优化与常见坑



1. 协程与调度器总览(为什么需要切线程)

在 Android 上,有两个核心概念要理解:

主线程(Main / UI 线程):负责绘制 UI、处理用户事件。任何长时间运行或阻塞的操作(网络、数据库、文件、复杂计算)放在主线程会导致 ANR(应用无响应)或界面卡顿。

后台线程(IO / Default):用于耗时操作。协程通过调度器(Dispatcher)将协程切换到合适线程池执行,从而避免卡 UI。

常用调度器:

Dispatchers.Main:运行在主线程(UI)。用于更新 UI、与 Android 框架交互。

Dispatchers.IO:用于网络、文件、数据库等 IO 密集型任务。基于一个可扩展线程池(适合等待型、阻塞型 IO)。

Dispatchers.Default:用于 CPU 密集型任务(复杂计算、排序、图像处理等)。

Dispatchers.Unconfined:常用于某些测试或非常特殊的案例——不是常规选择,可能在调度上下文上行为不稳定。

“切线程” 的常见方式:在协程内部使用 withContext(...) { ... } 来切换执行上下文(线程池)。这是协程推荐的显式切换方式,语义清晰、安全。

2. 必备第三方库(Gradle 依赖)及作用说明

下面是一个常见 Android 项目会用到的协程相关依赖(Kotlin DSL 或 Gradle Groovy 均可):

// Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" // 提供 Dispatchers.Main

// AndroidX lifecycle + ktx
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" // viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" // lifecycleScope

// Retrofit + Coroutine (suspend support)
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0" // 或 converter-gson
implementation "com.squareup.okhttp3:okhttp:4.11.0"

// Room
implementation "androidx.room:room-runtime:2.5.2"
kapt "androidx.room:room-compiler:2.5.2"
implementation "androidx.room:room-ktx:2.5.2" // support suspend & Flow

// Kotlin Flow + StateFlow 是 Kotlin stdlib coroutines 的一部分,无额外依赖

// Paging 3(可选)
implementation "androidx.paging:paging-runtime-ktx:3.2.0"


说明:

kotlinx-coroutines-android 提供 Dispatchers.Main 的实现(基于 Android Looper)。

lifecycle-viewmodel-ktx 提供 viewModelScope(自动随 ViewModel 清理)。

retrofit + suspend:Retrofit 本身会把 suspend 接口函数当成挂起点(配合 OkHttp)。你可以直接在 Retrofit 的 service 定义 suspend fun getUsers(): List<User>。

room-ktx 提供 suspend DAO 方法 + 返回 Flow<T> 的集成,便于结合协程使用。

3. 在协程中切换线程:withContext、launch 的调度器参数、flowOn 等详解
3.1 launch 本身接收 Dispatcher
viewModelScope.launch(Dispatchers.IO) {
// 协程的默认执行上下文是 IO(适合在这里直接做网络/DB)
val data = repository.loadFromNetwork()
withContext(Dispatchers.Main) {
// 回到主线程更新 UI
_uiState.value = data
}
}

3.2 withContext:显式切换并返回结果(推荐)

withContext 会挂起当前协程并把代码块切到目标调度器执行,执行完再切回调用者上下文(如果需要)。

viewModelScope.launch {
// 默认上下文为 Main(viewModelScope)
val data = withContext(Dispatchers.IO) {
api.getUsers() // 在 IO 池运行
}
// 回到 Main 继续执行
textView.text = data[0].name
}


推荐模式:在 ViewModel 中用 viewModelScope.launch { val r = withContext(Dispatchers.IO) { ... } ... }
这样将 IO 逻辑隔离在 withContext(Dispatchers.IO) 块中,方便单元测试与可读性。

3.3 async + await 与并发

当需要并发请求时使用 async(注意 async 默认是懒或立即启动取决于参数):

viewModelScope.launch {
val deferred1 = async(Dispatchers.IO) { api.loadA() }
val deferred2 = async(Dispatchers.IO) { api.loadB() }
val a = deferred1.await()
val b = deferred2.await()
// 合并结果
}


或更安全的 coroutineScope 包裹并发任务实现结构化并发:

viewModelScope.launch {
coroutineScope {
val a = async(Dispatchers.IO) { api.loadA() }
val b = async(Dispatchers.IO) { api.loadB() }
// 如果一个失败,coroutineScope 会取消其它协程
}
}

3.4 Flow 的 flowOn 与 collect 在 Main/IO 的区别

flow {} 定义的数据生产(emit)部分默认在调用者的上下文执行,flowOn() 用来改变上游执行调度器:

val dataFlow = flow {
val data = db.query() // 这是生产端,应该在 IO
emit(data)
}.flowOn(Dispatchers.IO) // 指定上游在 IO 执行

lifecycleScope.launch {
dataFlow.collect { data ->
// collect 在 Main(当前作用域)执行,可直接更新 UI
}
}


注意:flowOn 只影响上游发射;collect 的代码(下游)仍在调用 collect 时的上下文运行。

3.5 Dispatchers.Main.immediate

Main.immediate 会尝试在当前主线程立即执行,如果当前已经在主线程则不会重新调度。适用于需要避免重复切换但要保证在主线程执行的场景(较少用)。

4. 常见集成示例(更具体)
4.1 Retrofit + suspend

定义接口:

interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}


直接在协程中调用 api.getUsers() —— Retrofit 会在 OkHttp 的线程池执行网络请求并恢复协程。

注意:不要在 suspend 中又使用回调(反模式),把回调包装成 suspend 才行。

4.2 Room + suspend / Flow

DAO 示例:

@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun observeAll(): Flow<List<User>> // 推荐:Flow 自动发射 DB 更新

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(users: List<User>) // suspend 插入
}

4.3 Retrofit + OkHttp 拦截器(logging、token 等)

OkHttp 是在网络层处理的,与协程无直接关系,但在协程中使用 suspend 的 Retrofit 时,请确保 OkHttp 的拦截器不会阻塞主线程(拦截器本身运行在 OkHttp 的线程池)。

4.4 Paging3(与协程整合)

Paging 3 有 PagingSource 返回 PagingData,结合 Pager 和 flow 使用,推荐与 lifecycleScope 一起收集。

5. 实战:ViewModel → Repository → Retrofit + Room(完整示例)

下面给出一个合理的分层示例(简化版),展示如何在协程中切 IO/Main、如何处理异常与缓存到 Room。

Gradle(关键依赖)

(见第2节)

Repository(数据层)
class UserRepository(
private val api: ApiService,
private val userDao: UserDao
) {
// 1) 直接暴露本地 Flow(Room)
val usersFlow: Flow<List<User>> = userDao.observeAll()
.flowOn(Dispatchers.IO) // 上游读取 DB 在 IO

// 2) 同步刷新:从网络拉取并存本地
suspend fun refreshFromNetwork(): Result<Unit> {
return try {
val remote = withContext(Dispatchers.IO) {
api.getUsers() // network in IO
}
withContext(Dispatchers.IO) {
userDao.insertAll(remote) // DB write in IO
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}

// 3) 安全封装模板(可复用)
suspend fun <T> safeApiCall(block: suspend () -> T): Result<T> {
return try {
val res = withContext(Dispatchers.IO) { block() }
Result.success(res)
} catch (e: Exception) {
Result.failure(e)
}
}
}

ViewModel(逻辑层)
class UserViewModel(
private val repo: UserRepository
) : ViewModel() {

// StateFlow 封装 UI 状态
private val _uiState = MutableStateFlow<List<User>>(emptyList())
val uiState: StateFlow<List<User>> = _uiState.asStateFlow()

init {
// 收集本地 DB 的 Flow 并更新 UI(收集在 Main)
viewModelScope.launch {
repo.usersFlow.collect { users ->
_uiState.value = users
}
}
}

// 手动刷新网络
fun refresh() {
viewModelScope.launch {
// 如果不想阻塞主线程,可在 launch(Dispatchers.IO) 直接做
val res = repo.safeApiCall { api.getUsers() }
res.onSuccess {
// 如果 safeApiCall 已在 IO 写 DB, 不需要再切线程
// UI 更新通过 usersFlow 自动触发
}.onFailure {
// 在 Main 处理错误提示
}
}
}
}

Activity / Fragment(UI 层)
class UserFragment : Fragment(R.layout.fragment_user) {
private val vm: UserViewModel by viewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lifecycleScope.launchWhenStarted {
vm.uiState.collect { users ->
// 这里运行在 Main,可以直接更新 RecyclerView
adapter.submitList(users)
}
}

refreshButton.setOnClickListener {
vm.refresh()
}
}
}


几点说明:

usersFlow.flowOn(Dispatchers.IO) 把 DB 读取放到 IO。

collect 在 lifecycleScope.launchWhenStarted {} 的上下文(Main)运行,因此无需显式切 Main 来更新 UI。

所有网络/DB 写操作都放到 IO(withContext(Dispatchers.IO) 或 launch 参数)。

6. 异常处理、取消、Supervisor、结构化并发、性能优化与常见坑
6.1 异常处理

父协程遇到未捕获异常会取消其子协程(结构化并发)。

使用 CoroutineExceptionHandler 只能捕获协程的顶层异常(launch 启动的协程),对 async 的异常需在 await() 时处理。

示例:

val handler = CoroutineExceptionHandler { _, e ->
Log.e("TAG", "Caught $e")
}
viewModelScope.launch(handler) {
// any uncaught exception here will be handled
}


对于 async:

val deferred = viewModelScope.async { throw RuntimeException("err") }
try {
deferred.await()
} catch (e: Exception) {
// 处理
}

6.2 取消(Cancellation)

协程是协作式取消:suspend 函数需检查取消点(例如 delay, withContext, suspendCancellableCoroutine 等会响应取消)。

若在 while(true) 中做工作,需手动检查 isActive:

import kotlinx.coroutines.isActive

viewModelScope.launch {
while (isActive) {
// do work
}
}

6.3 SupervisorJob(隔离子失败)

默认 Job:子协程失败会取消父协程并传播取消。若希望子失败不影响其他子协程使用 SupervisorJob()。

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

6.4 性能优化

避免创建大量短生命周期 CoroutineScope。尽量复用 viewModelScope、lifecycleScope 或自定义长寿命 scope。

IO 操作尽量用 Dispatchers.IO,计算密集用 Default。

避免在生产端(flow 的 emit)做太重的工作:把重计算放到 map + flowOn(Dispatchers.Default)。

对并发量控制:对于大量并发请求,限制同时并发的数量(通过 Semaphore 或 Dispatcher 限制线程池)。

6.5 常见坑与纠正

不要在 suspend 函数里混用回调(未转换为 suspend),否则可能造成线程混乱。用 suspendCancellableCoroutine 把回调桥接为 suspend。

不要把所有逻辑都放在 Main scope:在 Main 中包含大量 withContext(Dispatchers.IO) 可以,但更清晰的写法是 launch(Dispatchers.IO) 来执行后台任务并在结束时切到 Main 更新 UI。

不要滥用 GlobalScope:会造成泄漏与生命周期管理困难。

async 未 await:可能会导致异常丢失(未触发时不会抛出)。

7. 最佳实践清单

使用结构化并发(viewModelScope/lifecycleScope),不要用 GlobalScope。

明确职责分层:UI(Fragment/Activity)只做展示,ViewModel 管理 UI 状态,Repository 负责数据源(网络/DB)。

IO 放 Dispatchers.IO;计算放 Default;UI 放 Main。

用 withContext 做显式切线程,更可读、易测试。

网络使用 suspend 的 Retrofit 接口;DB 使用 Room 的 suspend/Flow。

用 Flow + StateFlow 管理状态流(单向数据流)。

使用 safeApi 封装统一的错误处理与超时策略(withTimeout)。

对并发操作使用 async + await,但在发生失败时确保正确捕获异常。

使用 CoroutineExceptionHandler 处理顶层异常;对于 async 的异常在 await 时处理。

在需要子协程相互隔离失败时使用 SupervisorJob。

避免阻塞线程(Thread.sleep 等),使用 delay 与挂起函数。

测试时用 TestCoroutineDispatcher / runBlockingTest(或新版的 TestDispatcher)。

附:快速参考代码片段(最精简版)
// Retrofit service
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}

// Repository
class Repo(val api: ApiService, val dao: UserDao) {
val usersFlow = dao.observeAll().flowOn(Dispatchers.IO)
suspend fun refresh() = withContext(Dispatchers.IO) {
val list = api.getUsers()
dao.insertAll(list)
}
}

// ViewModel
class VM(val repo: Repo): ViewModel() {
val ui = repo.usersFlow.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
fun refresh() = viewModelScope.launch { repo.refresh() }
}

// Fragment
lifecycleScope.launchWhenStarted {
vm.ui.collect { adapter.submitList(it) }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
安卓多种通知 UI 的实现方式

在安卓开发中,我们经常需要在数据发生变化时通知 UI 更新,例如列表数据变化、状态改变、网络请求结果等。不同的项目架构、技术栈和团队习惯会导致选择不同的通知机制。本文整理了安卓常用的通知 UI 的方式,并分析其原理、优缺点和使用场景。

1. LiveData(Jetpack 组件)
特点

LiveData 是 Jetpack 架构组件的一部分。

具有生命周期感知(Lifecycle-aware),只在 Activity 或 Fragment 活跃时才更新 UI。

支持单向数据流,数据源更新,观察者自动收到通知。

用法示例
// ViewModel
val userName = MutableLiveData<String>()

// Activity/Fragment
viewModel.userName.observe(this) { name ->
textView.text = name
}

优点

生命周期感知,避免内存泄漏。

与 MVVM 架构自然结合。

简单易用,官方推荐。

缺点

只能在主线程更新(虽然可以用 postValue 跨线程,但有延迟)。

单向数据流,复杂事件管理可能不方便。

2. StateFlow / SharedFlow(Kotlin Coroutines)
特点

Kotlin 协程的 Flow 变体,用于响应式数据流。

StateFlow 持有最新状态,SharedFlow 用于事件流(类似 RxJava 的 PublishSubject)。

支持冷流、热流,灵活结合协程和生命周期。

用法示例
// ViewModel
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count

fun increment() {
_count.value += 1
}

// Fragment
lifecycleScope.launch {
viewModel.count.collect { value ->
textView.text = value.toString()
}
}

优点

与协程天然结合,异步友好。

可以处理复杂事件和状态流。

支持缓存最新值(StateFlow)。

缺点

使用前需要熟悉协程。

Lifecycle-aware 需要手动配合 lifecycleScope。

3. RxJava / RxKotlin(响应式编程)
特点

响应式流库,可实现复杂的事件组合、转换和过滤。

支持多线程切换、链式操作。

用法示例
val subject = PublishSubject.create<String>()

// 订阅
subject.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> textView.text = value }

// 发布
subject.onNext("Hello RxJava")

优点

功能强大,适合复杂事件流处理。

可实现跨组件通知。

支持背压、线程调度等高级特性。

缺点

学习成本较高。

容易产生内存泄漏,需要 CompositeDisposable 管理。

项目依赖大,现代 Android 官方更推荐使用 Flow/LiveData。

4. EventBus / Otto(事件总线)
特点

基于发布-订阅模式,实现跨组件消息传递。

组件之间无需直接引用。

用法示例(GreenRobot EventBus)
// 定义事件
data class MessageEvent(val message: String)

// 注册和接收
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: MessageEvent) {
textView.text = event.message
}

// 发布事件
EventBus.getDefault().post(MessageEvent("Hello EventBus"))

优点

跨组件通信方便。

简化复杂 UI 回调链。

支持线程切换。

缺点

容易滥用,变成“全局状态管理”。

难以追踪调用链,调试困难。

生命周期管理需要手动处理注册/注销。

5. 接口回调(Callback / Listener)
特点

最传统的方式,通过接口实现组件间通信。

常用于 Adapter -> Activity / Fragment 更新 UI。

用法示例
interface OnItemClickListener {
fun onClick(position: Int)
}

adapter.setOnItemClickListener(object : OnItemClickListener {
override fun onClick(position: Int) {
textView.text = "Clicked $position"
}
})

优点

简单直接,无需依赖库。

适合局部通信。

缺点

不适合复杂跨组件通信。

易产生内存泄漏(尤其是匿名内部类持有 Activity)。

6. Handler / Message / Looper(线程间通信)
特点

Android 原生机制,用于线程间消息通知。

UI 线程通过 Handler 接收子线程消息更新 UI。

用法示例
val handler = Handler(Looper.getMainLooper()) { msg ->
textView.text = msg.obj as String
true
}

// 子线程发送消息
Thread {
val msg = Message.obtain()
msg.obj = "Hello Handler"
handler.sendMessage(msg)
}.start()

优点

精确控制线程。

官方原生,无第三方依赖。

缺点

代码繁琐,不适合复杂数据流。

与现代架构(MVVM / Jetpack)结合较差。

7. ContentProvider / LiveData + Room(数据库驱动 UI 更新)
特点

数据库变化触发 UI 更新。

Room + LiveData 可自动通知观察者。

用法示例
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAllUsers(): LiveData<List<User>>
}

优点

数据和 UI 双向绑定。

可实现持久化与实时更新结合。

缺点

适合持久化数据,不适合临时事件。

依赖 Room 和 LiveData。

8. BroadcastReceiver(系统广播或自定义广播)
特点

通过广播机制通知多个组件。

可以是系统广播或自定义广播。

用法示例
// 注册广播
val filter = IntentFilter("com.example.ACTION_UPDATE")
registerReceiver(receiver, filter)

// 发送广播
val intent = Intent("com.example.ACTION_UPDATE")
sendBroadcast(intent)

优点

可跨 App 或跨组件通知。

系统级事件监听。

缺点

性能开销大,不适合高频事件。

生命周期管理需要注意。

9. 总结对比表
| 方式 | 生命周期感知 | 跨组件 | 异步支持 | 使用场景 |
|--------------------------|-------------------|----------------|---------------|---------------------------|
| LiveData | ✅ | ❌ | 主线程 | MVVM UI 更新 |
| StateFlow / SharedFlow | ⚠️ 手动管理 | ✅ | ✅ 协程 | 复杂状态流 |
| RxJava | ❌ | ✅ | ✅ | 复杂事件流 / 跨组件 |
| EventBus | ❌ | ✅ | ✅ | 简单跨组件事件 |
| Callback / Listener | ❌ | ❌ | ⚠️ | 局部回调 |
| Handler / Message | ❌ | ❌ | ✅ | 子线程 -> UI |
| Room + LiveData | ✅ | ⚠️ | ✅ | 数据库驱动 UI |
| BroadcastReceiver | ❌ | ✅ | ⚠️ | 系统或跨组件事件 |

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
深入理解 LinkedBlockingQueue 队列

在 Android 开发中,我们经常需要处理多线程任务,例如后台数据下载、消息分发、任务调度等。这时候 线程安全的队列 就显得非常重要,而 LinkedBlockingQueue 是 Java/Kotlin 提供的经典选择。

本文将从概念、用法、关键方法、实际应用和示例代码,全面讲解 LinkedBlockingQueue 的使用。

一、什么是 LinkedBlockingQueue?

LinkedBlockingQueue 是 Java 并发包 (java.util.concurrent) 下的阻塞队列实现。它的特点:

链表结构存储:内部通过链表存储元素,插入和删除效率高。

线程安全:支持多线程并发操作,无需额外加锁。

阻塞特性:

当队列满时,生产者线程会阻塞,直到有空间。

当队列空时,消费者线程会阻塞,直到有元素。

可选容量:

可指定最大容量,也可无限容量(默认是 Integer.MAX_VALUE)。

二、关键概念

生产者-消费者模式
LinkedBlockingQueue 非常适合生产者-消费者模式:

生产者线程负责向队列添加任务。

消费者线程负责从队列取出任务执行。

队列作为缓冲区,保证线程安全且无需手动同步。

阻塞 vs 非阻塞

阻塞方法:put() / take()

非阻塞方法:offer() / poll() / peek()

容量控制
队列容量可控,避免无限增长造成内存压力。

三、基本用法(Kotlin 示例)
1. 导入包
import java.util.concurrent.LinkedBlockingQueue

2. 定义队列
// 有容量限制的队列
val queue = LinkedBlockingQueue<String>(5)

// 无容量限制的队列
val unlimitedQueue = LinkedBlockingQueue<String>()

3. 入队操作
// 阻塞入队,如果队列满,会等待
queue.put("任务1")

// 非阻塞入队,队列满返回 false
val success = queue.offer("任务2")

4. 出队操作
// 阻塞出队,如果队列空,会等待
val task = queue.take()

// 非阻塞出队,队列空返回 null
val polledTask = queue.poll()

5. 查看队列元素
// 查看队头元素,但不移除
val head = queue.peek()

四、结合 Android 的多线程示例

假设我们要在安卓中做一个后台任务队列,生产者产生任务(如网络请求),消费者执行任务(如处理结果)。

class TaskQueueManager {

private val taskQueue = LinkedBlockingQueue<String>(10)

init {
// 启动消费者线程
Thread { consumeTasks() }.start()
}

// 生产任务
fun produceTask(task: String) {
Thread {
try {
taskQueue.put(task) // 队列满会阻塞
Log.d("TaskQueue", "任务入队: $task")
} catch (e: InterruptedException) {
e.printStackTrace()
}
}.start()
}

// 消费任务
private fun consumeTasks() {
while (true) {
try {
val task = taskQueue.take() // 队列空会阻塞
Log.d("TaskQueue", "处理任务: $task")
// 模拟任务处理
Thread.sleep(500)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}


使用示例:

val manager = TaskQueueManager()
manager.produceTask("下载图片")
manager.produceTask("上传数据")
manager.produceTask("处理日志")


特点:

生产者和消费者完全解耦。

队列自动控制线程安全。

阻塞队列确保在高并发下不会丢任务。

五、常用方法总结
方法 功能 阻塞特性
put(E e) 入队,如果队列满,阻塞 阻塞
take() 出队,如果队列空,阻塞 阻塞
offer(E e) 入队,不阻塞,队列满返回 false 非阻塞
poll() 出队,不阻塞,队列空返回 null 非阻塞
peek() 查看队头元素,不移除,队列空返回 null 非阻塞
remainingCapacity() 查看剩余可用容量 非阻塞
六、应用场景

后台任务队列:图片下载、上传、文件处理。

消息分发系统:事件通知、日志分发。

生产者-消费者模型:游戏逻辑、AI Agent任务队列。

限流场景:通过指定容量控制并发量。

七、总结

LinkedBlockingQueue 是 Android 开发中非常实用的线程安全队列,尤其适合 生产者-消费者模式 和多线程任务调度。

通过 Kotlin 的简单封装和线程启动方式,可以快速在安卓中实现可靠的任务队列,避免手动同步、线程安全问题,并提供阻塞/非阻塞两种操作模式,非常适合 AI Agent 或后台任务管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
Android 中实现稳定的 Socket 长连接:自动重连、心跳机制与前台服务保活

在实时通讯、物联网、无人机遥控、设备上传、IM(即时通讯)等场景中,Socket 长连接是最关键的通信方式之一。

但在 Android 上稳定维护 Socket 长连接并不是一件容易的事,尤其是:

App 切后台后连接断开

网络切换(4G/WiFi)导致连接失败

服务被系统回收

心跳不稳定导致服务端主动断开

需要自动重连、保活

本篇文章从实践角度讲解:
如何在 Android 中构建一个 稳定、可重连、可保活 的 Socket 通信模块。

1. 整体架构设计

推荐使用 前台服务 + 单独 Socket 线程 + RxJava 消息分发 的组合:

SocketService(前台服务)

├── SocketClient(核心连接模块)
│ ├── connect()
│ ├── disconnect()
│ ├── send()
│ ├── listenThread(接收消息)
│ ├── heartBeatThread(心跳包)
│ ├── autoReconnect()

└── DataBus(RxJava 发布订阅)


优点:

服务不容易被杀死 → 前台服务

Socket 独立线程运行 → 不阻塞 UI

RxJava → 方便上层 Activity 订阅数据

独立心跳和自动重连机制 → 稳定持续通信

2. 创建前台服务 SocketService
public class SocketService extends Service {

private SocketClient socketClient;

@Override
public void onCreate() {
super.onCreate();
startForegroundService();
socketClient = new SocketClient("192.168.1.100", 9000);
socketClient.connect();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY; // 保活关键
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

private void startForegroundService() {
NotificationChannel channel = new NotificationChannel(
"socket_channel", "Socket Service", NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);

Notification notification = new Notification.Builder(this, "socket_channel")
.setContentTitle("Socket 正在运行")
.setSmallIcon(R.mipmap.ic_launcher)
.build();

startForeground(1, notification);
}
}


关键点:

START_STICKY 让服务被系统杀死后自动重启

前台服务可以在后台长期稳定运行

3. SocketClient:核心连接模块
public class SocketClient {

private String host;
private int port;
private Socket socket;
private boolean isRunning = false;

public SocketClient(String host, int port) {
this.host = host;
this.port = port;
}

public void connect() {
new Thread(() -> {
try {
socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000);
isRunning = true;

startReadThread();
startHeartBeat();

} catch (Exception e) {
autoReconnect();
}
}).start();
}

4. 自动重连机制
private void autoReconnect() {
new Thread(() -> {
while (!isRunning) {
try {
Thread.sleep(3000);
Log.d("Socket", "正在重连...");
connect();
} catch (InterruptedException ignored) {}
}
}).start();
}


特点:

服务端断开 / 网络切换时自动重连

防止频繁重连 → 每 3 秒尝试一次

5. 心跳包(Heartbeat)

为了防止服务器认为客户端掉线,需要定时发送心跳:

private void startHeartBeat() {
new Thread(() -> {
while (isRunning) {
try {
send("HEARTBEAT".getBytes());
Thread.sleep(5000);
} catch (Exception e) {
isRunning = false;
autoReconnect();
}
}
}).start();
}


你也可以使用协议规定的心跳帧,比如 0x55 AA 00 01 00.

6. 接收消息线程
private void startReadThread() {
new Thread(() -> {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];

while (isRunning) {
int len = in.read(buffer);
if (len > 0) {
byte[] data = Arrays.copyOf(buffer, len);
DataBus.post(data);
}
}

} catch (Exception e) {
isRunning = false;
autoReconnect();
}
}).start();
}


收到的消息通过 DataBus 分发到 UI。

7. RxJava 消息总线
public class DataBus {
private static final PublishSubject<byte[]> bus = PublishSubject.create();

public static void post(byte[] data) {
bus.onNext(data);
}

public static Observable<byte[]> toObservable() {
return bus;
}
}


在 Activity 中监听:

DataBus.toObservable()
.subscribe(bytes -> {
Log.d("SocketData", ByteUtils.toHex(bytes));
});

8. 发送数据
public void send(byte[] data) {
new Thread(() -> {
try {
OutputStream out = socket.getOutputStream();
out.write(data);
out.flush();
} catch (Exception e) {
isRunning = false;
autoReconnect();
}
}).start();
}

9. Activity 中启动服务并通信
Intent intent = new Intent(this, SocketService.class);
startForegroundService(intent);

findViewById(R.id.btn_send).setOnClickListener(v -> {
socketClient.send("Hello".getBytes());
});

10. 总结:这是一套可上线的 Socket 长连接方案
功能 状态
前台服务保活 ✔
自动重连 ✔
网络切换重连 ✔
心跳保持在线 ✔
独立读写线程 ✔
RxJava 分发消息 ✔
UI 可随时订阅 ✔


以下是可用工具类:
package com.htnova.common.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SocketClient {

private String host;
private int port;
private volatile boolean isConnected = false;

private Socket socket;
private BufferedInputStream inputStream;
private BufferedOutputStream outputStream;

private ExecutorService socketThread = Executors.newSingleThreadExecutor();
private ExecutorService sendThread = Executors.newSingleThreadExecutor();

private SocketCallback callback;
private boolean isManualClose = false;

private int reconnectDelay = 3000; //自动重连间隔
private int heartbeatInterval = 5000; //心跳间隔(毫秒)
private byte[] heartbeatData = "HEARTBEAT".getBytes();

public interface SocketCallback {
void onConnected();
void onDisconnected();
void onMessage(byte[] msg);
void onError(Exception e);
}

public SocketClient(String host, int port, SocketCallback callback) {
this.host = host;
this.port = port;
this.callback = callback;
}

/**
* 开始连接
*/
public void connect() {
isManualClose = false;
socketThread.execute(() -> startConnect());
}

private void startConnect() {
try {
socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000);
socket.setKeepAlive(true);

inputStream = new BufferedInputStream(socket.getInputStream());
outputStream = new BufferedOutputStream(socket.getOutputStream());
isConnected = true;

if (callback != null) callback.onConnected();

startReadLoop();
startHeartBeatLoop();

} catch (Exception e) {
isConnected = false;
if (callback != null) callback.onError(e);
autoReconnect();
}
}

/**
* 消息读取循环
*/
private void startReadLoop() {
socketThread.execute(() -> {
byte[] buffer = new byte[1024];
int len;
try {
while (isConnected && (len = inputStream.read(buffer)) != -1) {
byte[] data = new byte[len];
System.arraycopy(buffer, 0, data, 0, len);

if (callback != null) callback.onMessage(data);
}
} catch (Exception e) {
if (!isManualClose) {
if (callback != null) callback.onError(e);
autoReconnect();
}
} finally {
close();
}
});
}

/**
* 发送心跳包
*/
private void startHeartBeatLoop() {
socketThread.execute(() -> {
while (isConnected) {
try {
Thread.sleep(heartbeatInterval);
send(heartbeatData);
} catch (Exception ignored) { }
}
});
}

/**
* 自动重连
*/
private void autoReconnect() {
if (isManualClose) return;

isConnected = false;
try {
Thread.sleep(reconnectDelay);
} catch (InterruptedException ignored) {}

connect();
}

/**
* 发送数据
*/
public void send(byte[] data) {
sendThread.execute(() -> {
try {
if (isConnected && outputStream != null) {
outputStream.write(data);
outputStream.flush();
}
} catch (Exception e) {
if (callback != null) callback.onError(e);
}
});
}

/**
* 关闭连接
*/
public void close() {
isManualClose = true;
isConnected = false;

try {
if (socket != null) socket.close();
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
} catch (IOException ignored) {}

if (callback != null) callback.onDisconnected();
}

/** 设置心跳包 */
public void setHeartbeat(byte[] heartbeat, int intervalMs) {
this.heartbeatData = heartbeat;
this.heartbeatInterval = intervalMs;
}

/** 设置自动重连间隔 */
public void setReconnectDelay(int delayMs) {
this.reconnectDelay = delayMs;
}
}

如何使用?
1. 初始化
SocketClient socketClient = new SocketClient("192.168.1.100", 9000, new SocketClient.SocketCallback() {
@Override
public void onConnected() {
Log.d("Socket", "已连接");
}

@Override
public void onDisconnected() {
Log.d("Socket", "已断开");
}

@Override
public void onMessage(byte[] msg) {
Log.d("Socket", "收到消息:" + new String(msg));
}

@Override
public void onError(Exception e) {
Log.e("Socket", "错误:" + e.getMessage());
}
});

2. 连接服务器
socketClient.connect();

3. 发送数据
socketClient.send("Hello Server".getBytes());

4. 设置心跳包
socketClient.setHeartbeat("PING".getBytes(), 3000);

5. 关闭连接
socketClient.close();





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
📌 目录

自定义 View 为什么存在?

自定义控件的 4 大分类

方式一:继承 View(完全自绘)

方式二:继承 ViewGroup(自定义布局)

方式三:组合控件(XML + inflate)

方式四:继承已有控件扩展功能

View 绘制三大流程(Measure/Layout/Draw)

资源文件(attrs.xml,自定义属性)

四种方式对比总结(表格)

最佳实践与性能优化

⭐ 1. 自定义 View 为什么存在?

Android 原生控件有限,自定义 View 可以让我们做:

仪表盘

雷达图

折线图

特效控件

自定义动画

高级布局(FlowLayout、TagLayout)

底层都离不开:测量 + 绘制 + 布局。

⭐ 2. 自定义控件的 4 大分类

Android 自定义控件主要分为 4 类:

类型 继承 是否自己画 是否自布局 是否包含 XML 子控件 使用场景
① 自定义绘制 View View ✔ ❌ ❌ 图形、动画、仪表盘等
② 自定义布局 ViewGroup ViewGroup ❌ ✔ ✔ 自定义复杂布局流式布局
③ 组合控件(复合控件) FrameLayout/LinearLayout 等 部分 部分 ✔(inflate) 自定义输入框、Card、搜索框
④ 扩展已有控件 TextView/ImageView… 可选 ❌ ❌ 扩展行为,如跑马灯、可折叠文字
🟥 3. 方式一:继承 View(完全自绘控件)

适用场景:

绘制图形(仪表盘、波形图)

自定义动画

数据可视化控件

核心点:

重写 onMeasure() 手动测量尺寸

重写 onDraw() 绘制图像

使用 Canvas/Path/Paint

🔧 示例代码
class CircleView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
}

override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val defaultSize = 200
val w = resolveSize(defaultSize, widthSpec)
val h = resolveSize(defaultSize, heightSpec)
setMeasuredDimension(w, h)
}

override fun onDraw(canvas: Canvas) {
canvas.drawCircle(width / 2f, height / 2f, width / 2f, paint)
}
}

🟦 4. 方式二:继承 ViewGroup(自定义布局控件)

适用场景:

流式布局 FlowLayout

九宫格

复杂排序布局

自定义 Banner、卡片堆叠布局

关键点:

onMeasure():测量每个子 View

onLayout():摆放子 View 位置

不负责绘制(不重写 onDraw)

🔧 示例代码示例(FlowLayout)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var lineWidth = 0
var totalHeight = paddingTop + paddingBottom
val widthSize = MeasureSpec.getSize(widthMeasureSpec)

for (i in 0 until childCount) {
val child = getChildAt(i)
measureChild(child, widthMeasureSpec, heightMeasureSpec)

if (lineWidth + child.measuredWidth > widthSize) {
totalHeight += child.measuredHeight
lineWidth = 0
}
lineWidth += child.measuredWidth
}

setMeasuredDimension(widthSize, totalHeight)
}

override fun onLayout(p0: Boolean, l: Int, t: Int, r: Int, b: Int) {
var x = paddingLeft
var y = paddingTop
val width = r - l

for (i in 0 until childCount) {
val child = getChildAt(i)
if (x + child.measuredWidth > width) {
x = paddingLeft
y += child.measuredHeight
}
child.layout(x, y, x + child.measuredWidth, y + child.measuredHeight)
x += child.measuredWidth
}
}

🟩 5. 方式三:组合控件(XML + inflate)

最常见的自定义控件方式。

适用场景:

自定义 TitleBar

自定义输入框(带图标、清理按钮)

SearchView、自定义卡片控件

核心点:使用 LayoutInflater

🔧 示例代码
class SearchBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {

init {
LayoutInflater.from(context)
.inflate(R.layout.view_search_bar, this)
}
}


view_search_bar.xml:

<LinearLayout ... >
<ImageView android:src="@drawable/ic_search"/>
<EditText android:hint="搜索"/>
</LinearLayout>


优点:

复用已有控件,效率高

易扩展、易维护

高度可定制化

🟨 6. 方式四:继承已有控件(行为扩展型)

适用场景:

扩展 EditText(限制输入)

扩展 TextView(自定义跑马灯)

扩展 ImageView(实现圆角图片)

示例:

class RoundImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatImageView(context, attrs) {

private val path = Path()

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
path.reset()
path.addRoundRect(
0f, 0f, w.toFloat(), h.toFloat(),
20f, 20f,
Path.Direction.CW
)
}

override fun onDraw(canvas: Canvas) {
canvas.save()
canvas.clipPath(path)
super.onDraw(canvas)
canvas.restore()
}
}

🎨 7. View 绘制三大流程(适用于所有方式)

核心流程图:

Measure → Layout → Draw

① onMeasure:测量大小

决定 View 的宽高(wrap_content 逻辑写这里)

② onLayout:对子 View 摆放位置

只存在于 ViewGroup

③ onDraw:绘制内容

纯 View 重绘的核心

🧩 8. 自定义属性(attrs.xml)

几乎所有自定义控件都需要支持 XML 属性。

attrs.xml:

<declare-styleable name="CircleView">
<attr name="circleColor" format="color"/>
</declare-styleable>


使用:

<com.xxx.CircleView
app:circleColor="@color/red"/>


读取:

val typed = context.obtainStyledAttributes(attrs, R.styleable.CircleView)
paint.color = typed.getColor(R.styleable.CircleView_circleColor, Color.BLACK)
typed.recycle()

📊 9. 四种方式对比总结
类型 自绘 子 View 自布局 难度 性能
纯 View(绘制型) ✔ ❌ ❌ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
ViewGroup(布局型) ❌ ✔ ✔ ⭐⭐⭐⭐⭐ ⭐⭐⭐
组合控件(XML inflate) 部分 ✔ 部分 ⭐⭐ ⭐⭐⭐⭐
扩展已有控件 部分 ❌ ❌ ⭐ ⭐⭐⭐⭐⭐
🧠 10. 性能优化建议

不要在 onDraw() 里创建对象

使用 postInvalidate() 更新 UI(子线程)

使用硬件加速(默认开启)

谨慎使用 saveLayer()(会创建离屏缓冲)

减少过深的 View 层级(组合控件时注意)

📌 最终总结

Android 自定义控件的核心是:

绘制(View) + 布局(ViewGroup) + 组合复用(inflate) + 控件行为扩展

四种方式对应不同场景,理解:

onMeasure

onLayout

onDraw

attrs.xml

即可轻松实现任意定制 UI。



📌 目录

View 绘制体系概述

自定义 View 的本质是什么?

Measure 测量流程解析

Layout 布局流程

Draw 绘制流程

Canvas & Paint 底层原理

自定义 View 的完整模板

常见问题与性能优化

总结

🔥 1. View 绘制体系概述(整体流程图)

Android UI 渲染是 从 ViewRootImpl → DecorView → 各层级 View 一层一层递归传递的。

下面是完整流程图(你可以作为博客插图):

┌───────────────────┐
│ ViewRootImpl │
└─────────┬─────────┘

┌─────────────▼──────────────┐
│ performTraversals() │
└─────────────┬──────────────┘
Measure → Layout → Draw(核心三步骤)

┌────────────┐ ┌────────────┐ ┌────────────┐
│ measure() │ │ layout() │ │ draw() │
└──────┬─────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
▼ ▼ ▼
onMeasure() onLayout() onDraw()

⭐ 2. 自定义 View 的本质是什么?

一句话总结:

自定义 View = 手动实现 Measure + 绘制逻辑 + 事件逻辑。

Android 框架提供了一个基础绘制管线,开发者只需要实现以下部分:

测量:决定 View 的大小 (onMeasure)

绘制:决定 View 怎么画 (onDraw)

布局:决定子 View 的位置(自定义 ViewGroup 才需要)

🎯 3. Measure 测量流程
✔ 测量的目标:

计算 View 的:

measuredWidth

measuredHeight

这两个值由 测量模式 (MeasureSpec) 决定:

Mode 含义
EXACTLY 精确大小(match_parent 或固定值)
AT_MOST 最大不能超过父容器(wrap_content)
UNSPECIFIED 不限制(滚动容器会用)
✔ 必须重写 onMeasure(wrap_content 的关键)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)

val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)

val desiredWidth = 200
val desiredHeight = 200

val width = when(widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> desiredWidth.coerceAtMost(widthSize)
else -> desiredWidth
}

val height = when(heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> desiredHeight.coerceAtMost(heightSize)
else -> desiredHeight
}

setMeasuredDimension(width, height)
}


⚠ 如果你不重写 onMeasure,wrap_content 会失效!

🎯 4. Layout 布局流程(仅 ViewGroup 需要)

作用:

确定子 View 的位置(left、top、right、bottom)

流程:

layout()
└── onLayout()


例子(自定义简单线性布局):

override fun onLayout(p0: Boolean, l: Int, t: Int, r: Int, b: Int) {
var childTop = paddingTop

for (i in 0 until childCount) {
val child = getChildAt(i)
val childHeight = child.measuredHeight
child.layout(paddingLeft, childTop, r - paddingRight, childTop + childHeight)
childTop += childHeight
}
}

🎯 5. Draw 绘制流程(核心)

draw() 的内部流程如下:

draw()
├── drawBackground()
├── onDraw() ← 开发者核心绘制逻辑
├── dispatchDraw() ← 绘制子 View(ViewGroup)
└── onDrawForeground()


你的自定义内容都写在 onDraw():

override fun onDraw(canvas: Canvas) {
paint.color = Color.RED
canvas.drawCircle(width / 2f, height / 2f, 100f, paint)
}

🎨 6. Canvas & Paint 底层绘制原理
✔ Canvas 是 绘图指令的集合

本质是向 Surface 发送 GPU 绘制指令,例如:

drawLine

drawCircle

drawPath

clipRect

rotate

Canvas 内部使用 GPU(Skia 图形库)。

✔ Paint 是画笔

Paint 决定线条风格:

color

strokeWidth

style (FILL / STROKE)

shader(渐变、BitmapShader)

antiAlias

例如:

val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
strokeWidth = 4f
style = Paint.Style.STROKE
}

🧩 7. 自定义 View 完整模板(可直接复制)
class CircleView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val defaultSize = 300
val width = resolveSize(defaultSize, widthMeasureSpec)
val height = resolveSize(defaultSize, heightMeasureSpec)
setMeasuredDimension(width, height)
}

override fun onDraw(canvas: Canvas) {
val radius = min(width, height) / 2f
canvas.drawCircle(width / 2f, height / 2f, radius, paint)
}
}


这是一份标准自定义 View 模板。

⚙️ 8. 性能优化 & 常见问题
1. 避免在 onDraw 创建对象

❌ 不要 new Paint / Path
✔ 在构造函数创建

2. 使用 invalidate() vs postInvalidate()

invalidate():UI 线程

postInvalidate():非 UI 线程

3. 避免使用过多的 saveLayer()

它会创建离屏缓冲,非常耗性能。

4. onMeasure 尽量使用 resolveSize()
5. 大量动画建议用:ValueAnimator + invalidate()

不要直接在 onDraw 做运算。

📌 9. 总结

自定义 View 是 Android UI 开发的核心能力,理解其底层流程是高级开发者必备技能。

View 绘制三大流程:Measure、Layout、Draw

测量模式 MeasureSpec

Canvas / Paint 底层原理

自定义 View 模板

性能优化策略

掌握这些,就可以绘制任何 UI:

仪表盘

雷达图

动态波形

自定义图标

特效控件

富交互图形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
Android 启动优化:从冷启动到首帧渲染

原理 → 工具 → 优化策略 → 实战代码 全流程解析 Android 启动优化

⭐ 目录

什么是应用启动?

冷启动 / 温启动 / 热启动的区别

Android 启动流程原理(Zygote → AMS → ActivityThread)

启动性能分析工具(Systrace / Perfetto / Start-up Profiler)

启动优化策略(从布局到异步化)

启动优化实战代码

启动耗时监控:生产环境落地方案

1. 什么是应用启动?

Android 官方定义应用启动包含:

Process Start:系统为你的 APP 创建新进程

Application.attach / onCreate

ActivityThread 创建 Activity

setContentView

首帧渲染完成(First Draw)

👉 从点击图标到看到主界面的整个过程,就是启动过程。

2. 冷启动 / 温启动 / 热启动
冷启动(Cold Start)

APP 进程完全不存在,需要:

fork Zygote 生成进程

初始化 Application

创建 Activity

⏱耗时最长,也是我们最要优化的。

温启动(Warm Start)

进程还在,但 Activity 被销毁,需要:

重新创建 Activity

热启动(Hot Start)

Activity 被放到后台并未销毁,仅需恢复界面。

3. Android 启动流程原理

完整启动链路如下:

Launcher 点击图标

AMS(ActivityManagerService) → 启动进程

Zygote fork 出应用进程

ActivityThread.main()

Application.attach() / onCreate()

创建 Activity → onCreate()

setContentView() → LayoutInflate

首帧渲染 Choreographer#doFrame()


看起来很长,但真正拖慢启动速度的主要集中在:

✔ Application.onCreate
✔ Activity.onCreate
✔ 布局过度复杂(setContentView)
✔ 主线程阻塞耗时任务

4. 启动性能分析工具

Google 提供了非常强大的工具来监控启动过程。

① Logcat Start-up Profiler(最简单)

Android Studio → Logcat → 使用过滤器 ActivityTaskManager
可以直接看到:

Displayed com.xxx/.MainActivity +500ms

② Android Studio Profiler - Startup

从 Android Studio 4.2 后有独立的 Startup Profiler。

可以看到:

Application 初始化耗时

Activity 初始化耗时

布局耗时

CPU 占用

③ Perfetto / Systrace(最专业)

可以分析每一段调用链耗时。
复杂,但分析非常精准,用于深度优化。

5. 启动优化策略(最核心)

Android 启动优化的核心结论只有一句话:

把所有不影响首屏显示的任务,全部延后。

(1)Application.onCreate 要“瘦身”

不要做:

复杂 SDK 初始化

大量 I/O 操作

大量反射

应该做:

只做必要初始化

其余延迟到 IdleHandler 或后台线程

(2)避免主线程阻塞

典型坑点:

读写文件

查询数据库

网络请求

SharedPreferences.apply() 卡顿

(3)首屏布局优化

减少嵌套(LinearLayout + ConstraintLayout)

使用 ViewStub 延迟加载

避免在 XML 中使用过度复杂的 Constraint

(4)使用 SplashActivity 容灾

在 Android 12 后必须使用 SplashScreen API,但仍可以:

在 Splash 阶段做预加载

减少真正 Activity 的压力

6. 启动优化实战代码

下面给你能直接复制使用的 Application 延迟初始化模板:

class App : Application() {

override fun onCreate() {
super.onCreate()

// 必要初始化(主线程)
initLogger()
initCrashHandler()

// 非必要初始化延迟执行
delayInit()
}

private fun delayInit() {
// 方式1:IdleHandler(UI 空闲时)
Looper.myQueue().addIdleHandler {
initBigSdk()
false
}

// 方式2:后台线程
Executors.newSingleThreadExecutor().execute {
initDatabase()
initAds()
initAnalytics()
}
}
}

启动耗时监控(线上生产可用)
object AppStartTrace {

private var appStartTime = 0L

fun start() {
appStartTime = System.currentTimeMillis()
}

fun end(activity: Activity) {
val cost = System.currentTimeMillis() - appStartTime
Log.d("AppStartTrace", "冷启动耗时:${cost}ms")
// 可上报后台
}
}


使用方式:

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppStartTrace.end(this)
}
}

7. 总结
从 原理、工具、优化策略、实战代码、线上监控 全面介绍了 Android 启动优化。


✔ 启动流程(Zygote → AMS → Activity)
✔ 如何判断启动慢在哪里
✔ 如何用工具分析
✔ 如何分阶段初始化
✔ 如何优化布局
✔ 如何写可落地的启动监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
📘 Android Binder 机制通俗原理全解析(流程图 + AIDL 示例)

Binder 是 Android 系统最核心的机制之一。所有系统服务(AMS、WMS、PackageManager、MediaService…)背后几乎都通过 Binder 完成通信,因此如果想真正理解 Android 系统原理,Binder 是绕不过去的基础。

本文通过通俗解释 + 配图 + 代码,快速理解 Binder 的本质与工作机制。

⭐ 1. 为什么 Android 一定要用 Binder?

一句话解释:

Android 是多进程系统,进程之间必须能通信,Binder 是它选中的 IPC 方案。

🔍 为什么要多进程?

每个 App 独立进程 → 防止互相崩溃

系统服务独立进程(system_server)

UI 服务 (SurfaceFlinger)

媒体服务(MediaService)

所以:App 想启动 Activity、申请权限、绘制 UI…全部要通过跨进程调用。

传统 IPC(Socket/管道/共享内存)都不够好,Binder 胜出。

⭐ 2. Binder 有多强?(对比传统 IPC)
IPC 方式 拷贝次数 性能 安全性 使用复杂度
Socket 2 中 低 高
管道 pipe 2 中 低 中
共享内存 1 高 低 高(需同步)
❗ Binder 1 高 高(自动携带UID/PID) 低(系统封装)

Binder 为什么快?

Binder 只进行一次“用户态 → 内核态”的数据拷贝
其他 IPC 都需要“两次拷贝”。

⭐ 3. Binder 机制大图(核心流程)

以下是最常见的 “App 启动 Activity” 的 Binder 调用流程:

+-----------------+ +----------------+
| Client | | Server |
| (App 进程) | | (AMS等服务) |
+--------+--------+ +--------+-------+
| ^
| Proxy 调用 |
v |
+--------+--------+ +--------+-------+
| Binder 驱动 | <----> | Stub(服务端)|
| (内核 Kernel) | | 业务逻辑 |
+-----------------+ +----------------+


再看更完整的带 ServiceManager 的图:

获取服务 注册服务
┌──────────────┐ ┌───────────────┐
│ Client │ --查询服务--> │ ServiceManager │
└──────────────┘ └───────────────┘
│ │
│ 调用服务 │
v v
┌──────────────┐ Binder ┌──────────────┐
│ ClientProxy │ <-------------> │ ServerStub │
└──────────────┘ └──────────────┘


✔ Proxy:客户端代理
✔ Stub:服务端接收者
✔ ServiceManager:系统服务目录
✔ Binder Driver:内核通信枢纽

⭐ 4. Binder 的四大组件结构
角色 作用
Binder 驱动(内核) 负责数据传输、线程管理、内存映射
ServiceManager 类似“系统服务注册中心”
Server(Stub) 服务端对象,处理真正的业务逻辑
Client(Proxy) 客户端代理,负责发起 Binder 调用

你写 AIDL 时生成的就是:

Stub.java(服务端)

Proxy.java(客户端)

⭐ 5. AIDL 示例:最小可运行 Binder Demo

下面以一个简单的 AIDL 文件演示:

📄 5.1 AIDL 文件(IRemoteService.aidl)
package com.demo.ipc;

// AIDL 接口
interface IRemoteService {
int add(int a, int b);
}


编译后自动生成:

IRemoteService.Stub(服务端)

IRemoteService.Stub.Proxy(客户端)

📄 5.2 服务端实现(Stub)
public class RemoteService extends Service {

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
@Override
public int add(int a, int b) {
return a + b;
}
};

@Override
public IBinder onBind(Intent intent) {
return binder;
}
}

📄 5.3 客户端调用(Proxy)
private IRemoteService remoteService;

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
remoteService = IRemoteService.Stub.asInterface(service);
try {
int result = remoteService.add(3, 5);
Log.d("IPC", "结果:" + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
remoteService = null;
}
};


绑定:

Intent intent = new Intent();
intent.setClassName("com.demo.server",
"com.demo.server.RemoteService");
bindService(intent, connection, BIND_AUTO_CREATE);


✔ 完整的 Binder Demo 至此全部跑通。

⭐ 6. Binder 为什么安全?

每次 Binder 调用都自带调用者 UID/PID(由内核提供),因此 AMS/WMS 能做:

权限校验

角色校验

进程身份确认

不可伪造 UID → 无法伪造系统身份

⭐ 7. Binder 为什么快?

关键原因:

一次拷贝 + 内核共享内存

驱动调度(队列 + 线程池)

零上下文切换同步(不用共享锁)

这是“低耗、高吞吐”的本质。

⭐ 8. 总结

Binder = 高性能 IPC + 安全校验 + RPC 封装 + 系统服务基础设施
Android 系统的运行效率、安全性和架构清晰度,几乎都依赖 Binder。

如果你想真正理解 Android:

✔ AMS 怎么启动 Activity
✔ WMS 怎么管理窗口
✔ MediaServer 为什么这么快
✔ PackageManagerService 如何校验 APK

全部要从 Binder 入门。