我们在后台和社区等各渠道收到了来自开发者们的反馈,也把一些开发者们常见的问题进行了简单的收集和梳理,整理成这一篇关于现代 Android 开发的常见问题和解答,如果您在构建过程中有任何其他的问题,欢迎通过留言的方式让我们知道。
其实在设计和实现 Jetpack 之初,我们就已经充分考虑了如何让初学者也能快速上手。您可以在 Android Jetpack 和 Android Jetpack 使用入门中找到学习 Jetpack 所需要的资源,比如文章、视频、Codelab、在线培训等。当您掌握到一定程度,可以尝试阅读 AndroidX/Jetpack 的源代码来进一步提升自己。如果您在开发当中遇到了问题难以解决,我们也非常欢迎您向我们反馈,寻求我们的帮助。
问: 如何从 Android 界面中收集数据流?
使用这些 API 有哪些好处呢?最重要的一方面就是它们具有感知生命周期的能力。比如只有当您的应用正在前台被用户使用时,这些方法才会从 Flow 中收集数据流。而这种能力恰好是其他方法所不能实现的,因此我们向您推荐这两种方法。
问: 如何调试应用的性能问题?
答: 我们提供了一些专门用于优化应用性能的 Jetpack 库,比如您可以用 Macrobenchmark 库监测应用启动、界面滚动性能或是监控掉帧时的性能表现。Benchmark 库可以用来监测特定函数的 CPU 开销。
对于上述的这些 Jetpack 库,您既可以用持续集成的方式远程测量应用的性能表现,又可以在本地执行,然后把结果显示在 Android Studio 当中。另外,别忘了 Android Studio 本身就提供了许多性能分析工具供您使用。您可以用这些工具来衡量所关注的不同性能指标,从而有的放矢,找准改进的方向。
我们都有这样的经验: 调试应用的界面非常简单,发现应用的性能问题也非常容易;但是到了真正要提升性能的时候,问题却变得极其复杂。因为即使您获得了性能指标,看到了统计数据,却往往在如何排查和优化的问题上碰了壁。所以我们也提供了一些在线的指导,您可以通过这些具体的案例来了解性能调试调优的思路和方法,在调试自己的应用时可以较容易地找准分析性能数据的着眼点。
MotionLayout
https://developer.android.google.cn/training/constraint-layout/motionlayout
ConstraintLayout
https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ConstraintLayout
答: MVVM (Model-View-ViewModel) 和 MVI (Model-View-Intent) 其实没有太大的区别,它们都是实现单向数据流 (UDF) 的具体方法,也是您构建应用的首选方式。因此,选择哪一种方法完全取决于应用的具体需求,取决于您是否希望使用 MVI 建立用户意图模型并将几乎所有内容都设置为响应式 (reactive)。所以,具体选择哪一种架构完全取决于您的应用和需求,但不必过分拘泥,因为它们都可以实现您的目标。
答: 我们建议您使用 Flow 或挂起函数。如果这只是一次性的调用,那么您应该使用挂起函数,如果您需要使用流式数据,那么建议您使用 Flow。
在解决这个问题时,如果考虑让存储区类实例通知 ViewModel 发生了数据变化,您会发现这样做很麻烦。因为 ViewModel 的生命周期往往比存储区类实例要短。正确的思考方式应该是: 如果 ViewModel 需要用到存储区中的某些数据,就让它观察或收集那些信息,这个过程可以通过 RxJava 或者协程来实现。这两种思路之间有细微的差异,主动和被动方互换之后,很多复杂的问题也迎刃而解了。值得一提的是,这种新思路对于所有短生命周期对象观察长生命周期对象中的数据都适用。当您处理其他单向数据流问题时,也可以试着从这个角度看问题,不论是存储区类实例和 ViewModel 之间还是 ViewModel 和视图之间的问题都会迎刃而解。
可能您还会思考是否可以使用 LiveData。不过需要明确的是,LiveData 其实是专门为界面构建的一组 API,它的设计用途非常清晰,就是用来保存界面状态的。如果您想在存储区类中使用 LiveData,会发现扩展性很差,因为在这种场景下有更适合的响应式库供选择。所以 LiveData 被设计为解决那一类非常具体的问题,超出这些范畴后就不再适合了。特别是当您正在使用 Kotlin 协程和 Flow 时就更不必再考虑 LiveData 了。另外,LiveData 是通过 XML 中绑定的形式使用的,以往人们使用它是因为与数据绑定紧密结合起来了。而现在,数据绑定支持使用 StateFlow,我们有了这种新的具有生命周期感知能力的协程 API,纵使 LiveData 在某些特定用途仍然表现出色,我们也没有再使用的必要了。
https://developer.android.google.cn/topic/libraries/architecture/livedata
https://developer.android.google.cn/kotlin/flow
问: 如何使用 Hilt 构建多模块应用?
问: WorkManager 是否会取代后台服务?
答: 如果您所指的后台服务是 Android 中的旧服务类型的话,答案是肯定的。WorkManager 是专门为需要确保任务可靠运行而设计的,它有两个重要特点: 保证任务可靠运行和可推迟运行任务。如果某个任务不需要独立运行,那么使用 WorkManager 与使用后台执行器、协程就没有任何区别。某些工作,比如发送电子邮件或投屏到电视非常适合使用 WorkManager。
在非常早期的 Android 中,创建服务来实现这些任务是非常重要的,因为操作系统需要提前知道您的应用准备做某件事情。也许新版本的 Android 仍可能根据是否有服务在运行来判断是否要继续运行您的应用,但今后您都不必继续使用了。所以,如果您不在意某些后台工作没有执行,那么可以使用常规的后台执行器;如果您在意,那么请使用 WorkManager。WorkManager 还非常适用于持久作业,也就是需要在后台持续运行的任务。如需了解近期 WorkManager 的新增功能,请参阅我们之前的推文: 现代 WorkManager API 已发布和 Android Studio 对现代 WorkManager 的支持。
Android 12 引入了一些新的限制,同时 WorkManager 2.7 也提供了新的 API 来帮助您适配这些限制,比如 setExpedited API。在早期,我们希望开发者们使用前台服务,因为如果应用的确需要在后台运行,就一定要用通知栏消息告诉用户这样做的必要性。但是很快,我们发现这个功能遭到某些应用的滥用,用户的通知栏出现了大量的占用和骚扰。所以我们为 WorkManager 增加了加急作业 API,在 Android 12 运行时,它可以在 JobScheduler 中委托加急作业,而在低版本 Android 运行时又通过委托给前台服务来提供更好的向后兼容性。
问: 是否有只能用 LiveData 处理的场景?
答: 您可能在对比 LiveData 和 Flow 时会产生一个疑问: 是否有 LiveData 不能用 Flow 替代的例子?其实不用这样对比,我们要从 LiveData 设计的思路来考虑。LiveData 是从专门解决界面显示问题的角度来设计的,它的目的很明确,就是要在应用处于前台时进行数据交互。这也意味着 LiveData 无法解决后台运行时的数据获取问题。所以这既是 LiveData 的优势,也是它不能很好适应其他场景的原因。您可以查看《从 LiveData 迁移到 Kotlin 数据流》了解如何将 LiveData 转换为 StateFlow 以及其中的差异。也可以阅读《实战 | 使用 Kotlin Flow 构建数据流 "管道"》来学习如何使用 Kotlin Flow。
所以如果您认为 LiveData 更适合您的用例,或者在您的用例中它实现起来更简单,完全可以用 LiveData 实现。不过您需要了解,StateFlow 可以完成您用 LiveData 实现的所有任务,并且可以做得更多,也更符合 Android 开发的技术趋势,同时让您日后的维护更加轻松。
问: 如何减少 Android Studio 的构建时间?
答:构建是开发者们经常会进行的一个操作,但构建的操作包括了很多个过程: Gradle 和 Gradle 插件的处理、构建、编译、打包资源等等。我们正在进行很多工作来优化这些方方面面,所以您需要做的就是使用最新版本的工具。每当 Android Gradle 插件、Kotlin 和其他您在构建过程需要用到的工具有更新时,请尽量更新它们。通常我们会在每次更新版本中修复近期的错误、提升稳定性或构建速度。
其次,像 KSP (Kotlin 符号处理) 这类工具可以在某些场景加快您的构建,建议您开始使用它们。另外我们还建议不要在构建中添加太多自定义的内容,设置不当可能会适得其反。因为自定义 Gradle 插件之类操作需要您对 Gradle、Gradle Android 插件有充分的了解,不适合新人去操作。如果您有像使用自定义构建这样的需求,应该尽可能只使用声明式的 DSL 样式配置。
如需了解更多,请参阅我们之前的推文《使用新 Android Gradle 插件加速您的应用构建》。
问: 如何将应用过渡到 Jetpack?
答: 您不必过分拘泥于是不是每一处地方都使用了 Jetpack。我们无法给到您这样一个清单来一一检查,因为这是没有意义的。开发中是否使用 Jetpack 来实现完全取决于您的应用架构。如果您的架构是可扩展的,比如所用到的接口都可以替换、数据库中的依赖次序可以交换等等,那么这些都是帮助您的应用取得成功的基石。因此,我们只想建议您尽可能选择一个合理的架构,让依赖项能够很容易进行替换——比如您可以很方便地用 DataStore 替换 SharedPreferences。
另外,Jetpack 已经推出好几年了,在这期间涌现出一大批优秀的官方或第三方库。所以我们建议您在自己实现某个功能前,先找找是不是已经有非常方便的库可以取用。重复造轮子固然能提升您的研发水平,但会浪费大量时间,您完全可以依托强大的开源力量创造出优秀的应用。举个例子,过去人们使用前台服务来实现某些功能,但是 Android 12 的发布,这些方法都不再适用了,开发者们不得不重新实现来兼容新的系统。但是如果您用 WorkManager 的 setExpedited API 来实现,那么不需要任何操作就可以同时兼容新、旧系统的特性,节省大量的时间并省去了反复调试的成本。
最后一个建议,请您一定养成充分测试的习惯。在开发的过程中,非常容易因代码变化而产生不确定因素。及时而充分的测试可以为代码质量提供保障。我们以 Jetpack 开源代码库为例,其中有一个分支适配了最新的 Android 代码,因此不论 Android 源代码主实例中有什么变化,我们都会为它进行所有的测试。这样就可以最大程度保证能在数月之前就发现可能出现的问题并做好修复工作,从而在新版本 Android 发布后,您可以放心地更新 Jetpack 库进行适配而无需担心出现兼容性问题。
如需了解更多,请参阅:
问: ViewModel 和 Hilt 或者 Dagger 在 Compose 中可以使用吗?
答: 答案是肯定的。在我们之前的推文《实践 | Jetpack Compose 中的状态管理》中详细介绍了在 Compose 中处理状态复杂性的不同方法。您会了解到 ViewModel 是一种负责提供对应用业务逻辑的访问的状态容器,它提供特定界面的界面状态,所以位于整个界面的最外层。如果您使用了 Compose Navigation,那么 ViewModel 就可以看作是一个路由来使用。
如需了解更多,请参阅:
问: 需要在构建多平台应用上投入精力吗?
答: 这个问题与 Kotlin Multiplatform (KMP) 相关,目前我们的建议是暂时不要。KMP 仍然是我们还在研究的领域 (目前是 Alpha 版),相关成果应该很快就会与您见面。我们知道很多开发者都对这项技术感兴趣,同时大家也在使用 Jetpack,所以我们不希望 Jetpack 阻碍您迁移到 KMP,您也不需要在两者中纠结。
我们在设计 KMP 时,首先需要验证它的可行性。比如构建 Room Multiplatform 库就需要确保它不会影响到只使用 Android 的用户。因此开发和推广 KMP 是一个平稳过渡的过程,从文档到 API 兼容性都必须能按照预期执行。对我们来说,将一个库迁移到 Kotlin Multiplatform 需要做大量的工作,比如构建一些小型的 Jetpack 库来进行测试和评估。未来您会看到很多这方面的进展,敬请期待!
问: 使用协程处理异常的最佳实践是什么?
答:建议您直接使用协程提供的异常处理机制。您不需要进行额外的处理,直接使用协程内置的方法即可。这种机制会自动帮您处理异常传播过程,不过您可能需要了解 SupervisorJob、SupervisorScope 以及 CoroutineScope 的工作原理。如需了解如何使用内置机制处理协程中的异常,请参阅文档:
问: 如何自动化发布到 Google Play 商店?
答: 目前 Android Studio 或者 Android Gradle 插件还不支持这项功能,但是开发者社区非常期待能实现它。不过您可以尝试一款非常好用的第三方工具,它可以同 Android Gradle 插件一起工作,将这个发布流程自动化。我们会探索未来把这项功能集成进 Android Studio 的可能性,但在我们计划完成之前,建议您通过开源库实现这个功能:
问: 能否使用 RemoteMediator 对 Pager 类单元测试?
答:未来您可以实现这样的测试,不过目前还只能通过真实界面来测试操作。我们的确有这样的计划要实现一个测试辅助类来解决分页测试相关的问题,但目前它依赖一些尚未公开的内部 API。所以您的理想做法是对 RemoteMediator 进行单元测试或是对实际分页进行集成测试。
分页功能的一个重要特点是非常依赖界面展示。它会在实际显示时优化您看到的内容,因此如果没有相关的代码上下文,就很难控制和测试它。
问: DataStore 是否支持加密?
答: DataStore 没有自带加密功能,但我们有计划将加密集成进来。所以目前您使用 DataStore 时可以选择先将数据序列化再写入存储中。您可以在 DataStore 的基础上自行加密,只不过还没有模块能帮您直接完成加密工作。
一种可行的方式是将一个加密库注入到 DataStore 中使用。我们希望能为您提供像 EncryptedSharedPreferences 那样的 API,不过这个过程还需要时间。目前的想法是创建单独的库,然后利用 DataStore 的 API 实现对数据的直接加密。
点击屏末 | 阅读原文 | 即刻了解 Jetpack 更多相关内容