code-abyss 1.6.16 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -6
  2. package/bin/install.js +59 -163
  3. package/bin/lib/ccline.js +82 -0
  4. package/bin/lib/utils.js +61 -0
  5. package/package.json +5 -2
  6. package/skills/SKILL.md +24 -16
  7. package/skills/domains/ai/SKILL.md +2 -2
  8. package/skills/domains/ai/prompt-and-eval.md +279 -0
  9. package/skills/domains/architecture/SKILL.md +2 -3
  10. package/skills/domains/architecture/security-arch.md +87 -0
  11. package/skills/domains/data-engineering/SKILL.md +188 -26
  12. package/skills/domains/development/SKILL.md +1 -4
  13. package/skills/domains/devops/SKILL.md +3 -5
  14. package/skills/domains/devops/performance.md +63 -0
  15. package/skills/domains/devops/testing.md +97 -0
  16. package/skills/domains/frontend-design/SKILL.md +12 -3
  17. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  18. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  19. package/skills/domains/frontend-design/engineering.md +287 -0
  20. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  21. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  22. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  23. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  24. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  25. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  26. package/skills/domains/infrastructure/SKILL.md +174 -34
  27. package/skills/domains/mobile/SKILL.md +211 -21
  28. package/skills/domains/orchestration/SKILL.md +1 -0
  29. package/skills/domains/security/SKILL.md +4 -6
  30. package/skills/domains/security/blue-team.md +57 -0
  31. package/skills/domains/security/red-team.md +54 -0
  32. package/skills/domains/security/threat-intel.md +50 -0
  33. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  34. package/skills/run_skill.js +139 -0
  35. package/skills/tools/gen-docs/SKILL.md +6 -4
  36. package/skills/tools/gen-docs/scripts/doc_generator.js +363 -0
  37. package/skills/tools/lib/shared.js +98 -0
  38. package/skills/tools/verify-change/SKILL.md +8 -6
  39. package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  40. package/skills/tools/verify-module/SKILL.md +6 -4
  41. package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  42. package/skills/tools/verify-quality/SKILL.md +5 -3
  43. package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  44. package/skills/tools/verify-security/SKILL.md +7 -5
  45. package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
  46. package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
  47. package/skills/domains/COVERAGE_PLAN.md +0 -232
  48. package/skills/domains/ai/model-evaluation.md +0 -790
  49. package/skills/domains/ai/prompt-engineering.md +0 -703
  50. package/skills/domains/architecture/compliance.md +0 -299
  51. package/skills/domains/architecture/data-security.md +0 -184
  52. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  53. package/skills/domains/data-engineering/data-quality.md +0 -894
  54. package/skills/domains/data-engineering/stream-processing.md +0 -791
  55. package/skills/domains/development/dart.md +0 -963
  56. package/skills/domains/development/kotlin.md +0 -834
  57. package/skills/domains/development/php.md +0 -659
  58. package/skills/domains/development/swift.md +0 -755
  59. package/skills/domains/devops/e2e-testing.md +0 -914
  60. package/skills/domains/devops/performance-testing.md +0 -734
  61. package/skills/domains/devops/testing-strategy.md +0 -667
  62. package/skills/domains/frontend-design/build-tools.md +0 -743
  63. package/skills/domains/frontend-design/performance.md +0 -734
  64. package/skills/domains/frontend-design/testing.md +0 -699
  65. package/skills/domains/infrastructure/gitops.md +0 -735
  66. package/skills/domains/infrastructure/iac.md +0 -855
  67. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  68. package/skills/domains/mobile/android-dev.md +0 -979
  69. package/skills/domains/mobile/cross-platform.md +0 -795
  70. package/skills/domains/mobile/ios-dev.md +0 -931
  71. package/skills/domains/security/secrets-management.md +0 -834
  72. package/skills/domains/security/supply-chain.md +0 -931
  73. package/skills/domains/security/threat-modeling.md +0 -828
  74. package/skills/run_skill.py +0 -153
  75. package/skills/tests/README.md +0 -225
  76. package/skills/tests/SUMMARY.md +0 -362
  77. package/skills/tests/__init__.py +0 -3
  78. package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
  79. package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
  80. package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
  81. package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
  82. package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
  83. package/skills/tests/test_change_analyzer.py +0 -558
  84. package/skills/tests/test_doc_generator.py +0 -538
  85. package/skills/tests/test_module_scanner.py +0 -376
  86. package/skills/tests/test_quality_checker.py +0 -516
  87. package/skills/tests/test_security_scanner.py +0 -426
  88. package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
  89. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
  90. package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
  91. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  92. package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
  93. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  94. package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
  95. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  96. package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
  97. package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
@@ -1,979 +0,0 @@
1
- ---
2
- name: android-dev
3
- description: Android 开发。Jetpack Compose、Kotlin、ViewModel、LiveData、Coroutines、Flow、MVVM、Android架构。当用户提到 Android 开发、Jetpack Compose、Kotlin 时使用。
4
- ---
5
-
6
- # 🤖 Android 开发 · Android Development
7
-
8
- ## Jetpack Compose 基础
9
-
10
- ### Composable 函数
11
- ```kotlin
12
- import androidx.compose.material3.*
13
- import androidx.compose.runtime.*
14
- import androidx.compose.ui.Modifier
15
- import androidx.compose.ui.unit.dp
16
-
17
- @Composable
18
- fun Greeting(name: String) {
19
- Column(
20
- modifier = Modifier.padding(16.dp)
21
- ) {
22
- Text(
23
- text = "Hello, $name!",
24
- style = MaterialTheme.typography.headlineMedium
25
- )
26
-
27
- Spacer(modifier = Modifier.height(8.dp))
28
-
29
- Button(onClick = { /* TODO */ }) {
30
- Text("Click Me")
31
- }
32
- }
33
- }
34
- ```
35
-
36
- ### State 管理
37
- ```kotlin
38
- @Composable
39
- fun CounterScreen() {
40
- var count by remember { mutableStateOf(0) }
41
- var isEnabled by remember { mutableStateOf(true) }
42
-
43
- Column(
44
- modifier = Modifier.padding(16.dp),
45
- horizontalAlignment = Alignment.CenterHorizontally
46
- ) {
47
- Text(
48
- text = "Count: $count",
49
- style = MaterialTheme.typography.headlineLarge
50
- )
51
-
52
- Row(
53
- horizontalArrangement = Arrangement.spacedBy(8.dp)
54
- ) {
55
- Button(onClick = { count-- }) {
56
- Text("-")
57
- }
58
- Button(onClick = { count++ }) {
59
- Text("+")
60
- }
61
- }
62
-
63
- Switch(
64
- checked = isEnabled,
65
- onCheckedChange = { isEnabled = it }
66
- )
67
- }
68
- }
69
- ```
70
-
71
- ### LazyColumn 列表
72
- ```kotlin
73
- @Composable
74
- fun UserList(users: List<User>) {
75
- LazyColumn(
76
- contentPadding = PaddingValues(16.dp),
77
- verticalArrangement = Arrangement.spacedBy(8.dp)
78
- ) {
79
- items(users) { user ->
80
- UserItem(user)
81
- }
82
- }
83
- }
84
-
85
- @Composable
86
- fun UserItem(user: User) {
87
- Card(
88
- modifier = Modifier.fillMaxWidth(),
89
- elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
90
- ) {
91
- Row(
92
- modifier = Modifier.padding(16.dp),
93
- verticalAlignment = Alignment.CenterVertically
94
- ) {
95
- AsyncImage(
96
- model = user.avatarUrl,
97
- contentDescription = null,
98
- modifier = Modifier.size(48.dp)
99
- )
100
-
101
- Spacer(modifier = Modifier.width(16.dp))
102
-
103
- Column {
104
- Text(
105
- text = user.name,
106
- style = MaterialTheme.typography.titleMedium
107
- )
108
- Text(
109
- text = user.email,
110
- style = MaterialTheme.typography.bodySmall
111
- )
112
- }
113
- }
114
- }
115
- }
116
- ```
117
-
118
- ## Compose 高级
119
-
120
- ### Side Effects
121
- ```kotlin
122
- @Composable
123
- fun TimerScreen() {
124
- var seconds by remember { mutableStateOf(0) }
125
-
126
- // LaunchedEffect: 启动协程
127
- LaunchedEffect(Unit) {
128
- while (true) {
129
- delay(1000)
130
- seconds++
131
- }
132
- }
133
-
134
- Text("Elapsed: $seconds seconds")
135
- }
136
-
137
- @Composable
138
- fun AnalyticsScreen(screenName: String) {
139
- // DisposableEffect: 清理资源
140
- DisposableEffect(screenName) {
141
- analytics.logScreenView(screenName)
142
- onDispose {
143
- analytics.logScreenExit(screenName)
144
- }
145
- }
146
-
147
- // SideEffect: 同步状态到外部
148
- SideEffect {
149
- externalState.update(screenName)
150
- }
151
- }
152
-
153
- @Composable
154
- fun SearchScreen() {
155
- var query by remember { mutableStateOf("") }
156
- var results by remember { mutableStateOf<List<Item>>(emptyList()) }
157
-
158
- // snapshotFlow: 监听状态变化
159
- LaunchedEffect(Unit) {
160
- snapshotFlow { query }
161
- .debounce(300)
162
- .filter { it.isNotEmpty() }
163
- .collectLatest { q ->
164
- results = repository.search(q)
165
- }
166
- }
167
-
168
- Column {
169
- TextField(
170
- value = query,
171
- onValueChange = { query = it }
172
- )
173
- LazyColumn {
174
- items(results) { item ->
175
- Text(item.name)
176
- }
177
- }
178
- }
179
- }
180
- ```
181
-
182
- ### Custom Modifier
183
- ```kotlin
184
- fun Modifier.shimmer(): Modifier = composed {
185
- var offsetX by remember { mutableStateOf(0f) }
186
- val infiniteTransition = rememberInfiniteTransition()
187
-
188
- val shimmerOffset by infiniteTransition.animateFloat(
189
- initialValue = 0f,
190
- targetValue = 1000f,
191
- animationSpec = infiniteRepeatable(
192
- animation = tween(1000, easing = LinearEasing)
193
- )
194
- )
195
-
196
- this.drawWithContent {
197
- drawContent()
198
- drawRect(
199
- brush = Brush.linearGradient(
200
- colors = listOf(
201
- Color.Transparent,
202
- Color.White.copy(alpha = 0.3f),
203
- Color.Transparent
204
- ),
205
- start = Offset(shimmerOffset, 0f),
206
- end = Offset(shimmerOffset + 200f, 0f)
207
- )
208
- )
209
- }
210
- }
211
-
212
- // 使用
213
- Box(
214
- modifier = Modifier
215
- .size(200.dp)
216
- .shimmer()
217
- )
218
- ```
219
-
220
- ### Navigation
221
- ```kotlin
222
- @Composable
223
- fun AppNavigation() {
224
- val navController = rememberNavController()
225
-
226
- NavHost(
227
- navController = navController,
228
- startDestination = "home"
229
- ) {
230
- composable("home") {
231
- HomeScreen(
232
- onNavigateToDetail = { id ->
233
- navController.navigate("detail/$id")
234
- }
235
- )
236
- }
237
-
238
- composable(
239
- route = "detail/{itemId}",
240
- arguments = listOf(navArgument("itemId") { type = NavType.IntType })
241
- ) { backStackEntry ->
242
- val itemId = backStackEntry.arguments?.getInt("itemId")
243
- DetailScreen(itemId = itemId)
244
- }
245
- }
246
- }
247
- ```
248
-
249
- ## ViewModel & LiveData
250
-
251
- ### ViewModel
252
- ```kotlin
253
- class UserViewModel(
254
- private val repository: UserRepository
255
- ) : ViewModel() {
256
-
257
- private val _users = MutableLiveData<List<User>>()
258
- val users: LiveData<List<User>> = _users
259
-
260
- private val _isLoading = MutableLiveData(false)
261
- val isLoading: LiveData<Boolean> = _isLoading
262
-
263
- private val _error = MutableLiveData<String?>()
264
- val error: LiveData<String?> = _error
265
-
266
- fun loadUsers() {
267
- viewModelScope.launch {
268
- _isLoading.value = true
269
- _error.value = null
270
-
271
- try {
272
- val result = repository.getUsers()
273
- _users.value = result
274
- } catch (e: Exception) {
275
- _error.value = e.message
276
- } finally {
277
- _isLoading.value = false
278
- }
279
- }
280
- }
281
-
282
- fun refresh() {
283
- loadUsers()
284
- }
285
- }
286
- ```
287
-
288
- ### StateFlow (推荐)
289
- ```kotlin
290
- class UserViewModel(
291
- private val repository: UserRepository
292
- ) : ViewModel() {
293
-
294
- private val _uiState = MutableStateFlow(UserUiState())
295
- val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
296
-
297
- init {
298
- loadUsers()
299
- }
300
-
301
- fun loadUsers() {
302
- viewModelScope.launch {
303
- _uiState.update { it.copy(isLoading = true) }
304
-
305
- repository.getUsers()
306
- .onSuccess { users ->
307
- _uiState.update {
308
- it.copy(users = users, isLoading = false)
309
- }
310
- }
311
- .onFailure { error ->
312
- _uiState.update {
313
- it.copy(error = error.message, isLoading = false)
314
- }
315
- }
316
- }
317
- }
318
- }
319
-
320
- data class UserUiState(
321
- val users: List<User> = emptyList(),
322
- val isLoading: Boolean = false,
323
- val error: String? = null
324
- )
325
-
326
- // Compose 中使用
327
- @Composable
328
- fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
329
- val uiState by viewModel.uiState.collectAsState()
330
-
331
- when {
332
- uiState.isLoading -> LoadingView()
333
- uiState.error != null -> ErrorView(uiState.error!!)
334
- else -> UserList(uiState.users)
335
- }
336
- }
337
- ```
338
-
339
- ## Kotlin Coroutines
340
-
341
- ### 基础用法
342
- ```kotlin
343
- // 启动协程
344
- viewModelScope.launch {
345
- val result = withContext(Dispatchers.IO) {
346
- repository.fetchData()
347
- }
348
- updateUI(result)
349
- }
350
-
351
- // 并发执行
352
- suspend fun loadData() = coroutineScope {
353
- val users = async { repository.getUsers() }
354
- val posts = async { repository.getPosts() }
355
-
356
- CombinedData(
357
- users = users.await(),
358
- posts = posts.await()
359
- )
360
- }
361
-
362
- // 超时控制
363
- try {
364
- withTimeout(5000) {
365
- repository.fetchData()
366
- }
367
- } catch (e: TimeoutCancellationException) {
368
- // 处理超时
369
- }
370
- ```
371
-
372
- ### Flow
373
- ```kotlin
374
- class UserRepository {
375
- fun observeUsers(): Flow<List<User>> = flow {
376
- while (true) {
377
- val users = api.getUsers()
378
- emit(users)
379
- delay(30000) // 每30秒刷新
380
- }
381
- }
382
-
383
- fun searchUsers(query: String): Flow<List<User>> = flow {
384
- emit(emptyList()) // 初始状态
385
- delay(300) // 防抖
386
- val results = api.search(query)
387
- emit(results)
388
- }.flowOn(Dispatchers.IO)
389
- }
390
-
391
- // ViewModel 中使用
392
- class UserViewModel(
393
- private val repository: UserRepository
394
- ) : ViewModel() {
395
-
396
- val users: StateFlow<List<User>> = repository.observeUsers()
397
- .stateIn(
398
- scope = viewModelScope,
399
- started = SharingStarted.WhileSubscribed(5000),
400
- initialValue = emptyList()
401
- )
402
-
403
- private val searchQuery = MutableStateFlow("")
404
-
405
- val searchResults: StateFlow<List<User>> = searchQuery
406
- .debounce(300)
407
- .filter { it.isNotEmpty() }
408
- .flatMapLatest { query ->
409
- repository.searchUsers(query)
410
- }
411
- .stateIn(
412
- scope = viewModelScope,
413
- started = SharingStarted.WhileSubscribed(),
414
- initialValue = emptyList()
415
- )
416
-
417
- fun search(query: String) {
418
- searchQuery.value = query
419
- }
420
- }
421
- ```
422
-
423
- ### Channel
424
- ```kotlin
425
- class EventBus {
426
- private val _events = Channel<Event>(Channel.BUFFERED)
427
- val events: Flow<Event> = _events.receiveAsFlow()
428
-
429
- suspend fun send(event: Event) {
430
- _events.send(event)
431
- }
432
- }
433
-
434
- // 使用
435
- viewModelScope.launch {
436
- eventBus.events.collect { event ->
437
- when (event) {
438
- is Event.UserLoggedIn -> handleLogin(event.user)
439
- is Event.DataUpdated -> refresh()
440
- }
441
- }
442
- }
443
- ```
444
-
445
- ## MVVM 架构
446
-
447
- ### Model
448
- ```kotlin
449
- data class User(
450
- val id: Int,
451
- val name: String,
452
- val email: String,
453
- val avatarUrl: String
454
- )
455
-
456
- data class LoginRequest(
457
- val username: String,
458
- val password: String
459
- )
460
-
461
- data class LoginResponse(
462
- val token: String,
463
- val user: User
464
- )
465
- ```
466
-
467
- ### Repository
468
- ```kotlin
469
- interface UserRepository {
470
- suspend fun login(username: String, password: String): Result<LoginResponse>
471
- suspend fun getProfile(): Result<User>
472
- fun observeUser(): Flow<User?>
473
- }
474
-
475
- class UserRepositoryImpl(
476
- private val api: ApiService,
477
- private val userDao: UserDao,
478
- private val tokenManager: TokenManager
479
- ) : UserRepository {
480
-
481
- override suspend fun login(username: String, password: String): Result<LoginResponse> {
482
- return try {
483
- val request = LoginRequest(username, password)
484
- val response = api.login(request)
485
- tokenManager.saveToken(response.token)
486
- userDao.insert(response.user)
487
- Result.success(response)
488
- } catch (e: Exception) {
489
- Result.failure(e)
490
- }
491
- }
492
-
493
- override suspend fun getProfile(): Result<User> {
494
- return try {
495
- val user = api.getProfile()
496
- userDao.insert(user)
497
- Result.success(user)
498
- } catch (e: Exception) {
499
- Result.failure(e)
500
- }
501
- }
502
-
503
- override fun observeUser(): Flow<User?> {
504
- return userDao.observeUser()
505
- }
506
- }
507
- ```
508
-
509
- ### ViewModel
510
- ```kotlin
511
- @HiltViewModel
512
- class LoginViewModel @Inject constructor(
513
- private val repository: UserRepository
514
- ) : ViewModel() {
515
-
516
- private val _uiState = MutableStateFlow(LoginUiState())
517
- val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
518
-
519
- fun updateUsername(username: String) {
520
- _uiState.update { it.copy(username = username) }
521
- }
522
-
523
- fun updatePassword(password: String) {
524
- _uiState.update { it.copy(password = password) }
525
- }
526
-
527
- fun login() {
528
- val state = _uiState.value
529
- if (!state.isValid) return
530
-
531
- viewModelScope.launch {
532
- _uiState.update { it.copy(isLoading = true, error = null) }
533
-
534
- repository.login(state.username, state.password)
535
- .onSuccess {
536
- _uiState.update { it.copy(isLoading = false, isLoggedIn = true) }
537
- }
538
- .onFailure { error ->
539
- _uiState.update {
540
- it.copy(isLoading = false, error = error.message)
541
- }
542
- }
543
- }
544
- }
545
- }
546
-
547
- data class LoginUiState(
548
- val username: String = "",
549
- val password: String = "",
550
- val isLoading: Boolean = false,
551
- val error: String? = null,
552
- val isLoggedIn: Boolean = false
553
- ) {
554
- val isValid: Boolean
555
- get() = username.isNotEmpty() && password.length >= 6
556
- }
557
- ```
558
-
559
- ### View (Compose)
560
- ```kotlin
561
- @Composable
562
- fun LoginScreen(
563
- viewModel: LoginViewModel = hiltViewModel(),
564
- onLoginSuccess: () -> Unit
565
- ) {
566
- val uiState by viewModel.uiState.collectAsState()
567
-
568
- LaunchedEffect(uiState.isLoggedIn) {
569
- if (uiState.isLoggedIn) {
570
- onLoginSuccess()
571
- }
572
- }
573
-
574
- Column(
575
- modifier = Modifier
576
- .fillMaxSize()
577
- .padding(16.dp),
578
- verticalArrangement = Arrangement.Center
579
- ) {
580
- OutlinedTextField(
581
- value = uiState.username,
582
- onValueChange = viewModel::updateUsername,
583
- label = { Text("Username") },
584
- modifier = Modifier.fillMaxWidth()
585
- )
586
-
587
- Spacer(modifier = Modifier.height(8.dp))
588
-
589
- OutlinedTextField(
590
- value = uiState.password,
591
- onValueChange = viewModel::updatePassword,
592
- label = { Text("Password") },
593
- visualTransformation = PasswordVisualTransformation(),
594
- modifier = Modifier.fillMaxWidth()
595
- )
596
-
597
- if (uiState.error != null) {
598
- Text(
599
- text = uiState.error!!,
600
- color = MaterialTheme.colorScheme.error,
601
- style = MaterialTheme.typography.bodySmall,
602
- modifier = Modifier.padding(top = 8.dp)
603
- )
604
- }
605
-
606
- Spacer(modifier = Modifier.height(16.dp))
607
-
608
- Button(
609
- onClick = viewModel::login,
610
- enabled = uiState.isValid && !uiState.isLoading,
611
- modifier = Modifier.fillMaxWidth()
612
- ) {
613
- if (uiState.isLoading) {
614
- CircularProgressIndicator(
615
- modifier = Modifier.size(24.dp),
616
- color = MaterialTheme.colorScheme.onPrimary
617
- )
618
- } else {
619
- Text("Login")
620
- }
621
- }
622
- }
623
- }
624
- ```
625
-
626
- ## 依赖注入 (Hilt)
627
-
628
- ### 配置
629
- ```kotlin
630
- @HiltAndroidApp
631
- class MyApplication : Application()
632
-
633
- @AndroidEntryPoint
634
- class MainActivity : ComponentActivity() {
635
- override fun onCreate(savedInstanceState: Bundle?) {
636
- super.onCreate(savedInstanceState)
637
- setContent {
638
- MyApp()
639
- }
640
- }
641
- }
642
- ```
643
-
644
- ### Module
645
- ```kotlin
646
- @Module
647
- @InstallIn(SingletonComponent::class)
648
- object NetworkModule {
649
-
650
- @Provides
651
- @Singleton
652
- fun provideOkHttpClient(): OkHttpClient {
653
- return OkHttpClient.Builder()
654
- .addInterceptor(AuthInterceptor())
655
- .connectTimeout(30, TimeUnit.SECONDS)
656
- .build()
657
- }
658
-
659
- @Provides
660
- @Singleton
661
- fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
662
- return Retrofit.Builder()
663
- .baseUrl("https://api.example.com")
664
- .client(okHttpClient)
665
- .addConverterFactory(GsonConverterFactory.create())
666
- .build()
667
- }
668
-
669
- @Provides
670
- @Singleton
671
- fun provideApiService(retrofit: Retrofit): ApiService {
672
- return retrofit.create(ApiService::class.java)
673
- }
674
- }
675
-
676
- @Module
677
- @InstallIn(SingletonComponent::class)
678
- abstract class RepositoryModule {
679
-
680
- @Binds
681
- @Singleton
682
- abstract fun bindUserRepository(
683
- impl: UserRepositoryImpl
684
- ): UserRepository
685
- }
686
- ```
687
-
688
- ## Room 数据库
689
-
690
- ### Entity
691
- ```kotlin
692
- @Entity(tableName = "users")
693
- data class UserEntity(
694
- @PrimaryKey val id: Int,
695
- val name: String,
696
- val email: String,
697
- @ColumnInfo(name = "avatar_url") val avatarUrl: String,
698
- @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
699
- )
700
- ```
701
-
702
- ### DAO
703
- ```kotlin
704
- @Dao
705
- interface UserDao {
706
- @Query("SELECT * FROM users")
707
- fun observeAll(): Flow<List<UserEntity>>
708
-
709
- @Query("SELECT * FROM users WHERE id = :id")
710
- suspend fun getById(id: Int): UserEntity?
711
-
712
- @Insert(onConflict = OnConflictStrategy.REPLACE)
713
- suspend fun insert(user: UserEntity)
714
-
715
- @Insert(onConflict = OnConflictStrategy.REPLACE)
716
- suspend fun insertAll(users: List<UserEntity>)
717
-
718
- @Delete
719
- suspend fun delete(user: UserEntity)
720
-
721
- @Query("DELETE FROM users")
722
- suspend fun deleteAll()
723
- }
724
- ```
725
-
726
- ### Database
727
- ```kotlin
728
- @Database(
729
- entities = [UserEntity::class],
730
- version = 1,
731
- exportSchema = false
732
- )
733
- abstract class AppDatabase : RoomDatabase() {
734
- abstract fun userDao(): UserDao
735
- }
736
-
737
- @Module
738
- @InstallIn(SingletonComponent::class)
739
- object DatabaseModule {
740
-
741
- @Provides
742
- @Singleton
743
- fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
744
- return Room.databaseBuilder(
745
- context,
746
- AppDatabase::class.java,
747
- "app_database"
748
- )
749
- .fallbackToDestructiveMigration()
750
- .build()
751
- }
752
-
753
- @Provides
754
- fun provideUserDao(database: AppDatabase): UserDao {
755
- return database.userDao()
756
- }
757
- }
758
- ```
759
-
760
- ## 网络层
761
-
762
- ### Retrofit
763
- ```kotlin
764
- interface ApiService {
765
- @POST("auth/login")
766
- suspend fun login(@Body request: LoginRequest): LoginResponse
767
-
768
- @GET("users/{id}")
769
- suspend fun getUser(@Path("id") id: Int): User
770
-
771
- @GET("users")
772
- suspend fun getUsers(
773
- @Query("page") page: Int,
774
- @Query("limit") limit: Int = 20
775
- ): List<User>
776
-
777
- @Multipart
778
- @POST("upload")
779
- suspend fun uploadImage(
780
- @Part file: MultipartBody.Part
781
- ): UploadResponse
782
- }
783
- ```
784
-
785
- ### Interceptor
786
- ```kotlin
787
- class AuthInterceptor(
788
- private val tokenManager: TokenManager
789
- ) : Interceptor {
790
- override fun intercept(chain: Interceptor.Chain): Response {
791
- val original = chain.request()
792
- val token = tokenManager.getToken()
793
-
794
- val request = if (token != null) {
795
- original.newBuilder()
796
- .header("Authorization", "Bearer $token")
797
- .build()
798
- } else {
799
- original
800
- }
801
-
802
- return chain.proceed(request)
803
- }
804
- }
805
-
806
- class LoggingInterceptor : Interceptor {
807
- override fun intercept(chain: Interceptor.Chain): Response {
808
- val request = chain.request()
809
- Log.d("API", "Request: ${request.method} ${request.url}")
810
-
811
- val response = chain.proceed(request)
812
- Log.d("API", "Response: ${response.code}")
813
-
814
- return response
815
- }
816
- }
817
- ```
818
-
819
- ## 性能优化
820
-
821
- ### LazyColumn 优化
822
- ```kotlin
823
- @Composable
824
- fun OptimizedList(items: List<Item>) {
825
- LazyColumn(
826
- contentPadding = PaddingValues(16.dp),
827
- verticalArrangement = Arrangement.spacedBy(8.dp)
828
- ) {
829
- items(
830
- items = items,
831
- key = { it.id } // 关键:提供稳定的 key
832
- ) { item ->
833
- ItemCard(item)
834
- }
835
- }
836
- }
837
-
838
- @Composable
839
- fun ItemCard(item: Item) {
840
- // 使用 remember 避免重组时重新计算
841
- val formattedDate = remember(item.timestamp) {
842
- formatDate(item.timestamp)
843
- }
844
-
845
- Card {
846
- Text(formattedDate)
847
- }
848
- }
849
- ```
850
-
851
- ### 图片加载 (Coil)
852
- ```kotlin
853
- @Composable
854
- fun AsyncImage(url: String) {
855
- AsyncImage(
856
- model = ImageRequest.Builder(LocalContext.current)
857
- .data(url)
858
- .crossfade(true)
859
- .memoryCachePolicy(CachePolicy.ENABLED)
860
- .diskCachePolicy(CachePolicy.ENABLED)
861
- .build(),
862
- contentDescription = null,
863
- modifier = Modifier.size(200.dp)
864
- )
865
- }
866
- ```
867
-
868
- ### 避免过度重组
869
- ```kotlin
870
- @Composable
871
- fun ExpensiveScreen(data: Data) {
872
- // ❌ 错误:每次重组都会创建新实例
873
- val processor = DataProcessor()
874
-
875
- // ✅ 正确:使用 remember
876
- val processor = remember { DataProcessor() }
877
-
878
- // ✅ 使用 derivedStateOf 避免不必要的重组
879
- val filteredData by remember {
880
- derivedStateOf {
881
- data.items.filter { it.isActive }
882
- }
883
- }
884
- }
885
- ```
886
-
887
- ## 测试
888
-
889
- ### Unit Test
890
- ```kotlin
891
- @Test
892
- fun `login success updates state correctly`() = runTest {
893
- val repository = FakeUserRepository()
894
- val viewModel = LoginViewModel(repository)
895
-
896
- viewModel.updateUsername("test")
897
- viewModel.updatePassword("password")
898
- viewModel.login()
899
-
900
- advanceUntilIdle()
901
-
902
- val state = viewModel.uiState.value
903
- assertTrue(state.isLoggedIn)
904
- assertNull(state.error)
905
- }
906
-
907
- class FakeUserRepository : UserRepository {
908
- var loginResult: Result<LoginResponse>? = null
909
-
910
- override suspend fun login(username: String, password: String): Result<LoginResponse> {
911
- return loginResult ?: Result.success(
912
- LoginResponse("token", User(1, "Test", "test@example.com", ""))
913
- )
914
- }
915
-
916
- override suspend fun getProfile(): Result<User> {
917
- return Result.success(User(1, "Test", "test@example.com", ""))
918
- }
919
-
920
- override fun observeUser(): Flow<User?> = flowOf(null)
921
- }
922
- ```
923
-
924
- ### UI Test
925
- ```kotlin
926
- @get:Rule
927
- val composeTestRule = createComposeRule()
928
-
929
- @Test
930
- fun loginFlow() {
931
- composeTestRule.setContent {
932
- LoginScreen(onLoginSuccess = {})
933
- }
934
-
935
- composeTestRule
936
- .onNodeWithText("Username")
937
- .performTextInput("testuser")
938
-
939
- composeTestRule
940
- .onNodeWithText("Password")
941
- .performTextInput("password123")
942
-
943
- composeTestRule
944
- .onNodeWithText("Login")
945
- .performClick()
946
-
947
- composeTestRule
948
- .onNodeWithText("Welcome")
949
- .assertIsDisplayed()
950
- }
951
- ```
952
-
953
- ## 工具清单
954
-
955
- | 工具 | 用途 |
956
- |------|------|
957
- | Android Studio | IDE |
958
- | Gradle | 构建工具 |
959
- | Hilt | 依赖注入 |
960
- | Retrofit | 网络请求 |
961
- | Room | 数据库 |
962
- | Coil | 图片加载 |
963
- | LeakCanary | 内存泄漏检测 |
964
- | Detekt | 代码规范 |
965
-
966
- ## 最佳实践
967
-
968
- - ✅ Jetpack Compose 优先,View 系统按需使用
969
- - ✅ MVVM 架构 + Repository 模式
970
- - ✅ StateFlow 替代 LiveData
971
- - ✅ Kotlin Coroutines 处理异步
972
- - ✅ Hilt 依赖注入
973
- - ✅ Room 本地持久化
974
- - ✅ 使用 key 优化 LazyColumn
975
- - ✅ remember/derivedStateOf 避免重组
976
- - ✅ 单元测试覆盖 ViewModel
977
- - ✅ UI 测试验证关键流程
978
-
979
- ---