引言
Android开发是一个充满活力且不断演进的领域。从简单的应用到复杂的系统级应用,开发者需要掌握从UI设计、数据处理到性能优化的全方位技能。本文将通过几个典型的实战案例,深入解析Android开发中的关键技术点,并针对常见问题提供详细的解决方案。无论你是初学者还是有经验的开发者,都能从中获得实用的指导。
案例一:使用Jetpack Compose构建现代UI
背景与需求
随着Android开发的演进,Jetpack Compose已成为构建原生UI的推荐方式。它采用声明式编程范式,简化了UI开发流程。本案例将展示如何使用Compose构建一个简单的待办事项(To-Do)应用,包括添加、删除和标记完成任务的功能。
技术要点
- State管理:使用
remember和mutableStateOf来管理UI状态。 - 组合函数:将UI分解为可重用的函数。
- Material Design组件:利用Compose内置的Material组件构建美观的界面。
实现步骤
- 项目设置:在
build.gradle文件中添加Compose依赖。 - 创建数据模型:定义一个
TodoItem类来表示待办事项。 - 构建UI:使用Compose函数构建界面,包括输入框、按钮和列表。
- 状态管理:使用
mutableStateListOf来管理待办事项列表。
代码示例
// TodoItem.kt
data class TodoItem(
val id: Int,
val text: String,
val isCompleted: Boolean = false
)
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TodoAppTheme {
TodoScreen()
}
}
}
}
@Composable
fun TodoScreen() {
var textInput by remember { mutableStateOf("") }
val todoList = remember { mutableStateListOf<TodoItem>() }
var nextId by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 输入区域
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
value = textInput,
onValueChange = { textInput = it },
label = { Text("输入待办事项") },
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
if (textInput.isNotBlank()) {
todoList.add(TodoItem(nextId, textInput))
nextId++
textInput = ""
}
}) {
Text("添加")
}
}
Spacer(modifier = Modifier.height(16.dp))
// 待办事项列表
LazyColumn {
items(todoList) { item ->
TodoItemRow(
item = item,
onToggle = {
val index = todoList.indexOfFirst { it.id == item.id }
if (index != -1) {
todoList[index] = item.copy(isCompleted = !item.isCompleted)
}
},
onDelete = {
todoList.removeAll { it.id == item.id }
}
)
}
}
}
}
@Composable
fun TodoItemRow(
item: TodoItem,
onToggle: () -> Unit,
onDelete: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = item.isCompleted,
onCheckedChange = { onToggle() }
)
Text(
text = item.text,
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
textDecoration = if (item.isCompleted) TextDecoration.LineThrough else null
)
IconButton(onClick = onDelete) {
Icon(Icons.Default.Delete, contentDescription = "删除")
}
}
}
常见问题与解决方案
问题:Compose性能问题
- 原因:频繁的重组或不必要的状态更新。
- 解决方案:使用
remember和derivedStateOf来避免不必要的重组。例如,对于大型列表,使用LazyColumn而不是Column。 - 代码示例:
“`kotlin
// 错误的做法:每次状态变化都会重组整个列表
@Composable
fun BadListExample() {
val list = remember { mutableStateListOf
() } Column { list.forEach { item -> Text(item.text) // 每次更新都会重组所有项 } } }
// 正确的做法:使用LazyColumn,只重组可见项 @Composable fun GoodListExample() {
val list = remember { mutableStateListOf<TodoItem>() } LazyColumn { items(list) { item -> Text(item.text) // 只重组可见项 } }} “`
问题:状态管理混乱
原因:在多个地方管理相同的状态,导致数据不一致。
解决方案:使用单一数据源(Single Source of Truth)原则,将状态提升到父组件或使用ViewModel。
代码示例: “`kotlin // 使用ViewModel管理状态 class TodoViewModel : ViewModel() { private val _todoList = MutableStateFlow
- >(emptyList())
val todoList: StateFlow
- > = _todoList.asStateFlow()
fun addTodo(text: String) {
val newItem = TodoItem(System.currentTimeMillis().toInt(), text) _todoList.update { it + newItem }}
fun toggleTodo(id: Int) {
_todoList.update { list -> list.map { item -> if (item.id == id) item.copy(isCompleted = !item.isCompleted) else item } }} }
// 在Compose中使用ViewModel @Composable fun TodoScreen(viewModel: TodoViewModel = viewModel()) {
val todoList by viewModel.todoList.collectAsState() // ... UI代码} “`
案例二:使用Retrofit与Coroutines处理网络请求
背景与需求
在移动应用中,网络请求是必不可少的功能。本案例将展示如何使用Retrofit和Kotlin Coroutines来高效地处理网络请求,并处理错误和加载状态。
技术要点
- Retrofit:用于定义HTTP API接口。
- Kotlin Coroutines:用于异步操作,避免回调地狱。
- StateFlow:用于管理网络请求的状态(加载中、成功、失败)。
实现步骤
- 添加依赖:在
build.gradle中添加Retrofit、Gson和Coroutines依赖。 - 定义API接口:使用Retrofit注解定义API端点。
- 创建Repository:封装网络请求逻辑。
- 使用ViewModel:管理UI状态和业务逻辑。
- 在Compose中观察状态:根据状态更新UI。
代码示例
// 1. 定义API接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@GET("posts")
suspend fun getPosts(): List<Post>
}
// 2. 定义数据模型
data class User(
val id: Int,
val name: String,
val email: String
)
data class Post(
val userId: Int,
val id: Int,
val title: String,
val body: String
)
// 3. 创建Retrofit实例
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
// 4. 创建Repository
class UserRepository {
suspend fun getUser(userId: Int): Result<User> {
return try {
val user = RetrofitClient.apiService.getUser(userId)
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun getPosts(): Result<List<Post>> {
return try {
val posts = RetrofitClient.apiService.getPosts()
Result.success(posts)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// 5. 创建ViewModel
class UserViewModel : ViewModel() {
private val repository = UserRepository()
private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
val userState: StateFlow<UiState<User>> = _userState.asStateFlow()
private val _postsState = MutableStateFlow<UiState<List<Post>>>(UiState.Loading)
val postsState: StateFlow<UiState<List<Post>>> = _postsState.asStateFlow()
fun fetchUser(userId: Int) {
viewModelScope.launch {
_userState.value = UiState.Loading
val result = repository.getUser(userId)
_userState.value = when {
result.isSuccess -> UiState.Success(result.getOrNull()!!)
else -> UiState.Error(result.exceptionOrNull()?.message ?: "Unknown error")
}
}
}
fun fetchPosts() {
viewModelScope.launch {
_postsState.value = UiState.Loading
val result = repository.getPosts()
_postsState.value = when {
result.isSuccess -> UiState.Success(result.getOrNull()!!)
else -> UiState.Error(result.exceptionOrNull()?.message ?: "Unknown error")
}
}
}
}
// 6. 定义UI状态
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
// 7. 在Compose中使用
@Composable
fun UserProfileScreen(userId: Int, viewModel: UserViewModel = viewModel()) {
val userState by viewModel.userState.collectAsState()
val postsState by viewModel.postsState.collectAsState()
LaunchedEffect(userId) {
viewModel.fetchUser(userId)
viewModel.fetchPosts()
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
when (val state = userState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> {
Text(text = "用户: ${state.data.name}", style = MaterialTheme.typography.h6)
Text(text = "邮箱: ${state.data.email}")
}
is UiState.Error -> {
Text(text = "错误: ${state.message}", color = Color.Red)
}
}
Spacer(modifier = Modifier.height(16.dp))
when (val state = postsState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> {
LazyColumn {
items(state.data) { post ->
PostItem(post)
}
}
}
is UiState.Error -> {
Text(text = "错误: ${state.message}", color = Color.Red)
}
}
}
}
@Composable
fun PostItem(post: Post) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
elevation = 4.dp
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = post.title, style = MaterialTheme.typography.subtitle1)
Text(text = post.body, style = MaterialTheme.typography.body2)
}
}
}
常见问题与解决方案
问题:网络请求在后台线程执行,但UI更新需要在主线程
- 原因:Retrofit的协程默认在后台线程执行,但Compose的UI更新必须在主线程。
- 解决方案:使用
Dispatchers.Main来确保UI更新在主线程执行。 - 代码示例: “`kotlin // 错误的做法:在后台线程更新UI viewModelScope.launch(Dispatchers.IO) { val result = repository.getUser(userId) // 这里更新UI会崩溃,因为不在主线程 _userState.value = UiState.Success(result.getOrNull()!!) }
// 正确的做法:使用Dispatchers.Main viewModelScope.launch(Dispatchers.Main) {
_userState.value = UiState.Loading val result = withContext(Dispatchers.IO) { repository.getUser(userId) } _userState.value = when { result.isSuccess -> UiState.Success(result.getOrNull()!!) else -> UiState.Error(result.exceptionOrNull()?.message ?: "Unknown error") }} “`
问题:内存泄漏
原因:在ViewModel中使用协程作用域,但未正确取消协程。
解决方案:使用
viewModelScope,它会在ViewModel清除时自动取消所有协程。代码示例: “`kotlin // 错误的做法:使用自定义的协程作用域 class BadViewModel : ViewModel() { private val scope = CoroutineScope(Dispatchers.IO) // 可能导致内存泄漏
fun fetchData() {
scope.launch { // 长时间运行的任务 }} }
// 正确的做法:使用viewModelScope class GoodViewModel : ViewModel() {
fun fetchData() { viewModelScope.launch { // 长时间运行的任务 } }} “`
案例三:使用Room数据库进行本地数据存储
背景与需求
本地数据存储是Android应用的重要组成部分。本案例将展示如何使用Room数据库来存储和查询数据,包括实体定义、DAO接口和数据库类。
技术要点
- Room:Android Jetpack的一部分,提供SQLite的抽象层。
- Entity:定义数据表结构。
- DAO:定义数据访问操作。
- Database:定义数据库实例。
实现步骤
- 添加依赖:在
build.gradle中添加Room依赖。 - 定义实体:使用
@Entity注解定义数据表。 - 创建DAO:使用
@Dao注解定义数据访问操作。 - 创建数据库类:使用
@Database注解定义数据库。 - 在ViewModel中使用:通过Repository模式访问数据库。
代码示例
// 1. 定义实体
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
)
// 2. 定义DAO
@Dao
interface NoteDao {
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Query("SELECT * FROM notes WHERE id = :noteId")
suspend fun getNoteById(noteId: Int): Note?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertNote(note: Note)
@Update
suspend fun updateNote(note: Note)
@Delete
suspend fun deleteNote(note: Note)
}
// 3. 定义数据库
@Database(
entities = [Note::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
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
}
}
}
}
// 4. 创建Repository
class NoteRepository(private val noteDao: NoteDao) {
fun getAllNotes(): Flow<List<Note>> = noteDao.getAllNotes()
suspend fun getNoteById(noteId: Int): Note? = noteDao.getNoteById(noteId)
suspend fun insertNote(note: Note) = noteDao.insertNote(note)
suspend fun updateNote(note: Note) = noteDao.updateNote(note)
suspend fun deleteNote(note: Note) = noteDao.deleteNote(note)
}
// 5. 创建ViewModel
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
private val _notes = MutableStateFlow<List<Note>>(emptyList())
val notes: StateFlow<List<Note>> = _notes.asStateFlow()
init {
val noteDao = AppDatabase.getDatabase(application).noteDao()
repository = NoteRepository(noteDao)
viewModelScope.launch {
repository.getAllNotes().collect { notes ->
_notes.value = notes
}
}
}
fun addNote(title: String, content: String) {
viewModelScope.launch {
val note = Note(title = title, content = content)
repository.insertNote(note)
}
}
fun deleteNote(note: Note) {
viewModelScope.launch {
repository.deleteNote(note)
}
}
}
// 6. 在Compose中使用
@Composable
fun NoteScreen(viewModel: NoteViewModel = viewModel()) {
val notes by viewModel.notes.collectAsState()
var showDialog by remember { mutableStateOf(false) }
var title by remember { mutableStateOf("") }
var content by remember { mutableStateOf("") }
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { showDialog = true }) {
Icon(Icons.Default.Add, contentDescription = "添加笔记")
}
}
) { padding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(padding)
) {
items(notes) { note ->
NoteItem(
note = note,
onDelete = { viewModel.deleteNote(note) }
)
}
}
}
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("添加新笔记") },
text = {
Column {
OutlinedTextField(
value = title,
onValueChange = { title = it },
label = { Text("标题") }
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = content,
onValueChange = { content = it },
label = { Text("内容") },
maxLines = 5
)
}
},
confirmButton = {
Button(
onClick = {
if (title.isNotBlank() && content.isNotBlank()) {
viewModel.addNote(title, content)
title = ""
content = ""
showDialog = false
}
}
) {
Text("保存")
}
},
dismissButton = {
TextButton(onClick = { showDialog = false }) {
Text("取消")
}
}
)
}
}
@Composable
fun NoteItem(note: Note, onDelete: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = 4.dp
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = note.title, style = MaterialTheme.typography.h6)
Text(text = note.content, style = MaterialTheme.typography.body1)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "创建时间: ${Date(note.createdAt)}",
style = MaterialTheme.typography.caption,
color = Color.Gray
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onDelete,
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
) {
Text("删除", color = Color.White)
}
}
}
}
常见问题与解决方案
问题:数据库操作在主线程执行导致ANR
- 原因:Room的suspend函数默认在调用线程执行,如果在主线程调用会阻塞UI。
- 解决方案:确保数据库操作在后台线程执行,使用
withContext(Dispatchers.IO)。 - 代码示例: “`kotlin // 错误的做法:在主线程执行数据库操作 viewModelScope.launch { // 这里在主线程执行,可能导致ANR repository.insertNote(note) }
// 正确的做法:在后台线程执行 viewModelScope.launch {
withContext(Dispatchers.IO) { repository.insertNote(note) }} “`
问题:数据库迁移问题
- 原因:当数据库结构发生变化时,需要处理数据迁移。
- 解决方案:使用Room的Migration API来处理数据库版本升级。
- 代码示例: “`kotlin // 定义数据库版本1到2的迁移 val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL(“ALTER TABLE notes ADD COLUMN priority INTEGER DEFAULT 0”) } }
// 在数据库构建时应用迁移 Room.databaseBuilder(
context.applicationContext, AppDatabase::class.java, "app_database") .addMigrations(MIGRATION_1_2) .build() “`
案例四:使用WorkManager进行后台任务调度
背景与需求
后台任务调度是Android开发中的常见需求,如数据同步、定期清理等。WorkManager是Android Jetpack的一部分,用于管理可延迟的、保证执行的后台任务。
技术要点
- WorkManager:用于调度后台任务,支持一次性或周期性任务。
- Worker:定义要执行的任务。
- WorkRequest:定义任务的约束和参数。
- WorkManager:调度和管理任务。
实现步骤
- 添加依赖:在
build.gradle中添加WorkManager依赖。 - 创建Worker类:继承
Worker类并实现doWork()方法。 - 定义WorkRequest:设置任务的约束和参数。
- 调度任务:使用WorkManager调度任务。
- 观察任务状态:使用
WorkManager.getWorkInfoLiveData()观察任务状态。
代码示例
// 1. 创建Worker类
class SyncWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
return try {
// 模拟同步操作
delay(2000) // 模拟耗时操作
// 这里可以执行实际的同步逻辑,如网络请求
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
}
// 2. 创建周期性任务Worker
class PeriodicSyncWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
return try {
// 执行周期性同步
delay(1000)
// 这里可以执行实际的同步逻辑
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
}
// 3. 在Activity或Fragment中调度任务
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 调度一次性任务
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setInputData(workDataOf("key" to "value"))
.build()
WorkManager.getInstance(this).enqueue(syncRequest)
// 调度周期性任务(最小间隔15分钟)
val periodicSyncRequest = PeriodicWorkRequestBuilder<PeriodicSyncWorker>(
15, TimeUnit.MINUTES
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"periodic_sync",
ExistingPeriodicWorkPolicy.KEEP,
periodicSyncRequest
)
// 观察任务状态
WorkManager.getInstance(this).getWorkInfoByIdLiveData(syncRequest.id)
.observe(this) { workInfo ->
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> {
// 任务成功
Toast.makeText(this, "同步成功", Toast.LENGTH_SHORT).show()
}
WorkInfo.State.FAILED -> {
// 任务失败
Toast.makeText(this, "同步失败", Toast.LENGTH_SHORT).show()
}
else -> {
// 其他状态
}
}
}
}
}
// 4. 在Compose中使用WorkManager
@Composable
fun WorkManagerScreen() {
val context = LocalContext.current
val workManager = WorkManager.getInstance(context)
val workInfoLiveData = workManager.getWorkInfosForUniqueWorkLiveData("periodic_sync")
val workInfos by workInfoLiveData.observeAsState(emptyList())
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(onClick = {
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
workManager.enqueue(syncRequest)
}) {
Text("启动同步任务")
}
Spacer(modifier = Modifier.height(16.dp))
Text("任务状态:", style = MaterialTheme.typography.h6)
workInfos.forEach { workInfo ->
Text(
text = "ID: ${workInfo.id}, 状态: ${workInfo.state}",
modifier = Modifier.padding(vertical = 4.dp)
)
}
}
}
常见问题与解决方案
问题:WorkManager任务在设备重启后丢失
- 原因:WorkManager默认在设备重启后会重新调度任务,但可能需要额外配置。
- 解决方案:使用
setInitialDelay或setPeriodic来确保任务在重启后继续执行。 - 代码示例:
// 确保任务在设备重启后继续执行 val periodicSyncRequest = PeriodicWorkRequestBuilder<PeriodicSyncWorker>( 15, TimeUnit.MINUTES ) .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build() ) .setInitialDelay(15, TimeUnit.MINUTES) // 初始延迟 .build()
问题:WorkManager任务执行时间过长
- 原因:Worker的
doWork()方法执行时间过长,可能导致系统杀死进程。 - 解决方案:将长时间运行的任务分解为多个小任务,或使用
CoroutineWorker并合理使用withContext。 - 代码示例: “`kotlin // 错误的做法:长时间运行的任务 class LongRunningWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { // 长时间运行的任务,可能被系统杀死 Thread.sleep(60000) // 60秒 return Result.success() } }
// 正确的做法:使用CoroutineWorker并合理调度 class BetterWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { return withContext(Dispatchers.IO) { // 分解任务为多个小任务 for (i in 1..10) { delay(1000) // 每次执行1秒 // 执行小任务 } Result.success() } }} “`
- 原因:Worker的
案例五:使用Hilt进行依赖注入
背景与需求
依赖注入(Dependency Injection)是管理复杂应用依赖关系的优秀实践。Hilt是Google官方推荐的依赖注入框架,基于Dagger构建,简化了依赖注入的配置。
技术要点
- Hilt:基于Dagger的依赖注入框架。
- Module:定义如何提供依赖。
- Component:定义依赖的生命周期。
- Scope:定义依赖的生命周期范围。
实现步骤
- 添加依赖:在
build.gradle中添加Hilt依赖。 - 创建Application类:使用
@HiltAndroidApp注解。 - 定义Module:使用
@Module和@Provides注解定义依赖提供方式。 - 使用
@Inject注解:在需要依赖的地方使用@Inject。 - 在Activity/Fragment中使用:使用
@AndroidEntryPoint注解。
代码示例
// 1. 创建Application类
@HiltAndroidApp
class MyApplication : Application()
// 2. 定义Module
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Provides
@Singleton
fun provideDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
}
// 3. 定义Repository
class UserRepository @Inject constructor(
private val apiService: ApiService,
private val database: AppDatabase
) {
suspend fun getUser(userId: Int): User {
// 先从数据库获取,如果不存在则从网络获取
val localUser = database.userDao().getUserById(userId)
if (localUser != null) return localUser
val remoteUser = apiService.getUser(userId)
database.userDao().insertUser(remoteUser)
return remoteUser
}
}
// 4. 定义ViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
val userState: StateFlow<UiState<User>> = _userState.asStateFlow()
fun fetchUser(userId: Int) {
viewModelScope.launch {
_userState.value = UiState.Loading
try {
val user = repository.getUser(userId)
_userState.value = UiState.Success(user)
} catch (e: Exception) {
_userState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
}
// 5. 在Activity中使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 使用ViewModel
viewModel.fetchUser(1)
}
}
// 6. 在Compose中使用
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val userState by viewModel.userState.collectAsState()
when (val state = userState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> {
Text(text = "用户: ${state.data.name}")
Text(text = "邮箱: ${state.data.email}")
}
is UiState.Error -> {
Text(text = "错误: ${state.message}", color = Color.Red)
}
}
}
常见问题与解决方案
问题:依赖注入循环依赖
- 原因:两个或多个类相互依赖,导致无法创建实例。
- 解决方案:使用
@Inject构造函数注入或字段注入,并确保依赖关系是单向的。 - 代码示例: “`kotlin // 错误的做法:循环依赖 class ClassA @Inject constructor(private val classB: ClassB) class ClassB @Inject constructor(private val classA: ClassA)
// 正确的做法:使用接口或抽象类打破循环 interface ClassBInterface {
fun doSomething()}
class ClassB @Inject constructor() : ClassBInterface {
override fun doSomething() { /* ... */ }}
class ClassA @Inject constructor(private val classB: ClassBInterface) “`
问题:Hilt无法注入某些依赖
- 原因:依赖没有正确提供或作用域不匹配。
- 解决方案:确保在Module中正确提供依赖,并使用正确的作用域。
- 代码示例: “`kotlin // 错误的做法:作用域不匹配 @Module @InstallIn(SingletonComponent::class) object AppModule { @Provides @Singleton fun provideRepository(): UserRepository { return UserRepository() } }
@AndroidEntryPoint class MainActivity : AppCompatActivity() {
@Inject lateinit var repository: UserRepository // 可能无法注入 // 正确的做法:确保作用域匹配 // 如果Activity需要不同的作用域,使用@ActivityScoped}
// 正确的做法:使用正确的作用域 @Module @InstallIn(ActivityComponent::class) object ActivityModule {
@Provides @ActivityScoped fun provideRepository(): UserRepository { return UserRepository() }} “`
总结
本文通过五个实战案例,深入解析了Android开发中的关键技术点:
- Jetpack Compose:现代UI开发的首选,通过声明式编程简化UI构建。
- Retrofit与Coroutines:高效处理网络请求,结合StateFlow管理状态。
- Room数据库:本地数据存储的优秀解决方案,支持响应式数据流。
- WorkManager:可靠的后台任务调度,支持一次性或周期性任务。
- Hilt:简化依赖注入,提高代码可维护性和可测试性。
每个案例都包含了详细的代码示例和常见问题的解决方案,帮助开发者在实际项目中快速应用这些技术。通过掌握这些实战技能,开发者可以构建出高质量、高性能的Android应用。
扩展阅读
希望本文能为你的Android开发之旅提供有价值的参考!
