# Gold three silver four again
Jetpack is a plus in previous interviews, but with Modern Android Development (MAD) being promoted this year, it’s almost a must.
Many candidates are full of details about Jetpack’s features and uses, but are often stumped when asked how they work. It doesn’t hurt to use the API, but because of that, if you have some knowledge of the source code, you might get a bonus.
This article shares an entry-level source code analysis that is often asked in an interview
# ViewModel
ViewModel is an important component in Android Jetpack. It has the advantage of having a life cycle like the one shown below and not being destroyed due to changes in Activity configuration such as screen rotation. It is an important basis for implementing UI state management in MVVM architecture.
class MyActivity : AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate")
val activity: FragmentActivity = this
val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()
// The viewModel here is still the instance before the rebuild due to the destruction and reconstruction
val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)
// A new ViewModel instance is created if the instance is directly new
// val viewModel = MyViewModel()
Log.d(TAG, " - Activity :${this.hashCode()}")
Log.d(TAG, " - ViewModel:${viewModel.hashCode()}")}}Copy the code
The log of the above code when switching between vertical and horizontal screens is as follows:
Oncreate-activity: 132818886-viewModel :249530701 onStart onResume # Rotate onPause onStop OnRetainNonConfigurationInstance onDestroy onCreate - Activity: 103312713 # Activity instance is different - the ViewModel: 249530701 #ViewModel instance same onStart onResumeCopy the code
The following code is the key to ensuring that the ViewModel is not destroyed when the screen is switched. Let’s take a look at the source code for the entry
val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)
Copy the code
# ViewModelProvider
ViewModelProvider source code is very simple, hold a ViewModelProvider respectively. The Factory and ViewModelStore instance
package androidx.lifecycle;
public class ViewModelProvider {
public interface Factory {
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store; }... }Copy the code
Get () returns the ViewModel instance
package androidx.lifecycle;
public class ViewModelProvider {...public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); .return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if(viewModel ! =null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return(T) viewModel; }... }Copy the code
The logic is clear:
- The ViewModelProvider obtains the ViewModel from the ViewModelStore
- If get failure, then through ViewModelProvider. Factory to create the ViewModel
# ViewModelStore
package androidx.lifecycle;
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear(a) {
for(ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); }}Copy the code
As you can see, the ViewModelStore is a wrapper around a Map.
val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
Copy the code
The FragmentActivity passed in the ViewModelProvider() constructor parameter 1 above (the base class is ComponentActivity) is actually an implementation of ViewModelStoreOwner.
package androidx.lifecycle;
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore(a);
}
Copy the code
The ViewModelStore in the ViewModelProvider comes from the ViewModelStoreOwner.
public class ViewModelProvider {
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
this.mViewModelStore = store;
}
Copy the code
The Activity in onDestroy attempts to clear the ViewModelStore. If the Destroy is set up because of ConfigurationChanged, the ViewModel will not be cleared to avoid the destruction caused by vertical/horizontal switching.
//ComponentActivity.java
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if(! getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); ()) { getViewModelStore().clear(); }}}});Copy the code
# FragmentActivity#getViewModelStore()
FragmentActivity implements the getViewModelStore method of ViewModelStoreOwner
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner.{
private ViewModelStore mViewModelStore;
@NonNull
@Override
public ViewModelStore getViewModelStore(a) {...if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = newViewModelStore(); }}return mViewModelStore;
}
static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; FragmentManagerNonConfig fragments; }... }Copy the code
Through getLastNonConfigurationInstance () to obtain NonConfigurationInstances instance, to get the real viewModelStore, GetLastNonConfigurationInstance ()?
# Activity#getLastNonConfigurationInstance()
package android.app;
public class Activity extends ContextThemeWrapper implements.{
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
@Nullable
public Object getLastNonConfigurationInstance(a) {
returnmLastNonConfigurationInstances ! =null
? mLastNonConfigurationInstances.activity : null;
}
Copy the code
Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.
We know through the official document, before the screen rotation by onRetainNonConfigurationInstance () returns the Activity instance, after the screen rotation by getLastNonConfigurationInstance (), So before and after the screen rotation at onRetainNonConfigurationInstance without destruction of the key
# Activity#onRetainNonConfigurationInstance()
Oncreate-activity: 132818886-viewModel :249530701 onStart onResume # Rotate onPause onStop OnRetainNonConfigurationInstance onDestroy onCreate - Activity: 103312713 # Activity instance is different - the ViewModel: 249530701 #ViewModel instance same onStart onResumeCopy the code
When the screen rotation, onRetainNonConfigurationInstance () between the onStop and onDestroy calls
package android.app;
public class Activity extends ContextThemeWrapper implements.{
public Object onRetainNonConfigurationInstance(a) {
return null; }... }Copy the code
OnRetainNonConfigurationInstance only empty implementation in the Activity, in FragmentActivity be rewritten
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner.{
@Override
public final Object onRetainNonConfigurationInstance(a) {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; FragmentManagerNonConfig fragments; }... }Copy the code
FragmentActivity by onRetainNonConfigurationInstance () returns the deposit ViewModelStore NonConfigurationInstances instance. It is worth mentioning onRetainNonConfigurationInstance provides a hook timing: onRetainCustomNonConfigurationInstance, allows us to make custom object like the ViewModel not be destroyed
NonConfigurationInstances will be passed by the system to the new reconstruction in the attach the Activity:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken)
Copy the code
Then, in the onCreate through getLastNonConfigurationInstance () to obtain the ViewModelStore NonConfigurationInstances
package androidx.fragment.app;
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner.{
private ViewModelStore mViewModelStore;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! =null&& nc.viewModelStore ! =null && mViewModelStore == null) { mViewModelStore = nc.viewModelStore; }... }}Copy the code
# summary
The Activity starts for the first time
- FragmentActivity# onCreate () is invoked
- The mViewModelStore of FragmentActivity is still null
- HogeActivity’s onCreate() is called
- ViewModelProvider instance created
- FragmentActivity#getViewModelStore() is called and mViewModelStore is created and assigned
Screen rotation occurs
- FragmentActivity# onRetainNonConfigurationInstance () is invoked
- Hold mViewModelStore NonConfigurationInstances instance is returned
The Activity to rebuild
- FragmentActivity# onCreate () is invoked
- From Activity# getLastNonConfigurationInstance () to obtain NonConfigurationInstances instance
- NonConfigurationInstances preserved the screen rotation in the former FragmentActivity mViewModelStore, its assignment to rebuild after FragmentActivity mViewModelStore
- HogeActivity# onCreate () is invoked
- Get the ViewModel instance via ViewModelProvider#get()