Reputation: 422
My app was saving a bigger bundle in activity onSaveInstanceState() each time the screen was rotated, and I came across something really confusing. Here is a minimal example:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val rcs = outState.getIntegerArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS")
val keys = outState.getStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS")
Log.d("MainActivity", "${rcs?.size}: $rcs, $keys")
}
}
I get the following output each time the screen is rotated:
3: [1332505437, 1835553837, 670316111], [FragmentManager:StartIntentSenderForResult, ...
6: [1332505437, 91080073, 1835553837, 381123153, 1187376284, 670316111], ...
...
See the full log after 6 rotations: https://pastebin.com/yfE04Fmc
Each time the screen is rotated, 3 elements are added to these two entries. After rotating the screen for a while, the instance state becomes significantly bigger for no apparent reason.
Target API: 30
Tested on: API 28 and 30
Does anyone know why this is happening?
UPDATE: I want to clarify that I do not save any state into the Bundle. The values I show are created by Android itself: in ActivityResultRegistry. I've created an empty project for this example, and the code I provided is the only code I have - there is nothing else that interacts with the state Bundle.
UPDATE 2: Bug report submitted: https://issuetracker.google.com/issues/191893160
UPDATE 3: Bug fixed, will be a part of Activity 1.3.0-rc02
and 1.2.4
releases
Upvotes: 5
Views: 1122
Reputation: 7271
Does anyone know why this is happening?
AppCompatActivity
is extended from ComponentActivity
(AppCompatActivity -> FragmentActivity -> ComponentActivity
)
ComponentActivity
is responsible for keeping the reference of ViewModelFactory
, so that when the Activity
recreates if can instantiate the same ViewModel
before screen rotation.
Now inside ComponentActivity
you will notice this:
@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
mActivityResultRegistry.onSaveInstanceState(outState);
}
Here the important part is mActivityResultRegistry.onSaveInstanceState(outState)
Inside this method you will see
public final void onSaveInstanceState(@NonNull Bundle outState) {
outState.putIntegerArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_RCS,
new ArrayList<>(mRcToKey.keySet()));
outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS,
new ArrayList<>(mRcToKey.values()));
outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS,
new ArrayList<>(mLaunchedKeys));
outState.putBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS,
(Bundle) mPendingResults.clone());
outState.putSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT, mRandom);
}
So you can see from there that the Android platform itself is saving this key in the bundle. Hence you can not do much about it.
In other word, One of the parents of AppCompatActivity
is saving these keys inside the Bundle
. If you are not using ViewModel
and do not want these keys to be saved; you can remove super.onSaveInstanceState(outState)
from MainActivity
(NOT RECOMMENDED). You will still get your saved keys but there could be some side effect, I am not aware of.
Good thing that you have reported an issue regarding this, let's see how the Google Team respond to it.
Upvotes: 2
Reputation: 2981
This may not solve your problem but one thing that you could try is removing the duplicate values. Keep the latest ones and remove the olders. Something like this:
Kotlin
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
removeDuplicates(outState)
}
private fun removeDuplicates(bundle: Bundle) {
val prevRCS = bundle.getIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS")
val newRCS = bundle.getIntegerArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS")
val newKeys = bundle.getStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS")
if (newRCS == null || newKeys == null) return
if (prevRCS == null) {
bundle.putIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS)
//bundle.putStringArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys)
} else if(prevRCS.size() != newRCS.size()) {
for (rcs in prevRCS) {
val index = newRCS.indexOf(rcs)
newRCS.remove(index)
newKeys.remove(index)
}
bundle.remove("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS")
bundle.remove("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS")
bundle.putIntegerArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS)
bundle.putIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS)
bundle.putStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys)
//bundle.putStringArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys)
}
}
Java
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
removeDuplicates(outState);
}
private static final void removeDuplicates(final Bundle bundle)
{
if(bundle == null) return;
final ArrayList<Integer> prevRCS = bundle.getIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS");
final ArrayList<Integer> newRCS = bundle.getIntegerArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS");
final ArrayList<String> newKeys = bundle.getStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS");
if(newRCS == null || newKeys == null) return;
if(prevRCS == null) {
bundle.putIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS);
//bundle.putStringArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys);
} else if(prevRCS.size() != newRCS.size()) {
for(Integer rcs : prevRCS) {
final int index = newRCS.indexOf(rcs);
newRCS.remove(index);
newKeys.remove(index);
}
bundle.remove("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS");
bundle.remove("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS");
bundle.putIntegerArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS);
bundle.putIntegerArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_RCS", newRCS);
bundle.putStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys);
//bundle.putStringArrayList("PREVIOUS_KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS", newKeys);
}
}
Upvotes: 0
Reputation: 96
It all depends on how you store the data in Bundle, according to standard Android developer document https://developer.android.com/guide/components/activities/activity-lifecycle#restore-activity-ui-state-using-saved-instance-state
Saving the state:
override fun onSaveInstanceState(outState: Bundle?) {
// Save the user's current game state
outState?.run {
putInt(STATE_SCORE, currentScore)
putInt(STATE_LEVEL, currentLevel)
}
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(outState)
}
companion object {
val STATE_SCORE = "playerScore"
val STATE_LEVEL = "playerLevel"
}
Restoring the state:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
with(savedInstanceState) {
// Restore value of members from saved state
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
} else {
// Probably initialize members with default values for a new instance
}
// ...
}
Am not sure why you are getting the data from the bundle in onSaveInstanceState() method.
Also since you are saving list, want to highlight this point.
Saved instance state bundles persist through both configuration changes and process death but are limited by storage and speed, because onSavedInstanceState() serializes data to disk. Serialization can consume a lot of memory if the objects being serialized are complicated. Because this process happens on the main thread during a configuration change, long-running serialization can cause dropped frames and visual stutter.
Do not use store onSavedInstanceState() to store large amounts of data, such as bitmaps, nor complex data structures that require lengthy serialization or deserialization. Instead, store only primitive types and simple, small objects such as String. As such, use onSaveInstanceState() to store a minimal amount of data necessary, such as an ID, to re-create the data necessary to restore the UI back to its previous state should the other persistence mechanisms fail. Most apps should implement onSaveInstanceState() to handle system-initiated process death
Upvotes: 1