目前项目采用单 Activity 模式,页面采用 Jetpack Navigation 导航
布局如下:Splash -> Home -> Detail
之前的设计逻辑
Activity
不做任何事情
ViewModel
持有 LiveData 类型变量 hasSDKInit,根据SDK初始化成功与否设置 true 或者 false
Splash
- 通过 parentActivity 实例化一个 ViewModel,并在 ViewModel 的构造函数中 初始化 SDK
- 监听 hasSDKInit, true ==》 跳转 Home,false ==》显示错误信息
Home
调用 SDK 实现相关功能。
发现有异常情况后调查生命周期
模拟APP被强杀可以去开发者选项打开
不保留活动
-
APP 打开
Splash onAttach Splash onCreate Activity onCreate Splash onCreateView Splash onViewStateRestored Splash onStart Activity onStart Activity onResume Splash onResume Splash onPause Splash onStop Home onAttach Home onCreate Home onCreateView Home onViewStateRestored Home onStart Splash onDestroy Splash onDetach Home onResume
-
APP 进入后台,相关生命周期:
Home onPause Activity onPause Home onStop Activity onStop Home onSaveInstanceState Activity onSaveInstanceState
-
APP 在后台被杀
Home onDestroy Home onDetach Activity onDestroy
-
APP在后台被杀后再次进入前台
Home onAttach Home onCreate Activity onCreate Home onCreateView Home onViewStateRestored Home onStart Activity onStart Activity onResume Home onResume
错误原因分析及生命周期理解
很明显,被杀后与 Activity 生命周期关联的 ViewModel 也结束了,与新打开 APP 的区别是,这时候是没有通过 Splash 去 初始化 SDK 的, Home 直接调用一个没有初始化的SDK 实例当然就报错了。
解决办法
预备知识
ViewModel 是与初始化它的 ViewLifeCycleOwner 唯一绑定的,全局唯一,不会重复实例化
ViewModel 是生命周期感知的,但是在 onDestroy 中做了判断,如果是因为类似横竖屏、黑暗模式、语言等ConfigChanged
导致的销毁,ViewModel 是不会被销毁的。另外很重要的一点
无论在什么情况下,Activity 的 onCreate 一定在 Fragment 的 onCreateView 之前。
这样我们把 SDK 的初始化挪到 Activity::onCreate
就行了。
Fragment 中通过``ViewModelProvider(requireActivity()).get(GlobalViewModel::class.java)`获取。
把 init SDK 放到 ViewModel 的构造函数中,并将 initSDK 方法私有化,不允许从外部调用
context 通过新建ViewModelFactory
类传入。
Google 官方不推荐 ViewModel 持有任何形式的 Context,如果确实要用,可以考虑单例或者 AndroidViewModel
Splash 页面监听 hasSDKInit 逻辑不变。
Home 页面原有逻辑不变,必要时加上对 hasSDKInit 的监听,true 才可进行后续操作。
而,如果SDK 的 init 不依赖网络等其他因素,默认情况下因为所有调用都在主线程,那么 就不用做监听。