Android开发这几年变化太快如果你还停留在写findViewById、开AsyncTask的年代出去面试或者接手新项目大概率会觉得很吃力。Kotlin彻底转正成了Android的一等语言Jetpack又把这几年Google在架构、生命周期、UI开发上的最佳实践全部收拢成了一套标准方案。这篇文章就围绕Kotlin Jetpack这套现代Android开发组合拳把里面的核心思路、关键技术点、实操时容易踩的坑一次讲清楚。1. 这套组合的底层逻辑为什么是Kotlin Jetpack先聊一个很多人没认真想过的问题为什么是Kotlin而不是继续用Java又为什么是Jetpack而不是各自为战的开源库Kotlin能上位表面原因是Google力推深层原因是它确实解决了Java在Android开发里最痛的那几个点。空指针安全问题通过可空类型和编译器检查把大量运行期崩溃提前到编译期就挡住冗长的样板代码用数据类、扩展函数、lambda表达式压缩掉一半以上函数式编程的支持让集合操作、回调处理写起来行云流水。这些不是锦上添花是实打实影响每天的开发效率和代码质量。Jetpack要解决的问题更根本。Android系统碎片化严重不同版本API差异大生命周期管理混乱配置变更比如转屏导致的数据丢失问题一直是老大难。Jetpack把这些跨版本兼容、生命周期绑定、数据持久化等复杂问题统一封装成一套组件让你用标准化的方式去处理。它不是一个库是一整套解决问题的思路和规范。这两者结合后效果是11大于2的。Kotlin的协程天然适合Jetpack组件里大量的异步场景lifecycleScope、viewModelScope这些扩展让协程的生命周期自动跟着Activity/Fragment走。空安全特性也让LiveData、Room这类组件在使用上更顺手。整个组合的核心价值就一句话让你用更少的代码、更清晰的结构、更少的崩溃写出更好维护的App。2. Kotlin核心特性真正用得上的那些语法光是会说Kotlin比Java好没用关键得知道好在哪、怎么用。这里只说实际项目里最高频的几个特性。2.1 空安全与不可变编译期就把坑填平Java里写String name null;是家常便饭但运行到name.length()直接NPE崩溃。Kotlin通过类型系统把可能为空和不能为空分开// 不可空类型编译器禁止赋值null var name: String Hello // 可空类型使用时必须做空判断 var nullableName: String? null // 安全调用为空则整个表达式为null不会崩溃 val length nullableName?.length // Elvis操作符为空时提供默认值 val len nullableName?.length ?: 0这套设计在实际开发里最大的价值不是你记得判空而是编译器强迫你判空。写代码的时候看到类型后面带?你就知道这里可能有坑必须处理。这一条规则就能消灭掉Java项目里至少30%的NPE崩溃。不可变性是另一个容易被忽略的点。用val声明只读变量用data class配合不可变集合配合copy()方法做浅拷贝。多线程环境下不可变对象天然线程安全不需要加锁也减少了状态被意外修改的隐患。2.2 扩展函数与高阶函数写DSL一样流畅的代码扩展函数可以给现有类添加新方法不用继承也不用修改原类。举个例子以前写Toast要写一长串用扩展函数一行搞定fun Context.showToast(message: String) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } // 在Activity里直接调用 showToast(保存成功)高阶函数参数是函数或返回函数配合标准库的let、apply、run、also能写出很优雅的链式代码user?.let { it.name 新名字 it.age 20 } ?: run { // user为null时执行 showEmptyView() }apply在初始化对象时特别好用val intent Intent().apply { action Intent.ACTION_VIEW data Uri.parse(url) flags Intent.FLAG_ACTIVITY_NEW_TASK }2.3 协程把异步从回调地狱里解放出来协程是Kotlin给Android开发带来的最大礼物。它本质上是线程框架的封装让异步代码可以像同步代码一样顺序书写。看个对比就明白了。传统回调方式// 两个网络请求第二个依赖第一个的结果 api.getUser { user - api.getOrders(user.id) { orders - showOrders(orders) } }协程方式lifecycleScope.launch { val user api.getUser() // 挂起不阻塞主线程 val orders api.getOrders(user.id) showOrders(orders) }launch启动一个协程suspend函数在遇到耗时操作时自动挂起完成后自动恢复。全程不阻塞主线程代码还是顺序的读起来逻辑非常清楚。协程里的调度器Dispatchers控制线程切换lifecycleScope.launch(Dispatchers.IO) { // 在IO线程执行 val result doNetworkRequest() withContext(Dispatchers.Main) { // 切回主线程更新UI textView.text result } }3. Jetpack核心组件每个都在解决一个具体问题Jetpack涵盖的组件非常多从架构到UI到行为到基础。实际项目里下面这几个是不能跳过的核心。3.1 Lifecycle让组件感知生命周期不再靠手动管理在没有Lifecycle之前你在Activity的onResume里做一件事必须记得在onPause里做相反的清理。一旦漏了轻则资源泄漏重则崩溃。Lifecycle把生命周期事件标准化了class LocationObserver : LifecycleObserver { OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun startLocation() { /* 开始定位 */ } OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun stopLocation() { /* 停止定位防止耗电 */ } } // 绑定 lifecycle.addObserver(LocationObserver())现在推荐用LifecycleCoroutineScope来配合协程lifecycleScope.launch启动的协程会在Lifecycle销毁时自动取消从根上杜绝协程泄漏lifecycleScope.launch { repeat(10) { delay(1000) updateTimer() } // Activity销毁时这个协程自动取消 }3.2 ViewModel数据在配置变更后依然健在转屏是Android开发的经典难题Activity被销毁重建Activity里的临时数据全没你必须在onSaveInstanceState里塞数据再恢复。麻烦而且只适合小量数据。ViewModel的设计目标很纯粹把数据从Activity里拿出来放到一个独立于Activity生命周期的容器里。Activity旋转销毁时ViewModel实例不会被销毁新的Activity通过同一个ViewModel继续拿到旧数据class MyViewModel : ViewModel() { val userData MutableLiveDataUser() fun loadUser() { viewModelScope.launch { userData.value repository.fetchUser() } } } class MainActivity : AppCompatActivity() { private val viewModel: MyViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.userData.observe(this) { user - // 更新UI转屏后这里自动恢复 } } }3.3 LiveData带生命周期感知的数据持有者LiveData是一套观察者模式生命周期绑定的实现。核心特性是只在活跃状态时才通知观察者。所谓活跃状态指Lifecycle处于STARTED之后。比如你在前台时数据更新立刻刷新UIApp退到后台通知停止UI本来看不见没必要浪费资源回到前台自动恢复。这个机制杀死了大量Activity销毁后还在更新UI导致崩溃的bugviewModel.userData.observe(this) { user - // 只在Activity处于STARTED状态时回调 // Activity销毁后自动移除观察者不会泄漏 }3.4 LiveData、Flow和StateFlow怎么选LiveData简单好用但在处理复杂异步数据流、数据转换、组合操作时能力有限。这时候Kotlin的Flow更合适。Flow是响应式数据流类似RxJava但基于协程学习门槛更低// Flow处理流式数据、组合变换更强大 fun getAllData(): FlowData flow { emit(repository.getLocalData()) // 先发本地缓存 emit(repository.getNetworkData()) // 再发网络数据 } // StateFlow状态持有型数据流类似LiveData但代码更干净 val userState MutableStateFlowUser?(null) // 收集数据 viewLifecycleOwner.lifecycleScope.launch { viewModel.userState.collect { user - // 更新UI } }简单的场景用LiveData涉及复杂流式处理、状态管理、Repository层的数据流建议直接用Flow/StateFlow。一个项目里两者可以共存不必强求统一。3.5 Room比SQLite好用到不知哪里去的数据库Room是SQLite的ORM封装在编译期做SQL检查发现问题直接编译报错而不留到运行期。三大核心组件Entity定义表结构、DAO定义数据库操作、Database持有数据库实例。Entity(tableName users) data class User( PrimaryKey val id: Int, val name: String, val age: Int ) Dao interface UserDao { Query(SELECT * FROM users WHERE age :minAge) suspend fun getUsersByAge(minAge: Int): ListUser Insert suspend fun insertUser(user: User) Query(DELETE FROM users WHERE id :id) suspend fun deleteById(id: Int) } Database(entities [User::class], version 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } // Kotlin中获取实例 val db Room.databaseBuilder( context, AppDatabase::class.java, app-db ).build()Room配合Flow使用很丝滑数据库变化自动通知UI层刷新Query(SELECT * FROM users) fun observeAllUsers(): FlowListUser3.6 DataStoreSharedPreferences的现代替代SharedPreferences有几个臭毛病无法保证类型安全、误用apply()容易造成ANR、不支持协程。DataStore用Flow替代了监听器天然支持协程和类型安全。val Context.dataStore by preferencesDataStore(name settings) val themeMode dataStore.data.map { preferences - preferences[KEY_THEME] ?: light } // 写入 dataStore.edit { settings - settings[KEY_THEME] dark }上手成本很低迁移也很平滑新项目建议直接用DataStore。3.7 Hilt把依赖注入规范化项目大了以后手动去创建对象、传依赖很痛苦代码里到处new XXX()测试也困难。Hilt是Google官方出的依赖注入框架基于Dagger但上手门槛低了很多HiltAndroidApp class MyApplication : Application() Module InstallIn(SingletonComponent::class) object AppModule { Provides Singleton fun provideApiService(): ApiService { return Retrofit.Builder()...build() } } AndroidEntryPoint class MainActivity : AppCompatActivity() { Inject lateinit var apiService: ApiService }构造函数注入是最值得推荐的class MainViewModel Inject constructor( private val repository: UserRepository ) : ViewModel() { // 直接可用了再不用手动new }4. 架构范式MVVM在现代Android里的落地姿势Kotlin和Jetpack提供了工具但工具怎么组合使用需要一套架构模式来约束。MVVMModel-View-ViewModel是目前Android社区公认的主流实践。4.1 分层各司其职的钱包设计MVVM的分层逻辑可以用一个生活场景来理解UI层View是前台收银员ViewModel是店铺经理Repository是仓库管理员Model是仓库里的货。View层只负责渲染UI、转发用户操作不做任何逻辑。ViewModel层持有界面的状态State接收View的事件调用Repository获取数据把结果暴露给View。不持有Context不操作View这样才可以独立测试。Model/Repository层数据源统一入口封装本地数据库Room和网络Retrofit数据向上提供干净的数据接口。4.2 ViewModelLiveData/StateFlow的核心闭环看一个加ViewModel层后的完整流程代码class UserViewModel Inject constructor( private val repository: UserRepository ) : ViewModel() { private val _userList MutableStateFlowListUser(emptyList()) val userList: StateFlowListUser _userList.asStateFlow() private val _loading MutableStateFlow(false) val loading: StateFlowBoolean _loading.asStateFlow() fun loadUsers() { viewModelScope.launch { _loading.value true try { val users repository.getUsers() _userList.value users } catch (e: Exception) { // 统一错误处理 } finally { _loading.value false } } } }UI层通过collect来观察状态viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.userList.collect { users - adapter.submitList(users) } } }repeatOnLifecycle保证仅在STARTED以上状态采集数据后台时自动暂停回到前台自动恢复。这是目前推荐的UI订阅范式。4.3 状态管理用一个类来表达界面状态纯粹用多个LiveData/StateFlow来管理不同数据块界面状态一多就乱。更好的做法是用**一个状态容器UI State**来统一表达data class UserListUiState( val users: ListUser emptyList(), val isLoading: Boolean false, val errorMessage: String? null, val query: String ) class UserListViewModel : ViewModel() { private val _uiState MutableStateFlow(UserListUiState()) val uiState: StateFlowUserListUiState _uiState.asStateFlow() fun search(query: String) { viewModelScope.launch { _uiState.update { it.copy(isLoading true, query query) } // ...网络请求 } } }UI层只观察一个uiState任何状态变化加载中、有数据、有错误、有搜索关键字都会自动刷新整个界面。5. 从进程到UIModern Compose UI带来的一次范式转变Jetpack Compose是这几年来Android UI开发最大的一次革命它完全抛弃了XML布局和findViewById用声明式UI来构建页面。5.1 Compose的核心写法状态决定界面传统View体系是命令式通过setText、setVisibility等来操作View数据变了你要手动改每个控件的状态。Compose是声明式你描述数据长什么样界面就是什么样数据变了界面自动重组RecompositionComposable fun UserProfile(user: User) { Column(modifier Modifier.padding(16.dp)) { Text( text user.name, style MaterialTheme.typography.titleLarge ) Text( text 年龄${user.age}, style MaterialTheme.typography.bodyMedium ) Button(onClick { /* 点按钮做什么 */ }) { Text(编辑) } } }Composable标记函数就是声明UI的地方。没有XML没有inflate没有findViewById一个函数看代码就知道界面长成什么样。5.2 状态与重组记住数据的正确姿势Compose里最常见的坑是状态丢失和无限重组。用remember保留状态用mutableStateOf让状态变化触发重组Composable fun Counter() { var count by remember { mutableStateOf(0) } Column { Text(当前计数$count) Button(onClick { count }) { Text(累加) } } }如果状态需要跨重组保留到配置变更后就用rememberSaveablevar count by rememberSaveable { mutableStateOf(0) }5.3 与ViewModel的配合Compose式状态订阅Compose配合ViewModel是Android现代开发的黄金组合用collectAsStateWithLifecycle来订阅状态Composable fun UserScreen(viewModel: UserViewModel hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() when { uiState.isLoading - LoadingIndicator() uiState.errorMessage ! null - ErrorView(uiState.errorMessage) else - UserList(uiState.users) } }注意UI状态和Compose状态的同步是自动的ViewModel里的数据一变界面立刻跟着变。这比传统的observenotifyDataSetChanged简单太多了。6. 一个小型完整的现代Android模块实现从构建到落地的全记录理论讲了半天不如一个完整的小项目来得实在。我自己实践过程中做过一个模拟项目X实现一个简单的新闻App包含网络请求、本地缓存、状态管理、UI展示第一阶段经历了从传统写法到现代写法的完整改造有很多值得说的东西。6.1 工程环境和依赖配置项目用Gradle Kotlin DSL来管理依赖核心依赖如下// build.gradle.kts (Module) plugins { id(com.android.application) id(org.jetbrains.kotlin.android) id(kotlin-kapt) id(dagger.hilt.android.plugin) } dependencies { // Compose implementation(platform(androidx.compose:compose-bom:2024.05.00)) implementation(androidx.compose.ui:ui) implementation(androidx.compose.material3:material3) // ViewModel Lifecycle implementation(androidx.lifecycle:lifecycle-viewmodel-compose:2.8.1) implementation(androidx.lifecycle:lifecycle-runtime-compose:2.8.1) // Hilt implementation(com.google.dagger:hilt-android:2.51.1) implementation(androidx.hilt:hilt-navigation-compose:1.2.0) kapt(com.google.dagger:hilt-compiler:2.51.1) // Retrofit OkHttp implementation(com.squareup.retrofit2:retrofit:2.11.0) implementation(com.squareup.retrofit2:converter-gson:2.11.0) implementation(com.squareup.okhttp3:logging-interceptor:4.12.0) // Room implementation(androidx.room:room-runtime:2.6.1) implementation(androidx.room:room-ktx:2.6.1) kapt(androidx.room:room-compiler:2.6.1) // DataStore implementation(androidx.datastore:datastore-preferences:1.1.1) }这段配置基本就是现代Android项目的标准起手式。Compose统一UIHilt统一依赖Room本地缓存Retrofit网络请求DataStore存偏好设置。6.2 构建Repository层统一数据源入口Repository层的职责是屏蔽数据来源网络还是本地缓存对上层ViewModel只提供干净统一的数据接口interface NewsRepository { fun getLatestNews(): FlowListNews fun getCachedNews(): FlowListNews } class NewsRepositoryImpl Inject constructor( private val newsApi: NewsApi, private val newsDao: NewsDao ) : NewsRepository { override fun getCachedNews(): FlowListNews { return newsDao.observeAll() } override fun getLatestNews(): FlowListNews { return flow { // 先发缓存再发网络两者兼得 emit(newsDao.getLocal()) val remote newsApi.fetchNews() newsDao.updateAll(remote) emit(remote) }.catch { e - // 网络失败时缓存在兜底 emit(newsDao.getLocal()) } } }网络失败时崩溃还是兜底这就是catch操作符存在的意义。通过这样的设计用户弱网环境下依然能看到内容。6.3 ViewModel层持有状态驱动数据HiltViewModel class NewsViewModel Inject constructor( private val repository: NewsRepository ) : ViewModel() { private val _uiState MutableStateFlow(NewsUiState()) val uiState: StateFlowNewsUiState _uiState.asStateFlow() init { loadNews() } fun loadNews() { viewModelScope.launch { _uiState.update { it.copy(isLoading true) } repository.getLatestNews().collect { news - _uiState.update { it.copy(newsList news, isLoading false) } } } } data class NewsUiState( val newsList: ListNews emptyList(), val isLoading: Boolean false, val errorMessage: String? null ) }6.4 Compose UI层响应式订阅状态Composable fun NewsScreen(viewModel: NewsViewModel hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() Scaffold(topBar { TopAppBar(title { Text(每日新闻) }) }) { padding - if (uiState.isLoading uiState.newsList.isEmpty()) { CircularProgressIndicator(modifier Modifier.padding(padding)) } else { LazyColumn(modifier Modifier.padding(padding)) { items(uiState.newsList, key { it.id }) { news - NewsCard(news) } } } } }key参数指定唯一IDCompose在做列表diff时才不会重复创建和错乱。这个小细节在数据频繁更新时非常关键。6.5 数据库和网络模块的Hilt装配Module InstallIn(SingletonComponent::class) object DataModule { Provides Singleton fun provideApi(): NewsApi { return Retrofit.Builder() .baseUrl(https://example-server.com/) .client(OkHttpClient.Builder().build()) .addConverterFactory(GsonConverterFactory.create()) .build() .create(NewsApi::class.java) } Provides Singleton fun provideDatabase(ApplicationContext context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, news.db) .build() } Provides Singleton fun provideNewsDao(db: AppDatabase): NewsDao db.newsDao() }整套依赖关系清清楚楚ViewModel依赖RepositoryRepository依赖DAO和API全部由Hilt在编译期生成并注入。测试的时候想替换成Mock版本直接改Module即可侵入性很小。7. 避坑指南从崩溃和卡顿中总结出的实战经验做了一段时间现代Android开发踩过挺多坑。把最典型的整理出来帮后来者省点时间。7.1 处理可空类型时别偷懒用过度断言Kotlin里可以强制断言非空!!滥用会在运行时炸出类似NullPointerException at line 32这种最恶心的崩溃。现实情况是你在验证过的场景里可以放心用!!但凡是数据来自网络、数据库、外部输入一律用安全调用或者?:兜底。我的经验法则是能用?:给默认值的就默认值不能用默认值就显式抛异常并带上上下文信息比如checkNotNull(data) { 加载用户数据时返回null }。这比裸!!可读性和排查性都强太多。7.2 配置变更不是只有转屏很多人的配置变更知识还停留在屏幕旋转上。实际上系统语言切换、浅深色模式切换、字体大小调整、分屏模式变化都会触发Activity重建。ViewModel原理上就是为了抗住这一切而设计的但你必须注意ViewModel里不要持有Activity/Fragment的Context或View引用否则内存泄漏比传统写法更严重。要Context用AndroidViewModel或者通过注入拿到ApplicationContext。7.3 协程取消与资源清理的时机lifecycleScope.launch会自动取消但取消不等于立刻停止。协程取消是协作式的——在suspend点检查取消状态并抛出CancellationException。如果你在finally里做清理工作要注意viewModelScope.launch { try { repository.saveData(data) } finally { withContext(NonCancellable) { // 这里不会被取消适合做必须完成的清理 } } }如果清理工作不需要完备保证直接放finally就行。别在协程里用Thread.sleep要用delay否则协程无法响应取消会出现协程取消不了的假象。7.4 Flow的冷热流别搞混Flow分冷热流。冷流flow{}构建的是每次有收集者时生产代码才执行热流StateFlow、SharedFlow是数据由上游独立产生多个收集者共享同一份数据。很多初学者在Repository层返回flow { ... }冷流界面层每次重组都重新收集导致重复请求。解决方法是把数据流用stateIn/stateInWhileSubscribed转成热流托管起来val newsList repository.getLatestNews() .stateIn( scope viewModelScope, started SharingStarted.WhileSubscribed(5000), initialValue emptyList() )WhileSubscribed(5000)的意思是没有收集者5秒后自动停止上游生产减少资源浪费。7.5 Compose重组引发的性能陷阱Compose的声明式写起来爽但有几处性能雷区。一个是避免在Composable函数里创建大对象比如把remember忘掉每次重组都new一堆数据// 错误写法 Composable fun BadExample() { val list (1..100000).toList() // 每次重组都重新创建 LazyColumn { items(list) { ... } } } // 正确写法 Composable fun GoodExample() { val list remember { (1..100000).toList() } LazyColumn { items(list) { ... } } }另一个是避免在onEach里频繁更新状态。如果状态以每秒几十次的频率变化Compose的重组会非常频繁导致界面掉帧。这时候要考虑用MutableStateFlow合并更新而不是用多个mutableStateOf分散触发。7.6 混合使用传统View和Compose时的注意事项存量项目不可能一夜间全转Compose大多是Fragment里嵌入部分Compose或者Compose里嵌套AndroidView。这种混合模式下最要注意Context的获取方式不同Compose里用LocalContext.current别试图在Composable上层持有Activity引用容易生命周期错乱。性能上也要小心。Compose和传统View的混合布局在滚动列表里反复切换会出现measure摩擦滚动流畅度有明显下降。我的建议是一个列表页面尽量保持单一实现方式不要在LazyColumn里混合使用传统RecyclerView和Compose item。8. 工具链与调试开发体验里面的大文章好的工具链能显著提升开发效率这块在现代Android开发里分量很重。8.1 代码风格与约束Kotlin官方推荐使用ktlint或detekt来统一代码风格、检查复杂度。我在项目里用ktlint作为Gradle任务挂在check阶段不符合规范直接构建失败强制团队统一风格。同时开启kotlin-allopen插件让某些类默认可继承省去写open关键字的繁琐。8.2 调试网络与数据库调试网络请求OkHttp的logging-interceptor必备但正式包要关掉。调试数据库推荐用Android Studio内置的Database Inspector直接在IDE里看表数据、执行SQL比SQLite官方工具方便多了。val client OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().apply { level if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } }) .build()8.3 自动化测试不只是嘴上说说Kotlin Jetpack架构下测试变得比以前容易太多了。ViewModel是纯Kotlin类且不依赖Android可以直接用JUnit测试Room数据库可以用内存数据库做单元测试Compose UI测试用createAndroidComposeRule模拟点击、断言文本。RunWith(JUnit4::class) class NewsViewModelTest { Test fun 加载新闻_数据为空时_显示空提示() { val repository FakeNewsRepository(emptyList()) val viewModel NewsViewModel(repository) runBlocking { viewModel.loadNews() } assertEquals(暂无内容, viewModel.uiState.value.errorMessage) } }写测试不是为了好看是给后续重构和加功能的信心。现代Android开发框架已经把可测试性做得很好了不写测试说不过去。9. 顺手回答几个高频疑问总有人问我这几个问题这里一起答了。Q1Kotlin会不会学完过时目前Android官方所有文档、示例、新组件默认语言都是KotlinJetpack新API也全是Kotlin-first。退一步说就算未来有新语言Kotlin积累的生态协程、Compose也不是一两年能被替代的。Q2Jetpack组件会不会太多学不完不需要都学。优先掌握Lifecycle、ViewModel、LiveData/StateFlow、Room、DataStore、Hilt至少了解、Compose现在新项目默认。其他如Navigation、Paging、WorkManager用到再查学习曲线没那么陡。Q3Compose现在是劝退还是必学新项目建议直接上Compose。Google已经把Compose列为新项目的默认UI方案传统View系统基本维持现状不推新。学习Compose初期会有重组、状态这些东西适应期但一个月后你会发现再也回不去XML时代了。Q4老项目怎么转型不要大爆炸式重写。我的经验是新页面用Compose老页面保持View用AndroidView做桥接逐步替换先引入Kotlin协程改造异步代码再把数据层抽象成Repository为ViewModel化做准备。一步一步来比一次性推倒重来带来的挫败感少很多。10. 个人体会和一些操作上的建议我自己的感受是Kotlin Jetpack这套组合真正的价值不是某个新特性有多惊艳而是它把Android开发引向了一条更可控的轨道。传统Android开发的问题在于生命周期要自己管、异步要自己调、UI要自己同步、崩溃要自己猜每一步都充满手工作坊式的不确定性。而现在很多曾经要靠经验丰富的老手才能避开的坑框架层直接帮你填了。举个小例子。以前做页面Activity里又写数据加载又写列表适配又写点击处理又写权限申请代码一多必然乱。现在ViewModel只管状态UI纯函数式渲染数据层独立每个模块职责单一、边界清楚。改需求的时候特别明显——改UI不用碰逻辑改逻辑不用动UI新需求来了多数情况下只是加一个State字段、加一个分支、加一个Repository方法而已。还有一件事值得说把协程和Flow学好是真的能够改变代码风格的。你会发现以前那种接口回调套回调的写法再也下不去手了因为顺序代码写起来太舒服、读起来太清楚了。这不是什么技术上的优越感纯粹是人面对复杂事物的本能选择——能简单就不复杂能清楚就不绕弯。如果是要独立开发或维护中小体量的App我的建议是别贪多先把下面这几块打稳Kotlin语法和协程、ViewModelStateFlow的状态管理、Room做本地数据、Compose写UI、Hilt管依赖、Retrofit做网络。这一套拿下来日常需求的开发效率会非常可观。等到基础扎实了再去研究Navigation做页面路由、Paging做列表分页、WorkManager做后台任务调度。整个生态是越用越顺的因为所有组件都在同一套生命周期和协程体系下协作搭配起来基本无障碍。最后给个非常实用的建议训练自己先写测试再写实现的习惯。哪怕只是给ViewModel写几个核心场景的单元测试长期收益也远超所花费的时间。逻辑出问题时有测试兜底和没有测试兜底排查效率完全是两个层级。现代Kotlin Jetpack这套架构本来就非常适合测试不利用起来太可惜了。
阅读完成 · 觉得有帮助?