关于 android-development
面向 Android 开发者的现代应用开发指南,涵盖从界面与架构到数据、测试和发布的实践,并涉及 Android 多种设备形态及 Kotlin Multiplatform。
主要功能
- 01Jetpack Compose 界面与状态管理
- 02现代 Android 架构与依赖注入
- 03Room、Retrofit 与仓储层
- 04Material Design 3 与平台适配
- 05测试、Play Store 发布及性能配置
- 06Kotlin Multiplatform 与 Compose Multiplatform
适用场景
- 01规划 Android 应用的分层架构
- 02实现 Compose 界面和导航
- 03构建本地及远程数据层
- 04准备测试与 Play Store 发布
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill android-development安装后在 Claude Code 中输入/android-development确认已加载。
详细介绍
Android 开发
构建现代 Android 应用的综合指南。
涵盖的平台
| 平台 | 最低 SDK | 目标 SDK |
|---|---|---|
| Android 手机 | API 24(7.0) | API 34(14) |
| Android 平板电脑 | API 24 | API 34 |
| Android TV | API 24 | API 34 |
| Wear OS | API 30 | API 34 |
| Android Auto | API 29 | API 34 |
Jetpack Compose(现代 UI)
基本结构
@Composable
fun MyApp() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Count: $count",
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
状态管理
// Local state
var text by remember { mutableStateOf("") }
// State hoisting
@Composable
fun StatefulCounter() {
var count by remember { mutableStateOf(0) }
StatelessCounter(count = count, onIncrement = { count++ })
}
@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
// ViewModel state
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: ItemRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadItems() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val items = repository.getItems()
_uiState.update { it.copy(items = items, isLoading = false) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message, isLoading = false) }
}
}
}
}
// Collecting in Compose
@Composable
fun MainScreen(viewModel: MainViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorMessage(uiState.error!!)
else -> ItemList(uiState.items)
}
}
导航
// Navigation setup
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
}
// Type-safe navigation (recommended)
@Serializable
data class DetailRoute(val itemId: String)
navController.navigate(DetailRoute(itemId = "123"))
列表
@Composable
fun ItemList(items: List<Item>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = items,
key = { it.id }
) { item ->
ItemCard(item = item)
}
}
}
// Pull to refresh
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RefreshableList(
items: List<Item>,
isRefreshing: Boolean,
onRefresh: () -> Unit
) {
val pullRefreshState = rememberPullToRefreshState()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
state = pullRefreshState
) {
LazyColumn { /* content */ }
}
}
现代 Android 架构
清洁架构分层
app/
├── data/
│ ├── local/
│ │ ├── AppDatabase.kt
│ │ └── ItemDao.kt
│ ├── remote/
│ │ ├── ApiService.kt
│ │ └── ItemDto.kt
│ └── repository/
│ └── ItemRepositoryImpl.kt
├── domain/
│ ├── model/
│ │ └── Item.kt
│ ├── repository/
│ │ └── ItemRepository.kt
│ └── usecase/
│ └── GetItemsUseCase.kt
├── presentation/
│ ├── home/
│ │ ├── HomeScreen.kt
│ │ └── HomeViewModel.kt
│ └── navigation/
│ └── AppNavigation.kt
└── di/
└── AppModule.kt
依赖注入(Hilt)
@HiltAndroidApp
class MyApplication : Application()
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
@Singleton
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Provides
@Singleton
fun provideItemRepository(
apiService: ApiService,
database: AppDatabase
): ItemRepository {
return ItemRepositoryImpl(apiService, database.itemDao())
}
}
数据层
Room 数据库
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey val id: String,
val name: String,
val createdAt: Long
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY createdAt DESC")
fun getItems(): Flow<List<ItemEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItems(items: List<ItemEntity>)
@Delete
suspend fun deleteItem(item: ItemEntity)
}
@Database(entities = [ItemEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
}
Retrofit API
interface ApiService {
@GET("items")
suspend fun getItems(): List<ItemDto>
@POST("items")
suspend fun createItem(@Body item: CreateItemRequest): ItemDto
@DELETE("items/{id}")
suspend fun deleteItem(@Path("id") id: String)
}
data class ItemDto(
val id: String,
val name: String,
@SerializedName("created_at") val createdAt: String
)
仓库模式
interface ItemRepository {
fun getItems(): Flow<List<Item>>
suspend fun refreshItems()
suspend fun deleteItem(id: String)
}
class ItemRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val itemDao: ItemDao
) : ItemRepository {
override fun getItems(): Flow<List<Item>> {
return itemDao.getItems().map { entities ->
entities.map { it.toDomain() }
}
}
override suspend fun refreshItems() {
val items = apiService.getItems()
itemDao.insertItems(items.map { it.toEntity() })
}
}
Material Design 3
主题设置
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
常用组件
// Top App Bar
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyTopBar(
title: String,
onBackClick: () -> Unit
) {
TopAppBar(
title = { Text(title) },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = { /* menu */ }) {
Icon(Icons.Default.MoreVert, contentDescription = "Menu")
}
}
)
}
// Bottom Navigation
@Composable
fun MyBottomBar(
selectedTab: Int,
onTabSelected: (Int) -> Unit
) {
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Default.Home, contentDescription = null) },
label = { Text("Home") },
selected = selectedTab == 0,
onClick = { onTabSelected(0) }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
label = { Text("Settings") },
selected = selectedTab == 1,
onClick = { onTabSelected(1) }
)
}
}
平台专属内容
Android TV
// Focus management
@Composable
fun TvButton(
onClick: () -> Unit,
content: @Composable () -> Unit
) {
var isFocused by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.onFocusChanged { isFocused = it.isFocused }
.focusable()
.clickable(onClick = onClick)
.background(
if (isFocused) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.surface
)
) {
content()
}
}
Wear OS
@Composable
fun WearApp() {
MaterialTheme {
ScalingLazyColumn(
modifier = Modifier.fillMaxSize(),
anchorType = ScalingLazyListAnchorType.ItemCenter
) {
item { TimeText() }
item {
Chip(
onClick = { },
label = { Text("Action") }
)
}
}
}
}
测试
单元测试
@Test
fun `getItems returns mapped domain objects`() = runTest {
val repository = ItemRepositoryImpl(
apiService = FakeApiService(),
itemDao = FakeItemDao()
)
val items = repository.getItems().first()
assertEquals(2, items.size)
assertEquals("Item 1", items[0].name)
}
Compose UI 测试
@HiltAndroidTest
class MainScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun displayItems_whenLoaded() {
composeTestRule.onNodeWithText("Item 1").assertIsDisplayed()
composeTestRule.onNodeWithText("Item 2").assertIsDisplayed()
}
@Test
fun navigateToDetail_onItemClick() {
composeTestRule.onNodeWithText("Item 1").performClick()
composeTestRule.onNodeWithText("Item Details").assertIsDisplayed()
}
}
Play Store 要求
必需项
- 隐私政策
- 应用图标(512x512)
- 功能图片(1024x500)
- 截图(至少 2 张)
- 目标 API 34+
App Bundle
android {
bundle {
language {
enableSplit = true
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
}
}
最佳实践
应该:
- 使用 Kotlin Coroutines 和 Flow
- 遵循单向数据流
- 为新 UI 使用 Jetpack Compose
- 妥善处理生命周期
- 支持多种屏幕尺寸
不应该:
- 阻塞主线程
- 泄漏上下文
- 将尺寸硬编码
- 忽略进程死亡
- 跳过 ProGuard 规则
Kotlin Multiplatform(KMP)/ Compose Multiplatform(CMP)
使用 KMP 共享业务逻辑
// shared/src/commonMain/kotlin/UserRepository.kt
expect class PlatformContext
class UserRepository(private val api: ApiService, private val db: Database) {
suspend fun getUsers(): List<User> {
return try {
val remote = api.fetchUsers()
db.saveUsers(remote)
remote
} catch (e: Exception) {
db.getCachedUsers()
}
}
}
// shared/src/androidMain/kotlin/PlatformContext.kt
actual class PlatformContext(val context: Context)
// shared/src/iosMain/kotlin/PlatformContext.kt
actual class PlatformContext
Compose Multiplatform(共享 UI)
// shared/src/commonMain/kotlin/App.kt
@Composable
fun App() {
MaterialTheme {
var users by remember { mutableStateOf<List<User>>(emptyList()) }
val repository = remember { UserRepository() }
LaunchedEffect(Unit) {
users = repository.getUsers()
}
LazyColumn {
items(users) { user ->
UserCard(user)
}
}
}
}
// Runs natively on Android, iOS, Desktop, and Web
KMP 项目结构
project/
├── shared/
│ └── src/
│ ├── commonMain/ # Shared Kotlin code
│ ├── androidMain/ # Android-specific
│ ├── iosMain/ # iOS-specific
│ └── desktopMain/ # Desktop-specific
├── androidApp/ # Android entry point
├── iosApp/ # iOS entry point (Swift/SwiftUI)
└── desktopApp/ # Desktop entry point
Navigation 3(类型安全导航)
// Define routes as serializable data classes
@Serializable
data object Home
@Serializable
data class Detail(val itemId: String)
@Serializable
data class Settings(val section: String? = null)
// Navigation setup
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController = navController, startDestination = Home) {
composable<Home> {
HomeScreen(onItemClick = { id ->
navController.navigate(Detail(itemId = id))
})
}
composable<Detail> { backStackEntry ->
val detail: Detail = backStackEntry.toRoute()
DetailScreen(itemId = detail.itemId)
}
composable<Settings> { backStackEntry ->
val settings: Settings = backStackEntry.toRoute()
SettingsScreen(section = settings.section)
}
}
}
API 35(Android 15)功能
| 功能 | 描述 |
|---|---|
| 强制启用边到边显示 | 应用必须妥善处理窗口内边距 |
| 预测性返回 | 系统返回手势,并带有预览动画 |
| 私人空间 | 由用户控制的隐藏应用配置文件 |
| 卫星连接 | 通过卫星发送 SMS/MMS |
| 应用归档 | 自动归档未使用的应用,同时保留数据 |
| Health Connect | 更新后的健康数据 API |
// Edge-to-edge (required on API 35+)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Scaffold(
modifier = Modifier.fillMaxSize(),
contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
) { innerPadding ->
MainContent(modifier = Modifier.padding(innerPadding))
}
}
}
// Predictive back with Compose
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(onBack: () -> Unit) {
BackHandler(onBack = onBack)
// Content renders with predictive back animation automatically
}
使用基准配置文件提升启动性能
// baselineprofile/build.gradle.kts
dependencies {
implementation("androidx.benchmark:benchmark-macro-junit4:1.3.0")
}
// BaselineProfileGenerator.kt
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generateBaselineProfile() {
rule.collect(packageName = "com.example.app") {
// Critical user journey
pressHome()
startActivityAndWait()
device.findObject(By.text("Login")).click()
device.wait(Until.hasObject(By.text("Dashboard")), 5000)
}
}
}
// app/build.gradle.kts
android {
buildTypes {
release {
// Baseline profiles improve startup by 15-30%
baselineProfile.automaticGenerationDuringBuild = true
}
}
}
Credential Manager API
// Modern sign-in replacing legacy APIs
val credentialManager = CredentialManager.create(context)
// Sign in with passkeys, passwords, or federated identity
suspend fun signIn() {
val request = GetCredentialRequest(listOf(
GetPasswordOption(),
GetPublicKeyCredentialOption(requestJson = passkeyRequestJson),
GetGoogleIdOption(serverClientId = WEB_CLIENT_ID),
))
try {
val result = credentialManager.getCredential(context, request)
handleSignIn(result)
} catch (e: GetCredentialException) {
handleSignInError(e)
}
}
// Create a passkey
suspend fun createPasskey() {
val request = CreatePublicKeyCredentialRequest(
requestJson = createPasskeyRequestJson
)
val result = credentialManager.createCredential(context, request)
handlePasskeyCreation(result)
}
评论
支持 Markdown