技术文档中心
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
  • 基础入门

    • Android 开发指南
    • Kotlin 基础
  • 进阶内容

    • Jetpack 组件
    • MVVM 架构
    • Jetpack Compose

Jetpack 组件

ViewModel

基本使用

class UserViewModel : ViewModel() {
    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> = _users

    fun loadUsers() {
        viewModelScope.launch {
            val data = repository.getUsers()
            _users.value = data
        }
    }
}

// Activity 中使用
class MainActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        viewModel.users.observe(this) { users ->
            // 更新 UI
        }
        
        viewModel.loadUsers()
    }
}

传递参数

class UserViewModel(private val userId: Int) : ViewModel() {
    // ...
}

class UserViewModelFactory(private val userId: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return UserViewModel(userId) as T
    }
}

// 使用
val viewModel: UserViewModel by viewModels {
    UserViewModelFactory(userId)
}

LiveData

基本使用

class UserViewModel : ViewModel() {
    private val _name = MutableLiveData<String>()
    val name: LiveData<String> = _name

    fun updateName(newName: String) {
        _name.value = newName
    }
}

// 观察数据
viewModel.name.observe(this) { name ->
    textView.text = name
}

Transformations

// map
val userName: LiveData<String> = Transformations.map(user) {
    it.name
}

// switchMap
val users: LiveData<List<User>> = Transformations.switchMap(query) { q ->
    repository.searchUsers(q)
}

// MediatorLiveData
val result = MediatorLiveData<String>()
result.addSource(source1) { value ->
    result.value = value
}
result.addSource(source2) { value ->
    result.value = value
}

Room 数据库

Entity(实体)

@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    
    @ColumnInfo(name = "user_name")
    val name: String,
    
    val age: Int,
    
    @Ignore
    val temp: String? = null
)

DAO(数据访问对象)

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAll(): List<User>

    @Query("SELECT * FROM users WHERE id = :userId")
    fun getById(userId: Int): User?

    @Query("SELECT * FROM users WHERE user_name LIKE :search")
    fun search(search: String): List<User>

    @Insert
    suspend fun insert(user: User): Long

    @Update
    suspend fun update(user: User)

    @Delete
    suspend fun delete(user: User)

    @Query("DELETE FROM users")
    suspend fun deleteAll()
}

Database

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

使用

class UserRepository(private val userDao: UserDao) {
    suspend fun getAllUsers(): List<User> {
        return userDao.getAll()
    }

    suspend fun insertUser(user: User) {
        userDao.insert(user)
    }
}

// ViewModel 中
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    fun loadUsers() {
        viewModelScope.launch {
            val users = repository.getAllUsers()
            _users.value = users
        }
    }
}

Navigation

配置

<!-- res/navigation/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.HomeFragment"
        android:label="Home">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="Detail">
        <argument
            android:name="userId"
            app:argType="integer" />
    </fragment>
</navigation>

Activity 布局

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/nav_graph"
    app:defaultNavHost="true" />

导航

// 基本导航
findNavController().navigate(R.id.action_home_to_detail)

// 传递参数
val bundle = bundleOf("userId" to 123)
findNavController().navigate(R.id.action_home_to_detail, bundle)

// 接收参数
val args: DetailFragmentArgs by navArgs()
val userId = args.userId

// 返回
findNavController().navigateUp()

WorkManager

定义 Worker

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            val data = inputData.getString("data")
            // 执行上传任务
            uploadData(data)
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

创建请求

// 一次性任务
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInputData(workDataOf("data" to "content"))
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueue(uploadRequest)

// 周期性任务
val periodicWork = PeriodicWorkRequestBuilder<UploadWorker>(
    15, TimeUnit.MINUTES
).build()

WorkManager.getInstance(context).enqueue(periodicWork)

观察状态

WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(uploadRequest.id)
    .observe(this) { workInfo ->
        when (workInfo.state) {
            WorkInfo.State.SUCCEEDED -> {
                // 成功
            }
            WorkInfo.State.FAILED -> {
                // 失败
            }
            WorkInfo.State.RUNNING -> {
                // 运行中
            }
        }
    }

DataStore

Preferences DataStore

// 创建
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

// 保存
suspend fun saveValue(key: String, value: String) {
    context.dataStore.edit { preferences ->
        preferences[stringPreferencesKey(key)] = value
    }
}

// 读取
val valueFlow: Flow<String> = context.dataStore.data
    .map { preferences ->
        preferences[stringPreferencesKey(key)] ?: ""
    }

Proto DataStore

// 定义 proto
syntax = "proto3";

message Settings {
  string theme = 1;
  bool notifications = 2;
}

// 使用
val Context.settingsDataStore: DataStore<Settings> by dataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)

// 读取
val settingsFlow: Flow<Settings> = context.settingsDataStore.data

// 更新
suspend fun updateTheme(theme: String) {
    context.settingsDataStore.updateData { currentSettings ->
        currentSettings.toBuilder()
            .setTheme(theme)
            .build()
    }
}

Paging 3

PagingSource

class UserPagingSource(
    private val api: ApiService
) : PagingSource<Int, User>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        return try {
            val page = params.key ?: 1
            val response = api.getUsers(page, params.loadSize)
            
            LoadResult.Page(
                data = response.data,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.data.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, User>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }
}

ViewModel

class UserViewModel(private val api: ApiService) : ViewModel() {
    val users: Flow<PagingData<User>> = Pager(
        config = PagingConfig(
            pageSize = 20,
            enablePlaceholders = false
        ),
        pagingSourceFactory = { UserPagingSource(api) }
    ).flow.cachedIn(viewModelScope)
}

Adapter

class UserAdapter : PagingDataAdapter<User, UserViewHolder>(UserComparator) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        return UserViewHolder(/* ... */)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        holder.bind(user)
    }

    object UserComparator : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
}

使用

class MainActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()
    private val adapter = UserAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        recyclerView.adapter = adapter
        
        lifecycleScope.launch {
            viewModel.users.collectLatest { pagingData ->
                adapter.submitData(pagingData)
            }
        }
    }
}

Hilt 依赖注入

配置

// Application
@HiltAndroidApp
class MyApplication : Application()

// Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity()

// Fragment
@AndroidEntryPoint
class HomeFragment : Fragment()

// ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel()

Module

@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
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

使用

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    @Inject
    lateinit var repository: UserRepository
    
    private val viewModel: UserViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // repository 已自动注入
    }
}

Compose(现代 UI)

基本组件

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Composable
fun UserCard(user: User) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = user.name,
                style = MaterialTheme.typography.h6
            )
            Text(
                text = "Age: ${user.age}",
                style = MaterialTheme.typography.body2
            )
        }
    }
}

状态管理

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

// ViewModel
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val users by viewModel.users.observeAsState(emptyList())
    
    LazyColumn {
        items(users) { user ->
            UserCard(user)
        }
    }
}

导航

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "home") {
        composable("home") {
            HomeScreen(
                onNavigateToDetail = { userId ->
                    navController.navigate("detail/$userId")
                }
            )
        }
        composable("detail/{userId}") { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId")
            DetailScreen(userId)
        }
    }
}
最近更新: 2026/2/24 16:53
Contributors: hailong
Next
MVVM 架构