习惯性的每天都会打开 medium 看一下技术相关的内容,偶然看到一位大佬分享和 Android Lifecycle 相关的面试题,觉得非常的有价值。
在 Android 开发中 Android Lifecycle 是非常重要的知识点。但是不幸的是,我发现很多新的 Android 开发对 Android Lifecycle 不是很了解,导致在开发中遇到很多奇怪的问题。分享这些面试题,不仅仅是为了通过面试,更是为了让 Android 开发者基础更加的扎实,防止在开发中遇到很多奇怪的问题。面试题一:Launch Fragment by Default
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager
.beginTransaction()
.replace(R.id.container, MainFragment())
.commit()
}
}
如果 Activity 因意外被杀死并被恢复,会再次执行 onCreate() 方法,创建新的 Fragment,因此在栈中会存在 2 个 Fragment。在 Fragment 上的任何操作都可能被执行两次,这将会导致出现奇怪的问题。![]()
为了防止 Activity 因意外被杀死而恢复,导致添加新的 Fragment,所以我们可以使用 stateInstanceState == null 作为判断条件,防止添加新的 Fragment,因此我们可以将上面的代码简单修改一下。class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, MainFragment())
.commit()
}
}
}
面试题二:Create Fragment with Constructor
如果往 Fragment 构造函数中添加参数,花几秒钟思考一下,下面的代码会有什么问题?supportFragmentManager
.beginTransaction()
.replace(R.id.container, MainFragment())
.commit()
class MainFragment(private val repository: Repository): Fragment() {
}
- 我们可以使用 .replace(R.id.container, MainFragment(repository)) 方法来代替。
我们不应该直接用带参数的构造函数实例化任何 Fragment(),如果想使用带参数的构造函数实例化 Fragment(),可以使用 FragmentFactory 解决这个问题,这是在 AndriodX Fragment 1.2.0 中新增加的 API,详情可以查看另外一篇文章 Google 建议使用这些 Fragment 的新特性。如果不使用 AndriodX Fragment 库,默认情况下系统是不支持的,虽然上面的代码可以正常编译运行,但是在运行过程当中,因为配置更改,导致在销毁恢复的过程中会崩溃,错误信息如下所示。Caused by: java.lang.NoSuchMethodException: MainFragment.<init>
这是因为系统需要在某些情况下重新初始化它,比如配置更改,例如设备被旋转时,导致 Fragment 被销毁,如果没有默认空的构造函数,系统不知道如何重新初始化 Fragment 实例。![]()
因此,我们总是需要确保实例化 Fragment 的时候有一个空的构造函数。面试题三:Instantiate ViewModel Directly
ViewModel 是 Jetpack 架构组件成员之一,花几秒钟思考一下,下面的代码会有什么问题?class MainActivity: AppCompatActivity() {
private val viewModel = MainViewModel()
}
class MainViewModel(): ViewModel {
}
- 我们还需要向它注入一些依赖项,例如 MainViewModel (repository)。
我们不应该直接实例化 ViewModel。ViewModel 是 Jetpack 架构组件成员之一,意味着它可以在配置更改时存活,例如设备旋转时,它比 Activity 有更长的生命周期。如果我们在代码中直接实例化 ViewModel,尽管它可以工作,但它将会变成一个普通的 ViewModel,失去原本拥有的特性。因此,要实例化 ViewModel,建议使用 ViewModel KTX,代码如下所示。class MainActivity: AppCompatActivity() {
private val viewMode:MainViewModel by viewModels()
}
- by viewModels () 会在重新启动或从已杀死的进程中恢复时,实例化一个新的 ViewModel。如果有配置更改,例如设备被旋转时,它将检查 ViewModel 是否已经创建,而不重新创建它。
![]()
- ViewModels() 会根据需要自动注入 SavedInstancestate (例如 Activity 中的 SavedInstanceState 和 Intent),如果我们有其他依赖是通过 Dagger Hilt 注入,它将与 ViewModel 一起使用下面的参数。
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: Repository,
savedStateHandle: SavedStateHandle
) : ViewModel {
}
面试题四:ViewModel as StateRestoration Solution
Jetpack 架构组件提供的 ViewModel 的作用是什么?class MainActivity: AppCompatActivity() {
private val viewMode:MainViewModel by viewModels()
// Some other Activity Code
}
ViewModel 是用于状态恢复,例如当 Activity 被杀死并重新启动时,ViewModel 是用来帮助恢复到原始状态。ViewModel 实际上是 google 提供的 Jetpack 架构组件之一,它鼓励 Android 开发者使用 MVVM 设计模式。它还有其它重要的功能,例如设备旋转时,即使 Activity 和 Fragment 被销毁,它们各自的 ViewModel 仍会保留,Google 在 ViewModel 中提供了一个名为 savedStateHandle 参数,该参数用于保存和恢复数据。面试题五:LiveData as State Restoration Solution
Jetpack 架构组件提供的 LiveData 的作用是什么。// Declaring it
val liveDataA = MutableLiveData<String>()
// Trigger the value change
liveDataA.value = someValue
它的存在是为了确保数据在 Activity 的生命周期中存活。当 Activity 在进程销毁返回时,数据将会自动恢复。LiveData 本身不能在进程销毁中存活。它是一种特殊类型的数据,根据观察者(Activity 或 Fragment)的生命周期来控制其发出的值。![]()
ViewModel 在配置变更后仍然存在,所以 ViewModel 内部的 LiveData 也一样。这确保 LiveData 发射值按照下图控制。![]()
然而 LiveData 本身不能在进程销毁中存活,当内存不足时,Activity 被系统杀死,ViewModel 本身也会被销毁。为了解决这个问题,Google 在 ViewModel 中提供了一个名为 savedStateHandle 参数,该参数用于保存和恢复数据,以便数据在进程销毁后继续存在。@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: Repository,
savedStateHandle: SavedStateHandle
) : ViewModel {
// Some other ViewModel Code
}
它是一种增强的机制,可以处理 Intent 和 SavedInstanceState,在以前的时候,这些都是由 Activity 单独处理的。![]()
为了确保 Livedata 保存下来,我们可以在 SavedStateHandle 中检查 Livedata 是否已经创建。val liveData = savedStateHandle.getLiveData<String>(KEY)
类似地,这也适用于 stateFlow,它可以在进程销毁中存活下来。val stateFlow = savedStateHandle.getStateFlow<String>(KEY, 0)
因此 LiveData 本身并不是用来恢复数据的。面试题六:When is The View Destroyed But Not the Instance
在 Activity 或 Fragment 通常会有一个视图。你能给我提供一个场景,实例的视图被破坏了,但实例(例如 Activity 或 Fragment)还存在。- 当内存不足时,App 在后台运行,进程会杀死 App。
实际上 Activity 总是与其视图一起被销毁。因此,在 Activity 中没有 onDestroyView () 生命周期方法。只有在 Fragment 中有 onDestroyView () 生命周期方法。在大多数情况下 Fragment 和它的视图一起被销毁。但是通过 Fragment transaction 用一个 Fragment 替换另一个 Fragment 时,栈下面的 Fragment 仍然存在,但是它的视图被破坏了。![]()
当一个 Fragment 被另一个 Fragment 替换时,会调用 onDestroyView () 方法,但不会调用 onDestroy () 或 onDetect () 方法。正因为这种复杂性,在使用 Fragment 时,会遇到许多奇怪的问题。和 Fragment 相关的问题,将会在后面的文章中分享。问题七:Lifecycle Aware Coroutine
在 App 中使用协程,如何确保它们的生命周期可感知。- 对于普通视图,只需在 Activity 或 Fragment 中使用 lifecycleScope,在 ViewModel 中使用 viewModelScope。
- 对于组合视图,需要使用 stateFlow 中的 collectAsState() 方法,因为当可组合函数不活动时,它不会收集。
对于普通视图,即使 lifecycleScope 是可用的,它在 Activity 或 Fragment 的整个生命周期中都处于活动状态。因为有时我们希望某些场景只在 onStart() 或 onResume() 之后处于活动状态。为此,我们需要在 lifecycleScope 中使用像 repeatOnLifecycle 这样的 API 提供额外的作用域。lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.stateFlowValue.collect {
// Do something
}
}
}
![]()
对于组合视图 collectAsState() 不会确保在组合函数处于活动状态时安全使用数据,它也不会停止继续发送 StateFlow,这会导致资源浪费。为了确保只在 Activity 或 Fragment 处于正确的生命周期时,例如在 onStart () 之后发出,我们需要使用 collectAsStateWithLifecycle () 和 stateFlow 中的 WhileSubscribed (...)。当我们在研究 collectAsStateWithLifecycle() 源码时,发现它也在使用 repeatOnLifecycle(…)。![]()
全文到这里就结束了,感谢你的阅读,坚持原创不易,欢迎 在看、点赞、分享 给身边的小伙伴,我会持续分享原创干货!!!
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!