Search code examples
androidandroid-lifecycleandroid-viewmodel

What is the difference between "Don't keep activities" and a Configuration change?


I am using View Model architecture component to handle the UI states and contain Business logic. It is known it survives a configuration change, but when Don't keep activities option is turned on in Developer options, a new instance of View Model is created. The activity onDestroy is called in both the cases then what is the difference between the two?

I am maintaining a state machine in the View Model and face issue in restoring it since View Model does not survive this. I cannot use Instance state since it has complex objects.

Following logs are from a sample project I tried. This has an Activity TestActivity which contains a fragment TestFragment, and TestFragment contains two fragments TestStateAFragment and TestStateBFragment

TestActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_test)

    if(savedInstanceState == null) {
        supportFragmentManager.beginTransaction()
            .add(R.id.container_fragment_test, TestFragment())
            .commitAllowingStateLoss()
    }
}

TestFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    Timber.i("onViewCreated")
    super.onViewCreated(view, savedInstanceState)
    if(savedInstanceState == null) {
        childFragmentManager.beginTransaction()
            .add(R.id.container_state_a, TestStateAFragment.newInstance())
            .commitAllowingStateLoss()
        childFragmentManager.beginTransaction()
            .add(R.id.container_state_b, TestStateBFragment.newInstance())
            .commitAllowingStateLoss()
    }
}

TestStateAFragment.kt

private val compositeDisposable: CompositeDisposable = CompositeDisposable()
private lateinit var viewModel : TestStateAViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    Timber.i("onCreate")
    super.onCreate(savedInstanceState)

    viewModel = ViewModelProviders.of(this, TestStateAViewModelFactory())
        .get(TestStateAViewModel::class.java)
    Timber.i(viewModel.toString())
}

Screen rotation :

(TestStateAFragment.kt:101)#onPause: onPause
(TestStateBFragment.kt:66)#onPause: onPause
(TestFragment.kt:72)#onPause: onPause
(TestActivity.kt:45)#onPause: onPause
(TestStateAFragment.kt:105)#onStop: onStop
(TestStateBFragment.kt:70)#onStop: onStop
(TestFragment.kt:76)#onStop: onStop
(TestActivity.kt:50)#onStop: onStop
(TestActivity.kt:23)#onSaveInstanceState: onSaveInstanceState
(TestFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestStateAFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestActivity.kt:54)#onDestroy: onDestroy
(TestStateAFragment.kt:110)#onDestroyView: onDestroyView
(TestStateBFragment.kt:75)#onDestroyView: onDestroyView
(TestFragment.kt:81)#onDestroyView: onDestroyView
(TestStateAFragment.kt:115)#onDestroy: onDestroy
(TestStateBFragment.kt:80)#onDestroy: onDestroy
(TestFragment.kt:86)#onDestroy: onDestroy
(TestFragment.kt:17)#onAttach: onAttach
(TestFragment.kt:18)#onAttach: TestFragment{984b690 (625e084e-68d8-46d6-9527-dfe2694fa5c1) id=0x7f07004c}
(TestFragment.kt:23)#onCreate: onCreate
(TestStateAFragment.kt:30)#onAttach: onAttach
(TestStateAFragment.kt:31)#onAttach: TestStateAFragment{1ff9e9a (f73d9a4a-4fb0-4e1c-b6b0-220941525ae2) id=0x7f07004d}
(TestStateAFragment.kt:39)#onCreate: onCreate
(TestStateAFragment.kt:44)#onCreate: com.example.lifecycleviewmodel.fragment.states.a.TestStateAViewModel@afc805d
(TestStateBFragment.kt:27)#onAttach: onAttach
(TestStateBFragment.kt:28)#onAttach: TestStateBFragment{908b1a7 (a5fa2bce-9b3b-4474-ae2a-0a891f289f65) id=0x7f07004e}
(TestStateBFragment.kt:33)#onCreate: onCreate
(TestActivity.kt:34)#onStart: onStart
(TestFragment.kt:34)#onCreateView: onCreateView
(TestFragment.kt:40)#onViewCreated: onViewCreated
(TestStateAFragment.kt:67)#onCreateView: onCreateView
(TestStateAFragment.kt:73)#onViewCreated: onViewCreated
(TestStateBFragment.kt:44)#onCreateView: onCreateView
(TestStateBFragment.kt:50)#onViewCreated: onViewCreated
(TestFragment.kt:61)#onStart: onStart
(TestStateAFragment.kt:90)#onStart: onStart
(TestStateBFragment.kt:55)#onStart: onStart
(TestActivity.kt:40)#onResume: onResume
(TestFragment.kt:66)#onResume: onResume
(TestStateAFragment.kt:95)#onResume: onResume
(TestStateBFragment.kt:60)#onResume: onResume

Don't keep activities off :

(TestStateAFragment.kt:101)#onPause: onPause
(TestStateBFragment.kt:66)#onPause: onPause
(TestFragment.kt:72)#onPause: onPause
(TestActivity.kt:45)#onPause: onPause
(TestStateAFragment.kt:105)#onStop: onStop
(TestStateBFragment.kt:70)#onStop: onStop
(TestFragment.kt:76)#onStop: onStop
(TestActivity.kt:50)#onStop: onStop
(TestActivity.kt:23)#onSaveInstanceState: onSaveInstanceState
(TestFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestStateAFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestActivity.kt:30)#onRestart: onRestart
(TestActivity.kt:34)#onStart: onStart
(TestFragment.kt:61)#onStart: onStart
(TestStateAFragment.kt:90)#onStart: onStart
(TestStateBFragment.kt:55)#onStart: onStart
(TestActivity.kt:40)#onResume: onResume
(TestFragment.kt:66)#onResume: onResume
(TestStateAFragment.kt:95)#onResume: onResume
(TestStateBFragment.kt:60)#onResume: onResume
(TestStateAFragment.kt:101)#onPause: onPause
(TestStateBFragment.kt:66)#onPause: onPause
(TestFragment.kt:72)#onPause: onPause
(TestActivity.kt:45)#onPause: onPause
(TestStateAFragment.kt:105)#onStop: onStop
(TestStateBFragment.kt:70)#onStop: onStop
(TestFragment.kt:76)#onStop: onStop
(TestActivity.kt:50)#onStop: onStop
(TestActivity.kt:23)#onSaveInstanceState: onSaveInstanceState
(TestFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestStateAFragment.kt:55)#onSaveInstanceState: onSaveInstanceState

Don't keep activities ON :

(TestStateAFragment.kt:101)#onPause: onPause
(TestStateBFragment.kt:66)#onPause: onPause
(TestFragment.kt:72)#onPause: onPause
(TestActivity.kt:45)#onPause: onPause
(TestStateAFragment.kt:105)#onStop: onStop
(TestStateBFragment.kt:70)#onStop: onStop
(TestFragment.kt:76)#onStop: onStop
(TestActivity.kt:50)#onStop: onStop
(TestActivity.kt:23)#onSaveInstanceState: onSaveInstanceState
(TestFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestStateAFragment.kt:55)#onSaveInstanceState: onSaveInstanceState
(TestActivity.kt:54)#onDestroy: onDestroy
(TestStateAFragment.kt:110)#onDestroyView: onDestroyView
(TestStateBFragment.kt:75)#onDestroyView: onDestroyView
(TestFragment.kt:81)#onDestroyView: onDestroyView
(TestStateAFragment.kt:115)#onDestroy: onDestroy
(TestStateBFragment.kt:80)#onDestroy: onDestroy
(TestFragment.kt:86)#onDestroy: onDestroy
2019-09-18 20:30:10.503 31473-31473/com.example.lifecycleviewmodel W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@d382041
(TestFragment.kt:17)#onAttach: onAttach
(TestFragment.kt:18)#onAttach: TestFragment{544b1c5 (625e084e-68d8-46d6-9527-dfe2694fa5c1) id=0x7f07004c}
(TestFragment.kt:23)#onCreate: onCreate
(TestStateAFragment.kt:30)#onAttach: onAttach
(TestStateAFragment.kt:31)#onAttach: TestStateAFragment{ea5f827 (f73d9a4a-4fb0-4e1c-b6b0-220941525ae2) id=0x7f07004d}
(TestStateAFragment.kt:39)#onCreate: onCreate
(TestStateAFragment.kt:44)#onCreate: com.example.lifecycleviewmodel.fragment.states.a.TestStateAViewModel@fa43f72
(TestStateBFragment.kt:27)#onAttach: onAttach
(TestStateBFragment.kt:28)#onAttach: TestStateBFragment{f66bb79 (a5fa2bce-9b3b-4474-ae2a-0a891f289f65) id=0x7f07004e}
(TestStateBFragment.kt:33)#onCreate: onCreate
(TestActivity.kt:34)#onStart: onStart
(TestFragment.kt:34)#onCreateView: onCreateView
(TestFragment.kt:40)#onViewCreated: onViewCreated
(TestStateAFragment.kt:67)#onCreateView: onCreateView
(TestStateAFragment.kt:73)#onViewCreated: onViewCreated
(TestStateBFragment.kt:44)#onCreateView: onCreateView
(TestStateBFragment.kt:50)#onViewCreated: onViewCreated
(TestFragment.kt:61)#onStart: onStart
(TestStateAFragment.kt:90)#onStart: onStart
(TestStateBFragment.kt:55)#onStart: onStart
(TestActivity.kt:40)#onResume: onResume
(TestFragment.kt:66)#onResume: onResume
(TestStateAFragment.kt:95)#onResume: onResume
(TestStateBFragment.kt:60)#onResume: onResume

So where do these two scenarios differ exactly and is there a way to determine which one took place?


Solution

  • When a configuration change occurs, the Android framework will call onRetainNonConfigurationInstance() on your Activity. You can return any object you want to. Then Android will destroy your Activity and immediate create a new instance. In onCreate() of the new instance you can call getLastNonConfigurationInstance(). If the Activity was recreated due to a configuration change, the object that you returned from onRetainNonConfigurationInstance() will be returned here. Otherwise the call returns null. In this case you can tell when your Activity was recreated due to a configuration change, and when it was (re)started for other reasons.

    The developer option "Don't keep activities" is not something that you usually have to deal with, as normal users should never have it enabled. You can use it for testing purposes to ensure that your Activity properly recovers in case Android decides to kill it off.

    In reality, Android usually doesn't kill off individual activities. If Android needs to recover resources from background applications it usually just kills off the entire OS process. However, I have recently seen some situations on some devices where Android does indeed kill off individual activities when an app is in the background. When the user then returns to the app, Android will recreate the Activity. In this case you will get null returned from getLastNonConfigurationInstance(), but you would get a non-null Bundle in the onCreate() call and you would also get a non-null Bundle in the onRestoreInstanceState(). So you should be able to tell the difference between:

    • Activity created for the first time
    • Activity instance created after configuration change
    • Activity instance recreated after Android killed it off and user returned to it

    See https://developer.android.com/reference/android/app/Activity.html?hl=en#onRetainNonConfigurationInstance()

    I'm not exactly sure of your requriements, but this information should help.