[Android] Source code analysis how ViewModel does not destroy when switching between horizontal and vertical screens

Insert picture description here

ViewModel


ViewModel is an important component in Android Jetpack. Its advantage is that it has a life cycle as shown in the figure below and will not be destroyed due to changes in Activity configuration such as screen rotation. It is an important foundation for implementing UI state management in the MVVM architecture.
Insert picture description here

class HogeActivity : AppCompatActivity {
    
    

  override fun onCreate(savedInstanceState: Bundle?) {
    
    
    super.onCreate(savedInstanceState)
    Log.d(TAG, "onCreate")

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // Activity由于横竖品切换销毁重建,此处的viewModel 仍然是重建前的实例
    val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
    // 如果直接new实例则会创建新的ViewModel实例
	//  val viewModel = FooViewModel()

    Log.d(TAG, "  - Activity :${
      
      this.hashCode()}")
    Log.d(TAG, "  - ViewModel:${
      
      viewModel.hashCode()}")
  }
}

The log of the above code when switching between horizontal and vertical screens is as follows:

#Activity初次启动
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity实例不同
  - ViewModel:249530701  #ViewModel实例相同
onStart
onResume

The following code is the key to ensure that the ViewModel is not destroyed when the screen is switched. Let's take a look at the source code for the entrance in turn

val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)

ViewModelProvider


ViewModelProvider source code is very simple, hold one ViewModelProvider.Factoryand ViewModelStoreinstance respectively

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;
    }

    ...
}

get()Return 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;
    }

    ...
}

The logic is very clear:

  1. ViewModelProvider obtains ViewModel through ViewModelStore
  2. If the acquisition fails, create a ViewModel through ViewModelProvider.Factory

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() {
    
    
        for (ViewModel vm : mMap.values()) {
    
    
            vm.onCleared();
        }
        mMap.clear();
    }
}

It can be seen that ViewModelStore is a right Mappackage.

val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)

The above code ViewModelProvider() passed in in the construction parameter 1 FragmentActivity(the base class is ComponentActivity) is actually ViewModelStoreOwneran implementation.

package androidx.lifecycle;

public interface ViewModelStoreOwner {
    
    
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStore in ViewModelProvider is from 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;
    }

FragmentActivity#getViewModelStore()


FragmentActivityImplements ViewModelStoreOwnerthe getViewModelStoremethod

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {
    
    

    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
    
    
        ...

        if (mViewModelStore == null) {
    
    
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
    
    
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
    
    
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

    static final class NonConfigurationInstances {
    
    
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    ...
}

By getLastNonConfigurationInstance()acquiring NonConfigurationInstancesinstance to get real viewModelStore, getLastNonConfigurationInstance()what is it?

Activity#getLastNonConfigurationInstance()


package android.app;

public class Activity extends ContextThemeWrapper implements ... {
    
    

    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;

    @Nullable
    public Object getLastNonConfigurationInstance() {
    
    
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

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.

From official documents, we know that through the onRetainNonConfigurationInstance()returned Activity instance before the screen is rotated, it can be getLastNonConfigurationInstance()obtained after the screen is rotated , so the key to not destroying the screen before and after rotation isonRetainNonConfigurationInstance


Activity#onRetainNonConfigurationInstance()


#Activity初次启动
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity实例不同
  - ViewModel:249530701  #ViewModel实例相同
onStart
onResume

When the screen rotation, onRetainNonConfigurationInstance()in onStopand onDestroybetween calls

package android.app;

public class Activity extends ContextThemeWrapper implements ... {
    
    

    public Object onRetainNonConfigurationInstance() {
    
    
        return null;
    }

    ...
}

onRetainNonConfigurationInstanceThere is only empty implementation in Activity, FragmentActivitywhich is rewritten in

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {
    
    

    @Override
    public final Object onRetainNonConfigurationInstance() {
    
    
        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;
    }

    ...
}

FragmentActivity returns the NonConfigurationInstances instance storing the ViewModelStore through onRetainNonConfigurationInstance().
It is worth mentioning that it onRetainNonConfigurationInstanceprovides a hook opportunity:, onRetainCustomNonConfigurationInstanceallows us to make custom objects not destroyed like ViewModel

NonConfigurationInstances will be attachpassed by the system to the newly rebuilt Activity in:

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)
        

Then in onCreate, by getLastNonConfigurationInstance()obtaining ViewModelStore in 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;
        }
        ...
    }
}

Recap


Activity starts for the first time

  • FragmentActivity#onCreate() is called
    • At this time, the mViewModelStore of FragmentActivity is still null
  • HogeActivity's onCreate() is called
    • ViewModelProvider instance creation
    • FragmentActivity#getViewModelStore() is called, mViewModelStore is created and assigned

Screen rotation occurs

  • FragmentActivity#onRetainNonConfigurationInstance() is called
    • NonConfigurationInstances instance holding mViewModelStore is returned

Activity reconstruction

  • FragmentActivity#onCreate() is called
    • Get NonConfigurationInstances instance from Activity#getLastNonConfigurationInstance()
    • The mViewModelStore of FragmentActivity before the screen rotation is saved in NonConfigurationInstances, and it is assigned to the mViewModelStore of FragmentActivity after reconstruction
  • HogeActivity#onCreate() is called
    • Get ViewModel instance through ViewModelProvider#get()

Guess you like

Origin blog.csdn.net/vitaviva/article/details/109256198