Android 原生开发文档
Android 是 Google 开发的基于 Linux 内核的开源移动操作系统,广泛应用于手机、平板、可穿戴设备、车载系统等。本文档系统介绍 Android 原生开发的核心概念、架构演进、Jetpack Compose 声明式 UI、网络与数据、后台任务与推送、性能优化等内容。
1. Android 架构演进
1.1 系统架构
Android 系统采用分层架构,从上到下依次为:
1.1.1 应用层(System Apps)
系统内置应用和用户安装的第三方应用,包括电话、短信、浏览器、联系人等。应用层使用 Java/Kotlin 语言开发,运行在 Android Runtime 之上。
1.1.2 Java API Framework
Google 提供的 Android 框架 API,包括:
- View System:UI 组件体系(TextView、Button、RecyclerView 等)
- Content Providers:数据共享机制
- Resource Manager:资源文件管理
- Notification Manager:通知管理
- Activity Manager:Activity 生命周期管理
- Window Manager:窗口管理
- Location Manager:位置服务
- Package Manager:包管理
1.1.3 Android Runtime 与系统库(Android Runtime / Native Libraries)
Android Runtime (ART):
- Android 5.0 起取代 Dalvik 成为官方运行时
- 支持 Ahead-of-Time (AOT) 和 Just-in-Time (JIT) 编译
- 引入 Profile-guided compilation (PGO) 优化
- 支持压缩垃圾回收 (Concurrent Compact GC)
- Android 12+ 支持 ART Mainline 模块化更新
Native Libraries:
- C/C++ 原生库,通过 JNI 调用
- WebView:基于 Chromium 的渲染引擎
- MediaCodec:音视频编解码
- OpenGL ES / Vulkan:图形渲染
- SQLite:本地数据库引擎
- SSL/TLS:加密通信(BoringSSL)
1.1.4 硬件抽象层(Hardware Abstraction Layer, HAL)
HAL 为上层框架提供统一的硬件访问接口,屏蔽不同硬件厂商的驱动差异。每种硬件模块(Camera、Audio、Sensor、GPS 等)对应一个 HAL 接口实现。
应用层 (System Apps)
|
Java API Framework
|
Android Runtime (ART) / Native Libraries
|
硬件抽象层 (HAL)
|
Linux Kernel1.1.5 Linux Kernel
Android 基于 Linux 长期支持版(LTS)内核,包含以下增强:
- Binder IPC:进程间通信机制
- wakelocks:电源管理
- ashmem:匿名共享内存
- Low Memory Killer:低内存时杀进程
- SELinux:强制访问控制
- Treble 架构:Android 8.0+ 引入 Project Treble,将系统框架与厂商 HAL 解耦,加速系统更新
1.2 应用组件
1.2.1 Activity
Activity 是用户界面的入口,管理一个窗口的内容。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
}
override fun onResume() {
super.onResume()
}
override fun onPause() {
super.onPause()
}
override fun onStop() {
super.onStop()
}
override fun onDestroy() {
super.onDestroy()
}
// 处理配置变更(如屏幕旋转)
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
}
// 处理返回结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
}
}Activity 启动模式:
| 模式 | 说明 |
|---|---|
| standard | 默认模式,每次启动创建新实例 |
| singleTop | 如果栈顶已有该 Activity 实例,复用不重建 |
| singleTask | 如果栈中已有实例,将其上方的 Activity 全部出栈,复用该实例 |
| singleInstance | 该 Activity 单独在一个任务栈中 |
// AndroidManifest.xml 中配置启动模式
<activity
android:name=".ui.DetailActivity"
android:launchMode="singleTop"
android:configChanges="orientation|screenSize" />
// 传递参数
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("KEY_ID", 1001)
putExtra("KEY_TITLE", "Detail")
}
startActivity(intent)
// 或使用 Activity Result API
startActivityForResult(intent, REQUEST_CODE)1.2.2 Fragment
Fragment 是 Activity 内可重用的 UI 片段,有自己的生命周期。
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textView.text = "Home Fragment"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
// Activity 中添加 Fragment
supportFragmentManager.commit {
replace(R.id.fragment_container, HomeFragment())
addToBackStack(null)
}Fragment 生命周期: onAttach -> onCreate -> onCreateView -> onViewCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
1.2.3 Service
Service 用于执行后台长时间运行的操作,不提供用户界面。
// 前台服务
class DownloadService : Service() {
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Downloading")
.setContentText("File is downloading...")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.build()
startForeground(NOTIFICATION_ID, notification)
// 执行下载任务
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Download Service",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
override fun onDestroy() {
super.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
}
companion object {
const val CHANNEL_ID = "download_service_channel"
const val NOTIFICATION_ID = 1
}
}绑定服务(Bound Service):
class MyBoundService : Service() {
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
fun getService(): MyBoundService = this@MyBoundService
}
override fun onBind(intent: Intent?): IBinder = binder
fun getData(): String = "Hello from BoundService"
}
// 客户端绑定
class MainActivity : AppCompatActivity() {
private var myService: MyBoundService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
myService = (service as MyBoundService.LocalBinder).getService()
}
override fun onServiceDisconnected(name: ComponentName?) {
myService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindService(Intent(this, MyBoundService::class.java), connection, BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
unbindService(connection)
}
}1.2.4 BroadcastReceiver
BroadcastReceiver 用于接收系统或应用发送的广播事件。
// 静态注册(AndroidManifest.xml)
class BootCompletedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// 设备启动完成后的处理
}
}
}
// 动态注册
class MainActivity : AppCompatActivity() {
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_AIRPLANE_MODE_CHANGED) {
val isEnabled = intent.getBooleanExtra("state", false)
}
}
}
override fun onResume() {
super.onResume()
val filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
registerReceiver(receiver, filter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
}Android 14+ 广播限制:
- 应用无法向清单注册的非上下文广播发送隐式广播
- 必须使用
Context.registerReceiver()动态注册 - 导出广播需声明
RECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED
1.2.5 ContentProvider
ContentProvider 用于跨进程数据共享。
class MyContentProvider : ContentProvider() {
private lateinit var dbHelper: MyDbHelper
override fun onCreate(): Boolean {
dbHelper = MyDbHelper(context)
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
return dbHelper.readableDatabase.query(
"my_table", projection, selection, selectionArgs, null, null, sortOrder
)
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = dbHelper.writableDatabase.insert("my_table", null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
override fun update(uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?): Int {
return dbHelper.writableDatabase.update("my_table", values, selection, selectionArgs)
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return dbHelper.writableDatabase.delete("my_table", selection, selectionArgs)
}
override fun getType(uri: Uri): String? = null
}1.2.6 Intent
Intent 是组件间通信的载体,支持显式 Intent 和隐式 Intent。
// 显式 Intent:启动特定 Activity
val explicitIntent = Intent(this, DetailActivity::class.java)
startActivity(explicitIntent)
// 隐式 Intent:系统根据 action 匹配
val implicitIntent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://www.example.com")
}
startActivity(implicitIntent)
// Intent 传递数据
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("user_id", 123)
putExtra("user_name", "John")
putExtra("is_premium", true)
putExtra("items", arrayListOf("item1", "item2"))
}
startActivity(intent)
// 接收数据
val userId = intent.getIntExtra("user_id", 0)
val userName = intent.getStringExtra("user_name")
// Intent 中 Bundle 的限制
// Intent 内的 Bundle 数据大小限制约 500KB-1MB(不同 ROM 不同)
// 大数据量建议使用持久化存储 + 传递 ID1.2.7 Application
Application 是应用全局的单例,在应用进程创建时初始化。
class MyApplication : Application() {
lateinit var appContainer: AppContainer
private set
override fun onCreate() {
super.onCreate()
instance = this
appContainer = AppContainer(this)
// 初始化第三方 SDK
initThirdPartySDKs()
}
private fun initThirdPartySDKs() {
// LeakCanary
// if (LeakCanary.isInAnalyzerProcess(this)) return
// LeakCanary.install(this)
}
override fun onLowMemory() {
super.onLowMemory()
// 低内存时的清理
}
companion object {
lateinit var instance: MyApplication
private set
}
}
// AndroidManifest.xml 中声明
// <application android:name=".MyApplication" ... />1.3 Build 系统
1.3.1 Gradle + Kotlin DSL
Android 使用 Gradle 构建系统,推荐使用 Kotlin DSL 替代 Groovy。
// settings.gradle.kts
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyApp"
include(":app")
include(":core:network")
include(":core:ui")
include(":feature:home")
include(":feature:profile")// 根目录 build.gradle.kts
plugins {
id("com.android.application") version "8.2.0" apply false
id("com.android.library") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}1.3.2 Version Catalogs (libs.versions.toml)
使用 TOML 文件统一管理依赖版本。
# gradle/libs.versions.toml
[versions]
kotlin = "1.9.20"
agp = "8.2.0"
compose-bom = "2024.01.00"
compose-compiler = "1.5.5"
retrofit = "2.9.0"
okhttp = "4.12.0"
room = "2.6.1"
hilt = "2.48.1"
navigation-compose = "2.7.6"
coroutines = "1.7.3"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
compose-ui = { module = "androidx.compose.ui:ui" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-material-icons = { module = "androidx.compose.material:material-icons-extended" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.1.0" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }// app/build.gradle.kts 引用
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
android {
namespace = "com.example.myapp"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
isDebuggable = true
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
}
create("production") {
dimension = "environment"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.5"
}
}
dependencies {
implementation(project(":core:network"))
implementation(project(":core:ui"))
implementation(project(":feature:home"))
implementation(project(":feature:profile"))
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.material3)
implementation(libs.compose.navigation)
implementation(libs.retrofit)
implementation(libs.retrofit.gson)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.compiler)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
implementation(libs.hilt.navigation.compose)
implementation(libs.coroutines.core)
implementation(libs.coroutines.android)
}1.3.3 构建变体(Build Variants)
每个构建变体 = 构建类型 (Build Type) x 产品风味 (Product Flavor)。
构建类型: debug, release
产品风味: dev, staging, production
构建变体: devDebug, devRelease, stagingDebug, stagingRelease, productionDebug, productionRelease// 每个变体可配置独立参数
android {
productFlavors {
create("dev") {
dimension = "environment"
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
}
create("production") {
dimension = "environment"
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
}
}
}
// 代码中使用
// val baseUrl = BuildConfig.API_BASE_URL1.3.4 签名配置 (SigningConfig)
android {
signingConfigs {
create("release") {
storeFile = file("keystore/release.keystore")
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}1.3.5 ProGuard / R8 混淆
# proguard-rules.pro
# 保留实体类
-keep class com.example.myapp.data.model.** { *; }
# 保留 Retrofit 接口
-keep,allowobfuscation interface com.example.myapp.data.remote.*Api
# 保留 Gson 序列化类
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
# 保留 Kotlin 协程相关
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# 移除日志
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int d(...);
public static int i(...);
}// R8 全局配置 (gradle.properties)
android.enableR8.fullMode=true
android.enableR8.libraries=false1.4 AndroidManifest
1.4.1 基本结构
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 权限声明 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 硬件功能声明 -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<!-- SDK 版本 -->
<uses-sdk android:minSdkVersion="26"
android:targetSdkVersion="34"
android:maxSdkVersion="34" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="false"
tools:targetApi="34">
<!-- Activity 注册 -->
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.DetailActivity"
android:exported="false"
android:parentActivityName=".ui.MainActivity" />
<!-- Service 注册 -->
<service
android:name=".service.DownloadService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<!-- Receiver 注册 -->
<receiver
android:name=".receiver.BootCompletedReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Provider 注册 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup" />
</provider>
<!-- 元数据 -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
</application>
</manifest>1.4.2 Application 重要属性
| 属性 | 说明 |
|---|---|
android:allowBackup | 是否允许应用数据备份 |
android:dataExtractionRules | Android 12+ 数据提取规则 |
android:networkSecurityConfig | 网络安全配置(明文 HTTP、证书锁定) |
android:hardwareAccelerated | 是否启用硬件加速 |
android:largeHeap | 是否请求大堆内存 |
android:theme | 应用全局主题 |
android:supportsRtl | 是否支持从右到左布局 |
android:requestLegacyExternalStorage | Android 10+ 旧版存储模式(targetSdk 30+ 不应使用) |
1.4.3 intent-filter 匹配规则
<!-- 隐式 Intent 匹配 -->
<activity android:name=".ui.ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<!-- 深层链接 (Deep Link) -->
<activity android:name=".ui.DeepLinkActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.example.com"
android:pathPrefix="/product" />
</intent-filter>
</activity>2. Jetpack Compose 声明式 UI
2.1 Compose 基础
2.1.1 @Composable 函数
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Composable
fun UserProfile(user: User) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = user.name, style = MaterialTheme.typography.headlineMedium)
Text(text = user.email, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(8.dp))
Text(text = user.bio)
}
}2.1.2 基本布局组件
@Composable
fun LayoutDemo() {
// Column: 垂直排列
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
// Row: 水平排列
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Favorite, contentDescription = null)
Text("Like")
Button(onClick = { }) { Text("Action") }
}
// Box: 层叠布局(类似 FrameLayout)
Box(
modifier = Modifier
.size(200.dp)
.background(Color.Gray)
) {
Text(
text = "Center Text",
modifier = Modifier.align(Alignment.Center)
)
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp)
)
}
}2.1.3 Modifier 链
@Composable
fun ModifierChainDemo() {
Text(
text = "Styled Text",
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(Color.LightGray, shape = RoundedCornerShape(8.dp))
.padding(12.dp)
.clickable { /* handle click */ }
.then(Modifier.shadow(4.dp))
)
}2.1.4 常用组件
@Composable
fun CommonComponents() {
// Text
Text(
text = "Hello Compose",
color = MaterialTheme.colorScheme.primary,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 2
)
// Image
Image(
painter = painterResource(R.drawable.avatar),
contentDescription = "Avatar",
modifier = Modifier
.size(48.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
// Button
Button(
onClick = { /* action */ },
enabled = true,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
shape = RoundedCornerShape(8.dp)
) {
Text("Submit")
}
// TextField
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
isError = text.length < 3
)
// Icon
Icon(
imageVector = Icons.Default.Settings,
contentDescription = "Settings",
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}2.1.5 Scaffold 与 TopAppBar / BottomBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
val navController = rememberNavController()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
navigationIcon = {
IconButton(onClick = { /* open drawer */ }) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
},
actions = {
IconButton(onClick = { /* search */ }) {
Icon(Icons.Default.Search, contentDescription = "Search")
}
IconButton(onClick = { /* settings */ }) {
Icon(Icons.Default.MoreVert, contentDescription = "More")
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
},
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("profile") { ProfileScreen() }
composable("settings") { SettingsScreen() }
}
}
}
}2.1.6 列表:LazyColumn / LazyRow
@Composable
fun UserList(users: List<User>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// 固定头部
item {
Text(
text = "Users (${users.size})",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
}
// 使用索引
itemsIndexed(users) { index, user ->
UserCard(user = user, isEven = index % 2 == 0)
}
// 分隔线
items(users.size) { index ->
UserRow(user = users[index])
if (index < users.size - 1) {
Divider()
}
}
// 底部加载状态
item {
if (isLoading) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
}
}
// LazyRow 水平滚动列表
@Composable
fun CategoryRow(categories: List<Category>) {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(categories) { category ->
CategoryChip(category = category)
}
}
}2.2 状态管理
2.2.1 State / MutableState
@Composable
fun Counter() {
// remember: 在重组时保持状态
var count by remember { mutableStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text("Count: $count", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { count++ }) {
Text("Increment")
}
}
}2.2.2 rememberSaveable
在配置变更(如屏幕旋转)后仍然保持状态。
@Composable
fun SaveableStateDemo() {
// rememberSaveable 通过 Bundle 保存状态,支持配置变更后的恢复
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Input") }
)
}
// 自定义 Saver
data class UserState(val name: String, val age: Int)
val UserStateSaver = run {
val nameKey = "name"
val ageKey = "age"
mapSaver(
save = { mapOf(nameKey to it.name, ageKey to it.age) },
restore = { UserState(it[nameKey] as String, it[ageKey] as Int) }
)
}
@Composable
fun CustomSaverDemo() {
var userState by rememberSaveable(stateSaver = UserStateSaver) {
mutableStateOf(UserState("", 0))
}
}2.2.3 derivedStateOf
从其他状态派生新状态,避免不必要的重组。
@Composable
fun TodoList(todos: List<Todo>) {
val completedCount by remember {
derivedStateOf { todos.count { it.isCompleted } }
}
val progress by remember {
derivedStateOf {
if (todos.isEmpty()) 0f
else completedCount.toFloat() / todos.size
}
}
Text("Completed: $completedCount / ${todos.size}")
LinearProgressIndicator(progress = progress)
}2.2.4 StateFlow 与 collectAsState
// ViewModel
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MainUiState())
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
private val _loadingState = MutableStateFlow(false)
val loadingState: StateFlow<Boolean> = _loadingState.asStateFlow()
fun loadData() {
viewModelScope.launch {
_loadingState.value = true
try {
val data = repository.fetchData()
_uiState.update { it.copy(data = data, error = null) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message) }
} finally {
_loadingState.value = false
}
}
}
}
data class MainUiState(
val data: List<String> = emptyList(),
val error: String? = null
)
// Composable
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
val isLoading by viewModel.loadingState.collectAsState()
Box(modifier = Modifier.fillMaxSize()) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
uiState.error?.let { error ->
Text("Error: $error", color = MaterialTheme.colorScheme.error)
}
LazyColumn {
items(uiState.data) { item ->
Text(item)
}
}
}
}2.2.5 ViewModel 与 viewModel()
// 使用 hiltViewModel()
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()
init {
loadProfile()
}
private fun loadProfile() {
viewModelScope.launch {
_user.value = userRepository.getCurrentUser()
}
}
}
@Composable
fun ProfileScreen(
viewModel: ProfileViewModel = hiltViewModel()
) {
val user by viewModel.user.collectAsState()
user?.let {
Text("Welcome, ${it.name}")
}
}2.2.6 SideEffect
@Composable
fun SideEffectDemo() {
// LaunchedEffect: 进入 Composition 时启动协程,离开时取消
LaunchedEffect(Unit) {
// 相当于 lifecycleScope.launch
val data = repository.fetchData()
// 更新 UI 状态
}
// LaunchedEffect 的 key 变化时会重启协程
var userId by remember { mutableStateOf("") }
LaunchedEffect(userId) {
if (userId.isNotBlank()) {
val user = repository.getUser(userId)
// 更新 UI
}
}
// DisposableEffect: 进入时执行,离开时清理
DisposableEffect(Unit) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> { /* 前台 */ }
Lifecycle.Event.ON_PAUSE -> { /* 后台 */ }
else -> {}
}
}
val lifecycle = LocalLifecycleOwner.current.lifecycle
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
// rememberCoroutineScope: 获取一个作用域,在 Composable 外部启动协程
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
val result = repository.performAction()
// 处理结果
}
}) {
Text("Execute")
}
// snapshotFlow: 将 Composable 状态转换为 Flow
var searchQuery by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
snapshotFlow { searchQuery }
.debounce(300)
.filter { it.length >= 2 }
.collect { query ->
// 防抖搜索
repository.search(query)
}
}
// SideEffect: 在每次重组后执行,不影响 Composition
SideEffect {
// 告诉外部系统当前的状态
analytics.trackScreen("Profile")
}
}2.3 导航(Navigation Compose)
2.3.1 基本导航
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToDetail = { itemId ->
navController.navigate("detail/$itemId")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(
itemId = itemId,
onNavigateBack = { navController.popBackStack() }
)
}
}
}
// 传递复杂参数
composable(
route = "profile/{userId}?source={source}",
arguments = listOf(
navArgument("userId") { type = NavType.IntType },
navArgument("source") {
type = NavType.StringType
defaultValue = "direct"
}
)
) { entry ->
ProfileScreen(
userId = entry.arguments?.getInt("userId") ?: 0,
source = entry.arguments?.getString("source") ?: "direct"
)
}
// 导航
navController.navigate("profile/123?source=notification")2.3.2 深层链接(Deep Link)
// 定义深层链接
composable(
route = "product/{productId}",
arguments = listOf(navArgument("productId") { type = NavType.IntType }),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://product/{productId}" },
navDeepLink { uriPattern = "https://www.example.com/product/{productId}" }
)
) { entry ->
val productId = entry.arguments?.getInt("productId") ?: 0
ProductScreen(productId)
}
// Activity 中处理深层链接
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// intent.data 中携带深层链接 URI
val deepLinkUri = intent.data?.toString()
}
}2.3.3 Bottom Navigation 集成
sealed class Screen(val route: String, val label: String, val icon: ImageVector) {
object Home : Screen("home", "Home", Icons.Default.Home)
object Explore : Screen("explore", "Explore", Icons.Default.Explore)
object Profile : Screen("profile", "Profile", Icons.Default.Person)
}
val screens = listOf(Screen.Home, Screen.Explore, Screen.Profile)
@Composable
fun BottomNavApp() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
screens.forEach { screen ->
NavigationBarItem(
icon = { Icon(screen.icon, contentDescription = screen.label) },
label = { Text(screen.label) },
selected = currentDestination?.route == screen.route,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) { HomeScreen() }
composable(Screen.Explore.route) { ExploreScreen() }
composable(Screen.Profile.route) { ProfileScreen() }
}
}
}
}2.3.4 导航动画
NavHost(
navController = navController,
startDestination = "list"
) {
composable(
route = "list",
enterTransition = { fadeIn(animationSpec = tween(300)) },
exitTransition = { fadeOut(animationSpec = tween(300)) }
) { ListScreen() }
composable(
route = "detail/{id}",
enterTransition = {
slideInHorizontally(
initialOffsetX = { it },
animationSpec = tween(300)
)
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { -it },
animationSpec = tween(300)
)
},
popEnterTransition = {
slideInHorizontally(
initialOffsetX = { -it },
animationSpec = tween(300)
)
},
popExitTransition = {
slideOutHorizontally(
targetOffsetX = { it },
animationSpec = tween(300)
)
}
) { entry ->
DetailScreen(id = entry.arguments?.getInt("id") ?: 0)
}
}2.4 主题
2.4.1 MaterialTheme
// 自定义颜色
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF1976D2),
onPrimary = Color.White,
primaryContainer = Color(0xFFBBDEFB),
secondary = Color(0xFF43A047),
onSecondary = Color.White,
background = Color(0xFFF5F5F5),
surface = Color.White,
error = Color(0xFFD32F2F),
onBackground = Color(0xFF212121),
onSurface = Color(0xFF212121)
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF90CAF9),
onPrimary = Color(0xFF0D47A1),
primaryContainer = Color(0xFF1565C0),
secondary = Color(0xFFA5D6A7),
onSecondary = Color(0xFF1B5E20),
background = Color(0xFF121212),
surface = Color(0xFF1E1E1E),
error = Color(0xFFEF5350),
onBackground = Color(0xFFE0E0E0),
onSurface = Color(0xFFE0E0E0)
)
// 自定义字体
private val AppTypography = Typography(
displayLarge = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp
),
headlineLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 32.sp,
lineHeight = 40.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
labelLarge = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
)
)
// 自定义形状
private val AppShapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
)