series
Back to Jetpack: Dependencies and transitive relationships of Jetpack’s major components
Changes to using activities and fragments under AdroidX
Can you really use a Fragment? Fragments FAQ and new postures for using fragments on androidx
AndroidX Fragment1.2.2 Source code analysis
Fragment returns the stack preparation section
Jetpack’s Fragment return stack is a Fragment return stack demo
preface
As we all know, activity has a set of onSaveInstanceState-onRestoreInstanceState state saving mechanism, which aims to “system resource recovery” or “configuration change” state saving, to provide users with a better experience
On androidx, the SavedState library is provided to help activities and fragments handle state saving and recovery
This article assumes that you have some understanding of the status Saving mechanism. For this part, go to Saving UI States
For process management on Android, Ian Lake’s Who Lives and Who Dies? Process priorities on Android
The viewModel-SavedState library is introduced in this article. The viewModel-SavedState library is introduced in this article. The viewModel-SavedState library is introduced in this article
Is there anything in software engineering that the middle tier can’t solve
Before we look at the SavedState library we need to talk a little bit about ComponentActivity
As of AndroidX Activity 1.0.0, ComponentActivity becomes a base class for FragmentActivity and AppCompatActivity.
As the saying goes, “Every cause must have fruit”, with a strong curiosity, I checked the reason for the introduction of ComponentActivity.
Can see ComponentActivity inherited androidx.core.app.Com ponentActivity (in fragment library), and initially only LifecycleOwner interface is realized
The inheritance of the activity we created now looks like this:
So back to the original question, why introduce ComponentActivity? Actually look at the class structure of ComponentActivity now the answer is very clear
ComponentActivity implements five interfaces, representing its five roles in addition to the activity. In line with the principle of single function, the authorities set up an intermediate layer to delegate some functions to specialized classes. OnBackPressedDispatcherOwner is we speak fragments return stack (on the back of the Jetpack OnBackPressedDispatcher 】 fragments return stack preparation) mentioned in structure, And the SavedStateRegistryOwner is a member of the main character SavedState that we are going to talk about today
SavedState
The introduction of SavedState
implementation "Androidx. Savedstate: savedstate: 1.0.0."
Copy the code
You don’t need to declare it explicitly, because the Activity library has already introduced it internally. For the dependencies of jetpack components, see back Jetpack for the dependencies and transitions of jetpack’s main components
This is a very small library
Android ViewModels: State Persistence — SavedState
SavedStateProvider
A component that holds state that will be restored and used later
public interface SavedStateProvider {
@NonNull
Bundle saveState(a);
}
Copy the code
SavedStateRegistry
A component that manages the SavedStateProvider list, a registry that is bound to its owner’s lifecycle (that is, activities or fragments). Each creation lifecycle owner creates a new instance
Once the owner of the registry is created (for example, after calling the activity’s onCreate(savedInstanceState) method), its performRestore(State) method is called to restore any state saved before the system killed its owner.
void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
// ...
if(savedState ! =null) {
mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
}
// ...
}
Copy the code
The SavedStateProvider for each registry is identified by the unique key used to register it
private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>();
public void registerSavedStateProvider(@NonNull String key, @NonNull SavedStateProvider provider) {
SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
if(previous ! =null) {
throw new IllegalArgumentException("SavedStateProvider with the given key is already registered"); }}public void unregisterSavedStateProvider(@NonNull String key) {
mComponents.remove(key);
}
Copy the code
Once you complete registration, you can pass consumeRestoredStateForKey (key) to use a specific key state of reduction
public Bundle consumeRestoredStateForKey(@NonNull String key) {
if(mRestoredState ! =null) {
Bundle result = mRestoredState.getBundle(key);
// It is cleared after the call, and the second call returns NULL
mRestoredState.remove(key);
if (mRestoredState.isEmpty()) {
mRestoredState = null;
}
return result;
}
return null;
}
Copy the code
Note that this method retrieves the saved state and then clears its internal reference, meaning that calling it twice with the same key will return NULL on the second call
Once the registry has been restored to its saved state, it is up to the provider to decide whether to require its recovered data. If not, unused restored data will be saved again the next time the owner of the registry is killed by the system
A registered provider can save its state until its owner is killed by the system. When this happens, its Bundle saveState() method is called. For each registered SavedStateProvider, state can be saved like this.
savedState.putBundle(savedStateProviderKey, savedStateProvider.saveState());
Copy the code
The source code for the performSave(outBundle) method is shown below
void performSave(@NonNull Bundle outBundle) {
Bundle components = new Bundle();
// 1. Save the unused state
if(mRestoredState ! =null) {
components.putAll(mRestoredState);
}
// 2. Use SavedStateProvider to save the state
for (Iterator<Map.Entry<String, SavedStateProvider>> it = mComponents.iteratorWithAdditions(); it.hasNext(); ) {
Map.Entry<String, SavedStateProvider> entry1 = it.next();
components.putBundle(entry1.getKey(), entry1.getValue().saveState());
}
// 3. Save the bundle to the outBundle object
outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}
Copy the code
Perform state saving to merge all unused states with those provided by the registry. This outBundle is the bundle passed in to the activity’s onSaveInstanceState.
SavedStateRegistryController
A component that wraps SavedStateRegistry and allows it to be controlled through its two main methods: performRestore(savedState) and performSave(outBundle). These two methods will be handled internally by methods in SavedStateRegistry.
public final class SavedStateRegistryController {
private final SavedStateRegistryOwner mOwner;
private final SavedStateRegistry mRegistry;
public void performRestore(@Nullable Bundle savedState) {
// ...
mRegistry.performRestore(lifecycle, savedState);
}
public void performSave(@NonNull Bundle outBundle) { mRegistry.performSave(outBundle); }}Copy the code
SavedStateRegistryOwner
Component that holds SavedStateRegistry. By default, both ComponentActivity and Fragment in the AndroidX package implement this interface.
public interface SavedStateRegistryOwner extends LifecycleOwner {
@NonNull
SavedStateRegistry getSavedStateRegistry(a);
}
Copy the code
The state of the Activity is saved
One thing we need to be clear about here is what state does an activity save?
You can refer to the official documentation for this part
Simply put, the state of an activity can be divided into view state and member state
By default, the system uses the Bundle instance state to store information about each View object in the activity layout (for example, text values entered into EditText or the scrolling position of recyclerView). Therefore, if the Activity instance is destroyed and recreated, the layout state reverts to its previous state without requiring you to perform any code. (Note that the view whose state needs to be restored requires an ID.)
This part of the logic is implemented in the onSaveInstanceState method of the activity
About when saveInstanceState() is called
- TargetSdkVersion 28 and later: executed after onStop()
- TargetSdkVersion greater than 11 and less than 28: executed before onStop()
- TargetSdkVersion less than 11: executed before onPause()
SavedStateRegistryOwner (); SavedStateRegistryOwner (); SavedStateRegistryOwner
public class ComponentActivity extends androidx.core.app.ComponentActivity implements SavedStateRegistryOwner {
private final SavedStateRegistryController mSavedStateRegistryController = SavedStateRegistryController.create(this);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedStateRegistryController.performRestore(savedInstanceState);
// ...
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
// ...
// Call onSaveInstanceState to save the view state
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}
@NonNull
@Override
public final SavedStateRegistry getSavedStateRegistry(a) {
returnmSavedStateRegistryController.getSavedStateRegistry(); }}Copy the code
Its internal hold SavedStateRegistryController instance mSavedStateRegistryController, Query the saved state using the Controller’s performRestore method in the activity’s onCreate method. Use the Controller’s performSave method in onSaveInstanceState to save the state
In addition to view state and member state, an activity is also responsible for saving its internal fragment state. The onSaveInstanceState method of FragmentActivity saves the state of its internal fragment and restores the saved fragment in the onCreate method. This explains the problem of overlapping fragments if not handled properly
Fragment state is saved
Androidx Fragment uses the FragmentStateManager to handle fragment state saving
There are four ways to save the correlation
saveState
saveBasicState
saveViewState
saveInstanceState
The invocation chain is that the activity indirectly calls the FragmentManager’s saveAllState via the FragmentController, followed by the subsequent save method
Fragment state can be divided into view state, member state, and Child Fragment state
Regarding view state, the FragmentStateManager provides the saveViewSate method, which calls in two places:
- Called when the activity or parent Fragment triggers state saving, as described above
- The fragment is about to enter
onDestroyView
Called during the life cycle, with its location atFragmentManager
Inside the moveToState method, which explains why the replace operation added to the return stack automatically restores the view state when it returns
Member state is handled by the state mechanism in the activity, as described in the previous section
About child fragments, fragments of the onCreate method will be called restoreChildFragmentState child fragments to restore the state, And call performSaveInstanceState in the saveBasicState method in the FragmentStateManager to save the state of the Child Fragment
Viewmodel-SavedState
2020-01-22, ViewModel-savedState 1.0.0 official release, 02-05 official release 2.2.0
implementation "Androidx. Lifecycle: lifecycle - viewmodel - savedstate: 2.2.0." "
Copy the code
You don’t need to import the library manually because the Fragment library already imports it internally
UI State under Jetpack MVVM is usually held and stored by the ViewModel, so this module appears. After configuring this module, the ViewModel object will receive the SavedStateHandle object (key-value mapping) through its constructor. Allows you to save the state and query the saved state. These values persist after the system terminates the process and are available through the same object.
Android ViewModels: State Persistence — SavedState
SavedStateHandle
Internally holds a map of the key-values of the saved state, allowing states to be read and written that persist even after the application process is killed
SavedStateHandle is passed in through the ViewModel constructor, and the main main methods are as follows
- T get(String key)
- MutableLiveData getLiveData(String key)
- void set(String key, T value)
SavedStateHandle also contains an instance of SavedStateProvider, which is used to help the ViewModel owner save state
AbstractSavedStateViewModelFactory
An implementation ViewModelFactory. KeyedFactory ViewModel Factory, it will create a request and instantiate the ViewModel SavedStateHandle associated
public abstract class AbstractSavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {
private final SavedStateRegistry mSavedStateRegistry;
// Default state used when the saved state is empty
private final Bundle mDefaultArgs;
@Override
public final <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
// Read the saved state
Bundle restoredState = mSavedStateRegistry.consumeRestoredStateForKey(key);
// Create a handle that holds the state
SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, mDefaultArgs);
// ...
/ / create the viewModel
T viewmodel = create(key, modelClass, handle);
// ...
returnviewmodel; }}Copy the code
SavedStateViewModelFactory
The realization of a AbstractSavedStateViewModelFactory
public final class SavedStateViewModelFactory extends AbstractSavedStateVMFactory {
public SavedStateViewModelFactory(@NonNull Application application, @NonNull SavedStateRegistryOwner owner) {
this(application, owner, null);
}
public SavedStateViewModelFactory(@NonNull Application application, @NonNull SavedStateRegistryOwner owner, @Nullable Bundle defaultArgs) {
mSavedStateRegistry = owner.getSavedStateRegistry();
mLifecycle = owner.getLifecycle();
mDefaultArgs = defaultArgs;
mApplication = application;
mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
Constructor<T> constructor;
if (isAndroidViewModel) {
constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
} else {
constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
}
// doesn't need SavedStateHandle
if (constructor == null) {
return mFactory.create(modelClass);
}
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
T viewmodel;
if (isAndroidViewModel) {
viewmodel = constructor.newInstance(mApplication, controller.getHandle());
} else {
viewmodel = constructor.newInstance(controller.getHandle());
}
viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
return viewmodel;
/ /...}}Copy the code
The working process
ViewModelProvider(this).get(MyViewModel::class.java)
Copy the code
Create a ViewModel instance in your activity and pass this (SavedStateRegistryOwner) as a parameter that can access its SavedStateRegistry, If there is no incoming factory will pass the activity to rewrite getDefaultViewModelProviderFactory method to get the default factory. Factory then takes the saved state, wraps it in SavedStateHandle, and passes it to the ViewModel. The ViewModel can read and write to the Handle
When the activity’s onSaveInstanceState(outState) method is called, its SavedStateRegistry performSave(outState) method is executed, All saveState methods within the SavedStateProvider are executed, and once executed, outState contains the saved state
When the app is restarted, the activity and a new Registry are created, the activity’s onCreate(savedInstanceState) method is called, Then Registry’s performRestore(savedInstanceState) will be called to restore the previously saved state
The state saves the correct posture
The ViewModel constructor adds the SavedStateHandle argument and saves the data you want to save using the Handle
class WithSavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
private val key = "key"
fun setValue(value: String) = state.set(key, value)
fun getValue(a): LiveData<String> = state.getLiveData(key)
}
Copy the code
Don’t need to rewriteonSaveInstanceState/onRestoreInstanceState
methods
The Demo address
SavedState is only suitable for saving lightweight data. For heavyweight operations, please consider using persistent schemes such as SP and database
About me
I am a Fly_with24
- The Denver nuggets
- Jane’s book
- Github