The use and source code analysis of Android Jetpack's ViewModel

The ViewModel class is a kind of business logic or screen-level state container. It is used to expose state to interfaces, and to encapsulate related business logic. Its main advantage is that it caches state and persists it across configuration changes. This means that the interface will not need to refetch data when navigating between activities or after making configuration changes, such as when the screen is rotated.

Now, the commonly used project architecture is changing from MVP to MVVM. Compared with P (presenter) in MVP, what are the advantages of ViewModel in MVVM?

Advantages of ViewModel

  • Can persist interface state (for example, screen rotation)
  • Provides access to business logic()

Let's first look at the use of ViewModel through code. Then, look at the principle of ViewModel

Text content:

1. The use of viewModel

Before, I learned LiveData. Here, we use the combination of LiveData+ViewModel.

insert image description here

First create a ViewModel to update UI data

public class MyViewModel extends ViewModel {
    //liveData
    public MutableLiveData<String> name = new MutableLiveData<>();

    /**
     * 获取用户名字
     */
    public void obtainUserName() {
        name.setValue("张三");
    }
}


Create an Activity below, use 2 TextViews, 1 update data with ViewModel; update data in a normal way

public class ViewModelActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_livedata_test);
        //textView用viewModel更新数据
        final TextView tvContent = findViewById(R.id.tv_content);
        
        //textView用普通方式更新数据
        final TextView tvUnUnUsedViewModelContent = findViewById(R.id.tv_content_unused_viewmodel);
        //获取viewModel实例
        final MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
        //添加LiveData观察者
        myViewModel.name.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tvContent.setText(s);
            }
        });
        //点击按钮,更新2个TextView的数据
        findViewById(R.id.btn_livedata_change).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.obtainUserName();
                tvUnUnUsedViewModelContent.setText("xxx");
            }
        });
    }
}


Finally, there is the manifest file, which does not configure anything

At this point, after we run the project, click the button, and the data will be displayed normally.

insert image description here

However, if we rotate the screen, we will find that the data of the first TextView still exists, but the data of the second TextView returns to the default state
insert image description here

At this point, the basic use of ViewModel+LiveData is introduced

  • Create ViewModels
  • Activity gets the ViewModel object
  • Add LiveData observer in ViewModel

When the data changes, the state of the UI can be changed automatically.

However, the advantage of ViewModel over ordinary data updates is that data will not be lost when the screen is rotated.

And, the scope of ViewModel will be limited to Lifecycle of ViewModelStoreOwner. It remains in memory until its ViewModelStoreOwner permanently disappears. The implementation classes of ViewModelStoreOwner are (Activity, Fragment, etc.)

ViewModel is very simple to use, let's see how it is implemented

Two, ViewModel source code analysis

The use of ViewModel is just to instantiate a ViewModel, and then call the data inside the ViewModel to update the data.

Let's take a look at how the ViewModel is instantiated. instantiation code

 final MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

Here, we divide into 2 steps

  • 1, new ViewModelProvider gets provider
  • 2. Obtain an instance of ViewModel through get(XXViewModel.class)

First look at what new ViewModelProvider() does

2.1, new ViewModelProvider(this)

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

...

//实现了ViewModelStoreOwner,HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner 
	

We passed this(Activity) in new ViewModelProvider().
Here's a look at Activity, which implements the ViewModelStoreOwner and HasDefaultViewModelProviderFactory interfaces

Instantiate the ViewModelProvider and call the constructor of this, which is equivalent to this(activity.getViewModelStore(), activity.getDefaultViewModelProviderFactory())

It is equivalent to getting both parameters from the activity.

First look at the constructor of this() with 2 parameters, and finally, what is the obtained content

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

We found that ViewModelProvider is to save data

  • saveViewModelStore
  • save factory

Next, look at the factory and ViewModelStore obtained from the activity

First look at the code to get ViewModelStore

  • ComponentActivity#getViewModelStore()
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
		//如果是null的话
        if (mViewModelStore == null) {
			//查看,最后一次配置(横竖屏)时候,里面是否已经保存了ViewModelStore
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
				//如果已经保存了,这里直接恢复
                mViewModelStore = nc.viewModelStore;
            }
			//没有保存,就new一个新的出来
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

Through the above code, we can find that if it is switching between horizontal and vertical screens.

If getLastNonConfigurationInstance() will save the previous viewmodelstore, it will directly restore the previous viewModelStore, which is why, if we switch between horizontal and vertical screens, the data will not be lost.

  • Get the code of the factory
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

If the factory is null, directly instantiate a SavedStateViewModelFactory factory

Go here and read new ViewModelProvider()

Overall process:

insert image description here

2.2,ViewModelProvider#get(MyViewModel.class)分析

From the incoming class, we can basically guess that it should be the ViewModel reconstructed by reflection

Look at the specific code


    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
		//这里传入的是类的全路径 (xx.xx.Test),class
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

Look at the get overloaded method


 @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
		//查看store里面是否保存了该key的ViewModel
		//如果是横竖屏切换的话,store中是保存有ViewModel的
        ViewModel viewModel = mViewModelStore.get(key);
		//如果viewModel是该class的。直接返回
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
		//通过上面,我们知道activity里面默认的Factory是SavedStateViewModelFactory
		//SavedStateViewModelFactory实现了KeyedFactory类
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
		//创建的viewmodel保存到store中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

Here, first check whether the ViewModel is saved in the viewModeStore, if it is saved, return directly, if not, call the create() method to create it, and save it in the viewModelStore.

main effect:

  • Get ViewModel from ViewModelStore
  • If not above, get the ViewModel through the Factory#create() method
  • Put the ViewModel into the ViewModelStore

Let's look at the Factory#create() method
. As mentioned above, the default Factory is SavedStateViewModelFactory, which inherits KeyedFactory. Look directly at its create() method

SavedStateViewModelFactory.java

	@Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
		//获取ViewModel构造器类型(AndroidViewModel还是ViewModel)
        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);
        try {
			//通过反射获取ViewModel
            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;
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to access " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("An exception happened in constructor of "
                    + modelClass, e.getCause());
        }
    }

Facory obtains ViewModel, mainly to obtain a suitable constructor to create ViewModel objects through reflection.

At this point, we have seen the whole process of ViewModel acquisition.

Get the overall flowchart of ViewModel

insert image description here

3. Other relevant knowledge points

3.1 ViewModelStore class

Have you found out that the ViewModelStore class appears most in it, let's take a look at this class

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

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

Through the above, we know that this class encapsulates the Map object. The cached ViewModel is also cached in the Map.

3.2 How does the viewModel save data when the Activity is rebuilt?

When the screen rotation occurs in the Activity, the onRetainCustomNonConfigurationInstance() method of the Activity will be executed

Let's look at the ComponentActivity#onRetainCustomNonConfigurationInstance() method

  @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
			//如果viewModelStore不是空的话,会获取他最后的配置
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }
		//把viewModelStore的内容重新恢复
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

Here, it can be clearly seen that the content in viewModelStore is restored here.

So, when the Activity is rebuilt, how is the data saved?

When the Activity screen rotates, the overall process should be

  • Before the Activity is destroyed, the ActivityThread#performDestroyActivity() method is executed.
    If the Activity is rebuilt, the activity configuration NonConfigurationInstances information will be saved in the ActivityClientRecord
  • When the Activity is rebuilt, the activity#performLaunchActivity(ActivityClientRecord,Intent) method
  • Execute the activity.attach() method to pass the lastNonConfigurationInstances object.

In this way, the recovery of the ViewModelStore content is guaranteed. (A lot of processes are omitted in the middle, if you are interested, please read it yourself)

ViewModel life cycle. The various lifecycle states in which an Activity goes through a screen rotation and ends. The diagram also shows the ViewModel's lifecycle alongside the associated activity's lifecycle. This diagram illustrates the various states of the activity. These basic states also apply to the fragment lifecycle.

official map
insert image description here

3.3, when will the ViewModel data be cleared?

When the Activity is initialized, it is judged through Lifecycle, the code

 public ComponentActivity() {
	...
 	getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
				//当页面销毁时
                if (event == Lifecycle.Event.ON_DESTROY) {
					//并且不是横竖屏切换等reLaunchActivity的情况
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });

	}

When the Activity is initialized, the data that has been set for the ViewModelStore will be cleaned up when the page is onDestroy.

Official document: https://developer.android.google.cn/topic/libraries/architecture/viewmodel


ViewModel类型:SharedViewModel,AndroidViewModel,ViewModel,SavedStateViewModel

Guess you like

Origin blog.csdn.net/ecliujianbo/article/details/128023830