# 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:

  1. The ViewModelProvider obtains the ViewModel from the ViewModelStore
  2. 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()