欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

搞懂Android Jetpack ViewModel 使用及原理

程序员文章站 2022-06-07 15:13:59
...

ViewModel 的官方解释

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存

ViewModel 的用处

  • 屏幕切换后,数据不会丢失
    根据官方简介,当界面发生横竖屏切换时,viewmodel中的数据不会发生变化。只有当Activity/Fragment执行finish被销毁时,ViewModel才会被销毁。ViewModel的生命结束周期是与Activity/Fragment一致的,所以不存在内存泄漏的问题。ViewModel对应的生命周期图如下所示,当屏幕旋转时,并不会执行onCleard回调,只有当调用finish()时,才会执行。
    搞懂Android Jetpack ViewModel 使用及原理
public class AppCompatActivity extends FragmentActivity 
……
public class FragmentActivity extends SupportActivity implements ViewModelStoreOwner,

由上面代码可知,AppCompatActivity 实现了ViewModelStoreOwner,自身有一个ViewModelStore对象mViewModelStore,当getViewModelStore()回调时,进行以下操作。
首先判断如果为null,则从上次屏幕切换时缓存的数据中获取,如果缓存中为null,则创建一个新的。

FragmentActivity.java

 @NonNull
    public ViewModelStore getViewModelStore() {
        if (this.getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
        } else {
            if (this.mViewModelStore == null) {
                FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
                if (nc != null) {
                    this.mViewModelStore = nc.viewModelStore;
                }

                if (this.mViewModelStore == null) {
                    this.mViewModelStore = new ViewModelStore();
                }
            }

            return this.mViewModelStore;
        }
    }
  • 在 Fragment 之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信

public class SharedViewModel extends ViewModel {
       private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

       public void select(Item item) {
           selected.setValue(item);
       }

       public LiveData<Item> getSelected() {
           return selected;
       }
   }

   public class MasterFragment extends Fragment {
       private SharedViewModel model;

       public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
           super.onViewCreated(view, savedInstanceState);
           model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
           itemSelector.setOnClickListener(item -> {
               model.select(item);
           });
       }
   }

   public class DetailFragment extends Fragment {

       public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
           super.onViewCreated(view, savedInstanceState);
           SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
           model.getSelected().observe(getViewLifecycleOwner(), { item ->
              // Update the UI.
           });
       }
   }

这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。此方法具有以下优势:
1.Activity 不需要执行任何操作,也不需要对此通信有任何了解。
2.除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
3.每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

  • 担任MVVM中的VM角色
    MVVM中的VM即是ViewModel,作为View和Model之间的连接桥梁,配合LiveData,可以轻松实现观察者模式。

ViewModel用法

创建ViewModel类,继承AndroidViewModel

public class UserViewModel extends AndroidViewModel {
   public MutableLiveData<String> notifyDateChanged = new MutableLiveData<>();

   protected CompositeDisposable mCompositeDisposable = new CompositeDisposable();

   @Override
   protected void onCleared() {
       super.onCleared();
       if (mCompositeDisposable != null) {
           mCompositeDisposable.clear();
       }
   }

   public UserViewModel(@NonNull Application application) {
       super(application);
   }
}

发送数据

mUserVM.notifyDateChanged.setValue("hello");

Activity/Fragment中初始化并添加监听,接收数据

 mUserVM = ViewModelProviders.of(this).get(UserViewModel.class);
 mUserVM.notifyDateChanged.observe(this, result -> {
          //todo here
        });

ViewModel 实例化源码分析

of(@NonNull FragmentActivity activity)

ViewModelStore是一个存储ViewModel的容器,ViewModel 通过键值对存放其中。
HashMap<String, ViewModel> mMap = new HashMap<>();

  1. 初始化ViewModelProvider(ViewModelProvider.java)
    主要作用是对mFactory,mViewModelStore进行赋值
 @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

AndroidViewModelFactory 是用来实例化ViewModel的工厂类,继承自Factory,此处AndroidViewModelFactory进行初始化。后面在获取ViewModel实例时,会调用到AndroidViewModelFactory.create(@NonNull Class<T> modelClass)来创建新的ViewModel

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }
  1. ViewModelStores.java,初始化ViewMoldeStore对象
    如果当前版本的FragmentActivity实现了ViewModelStoreOwner接口,则可以直接调用getViewModelStore()FragmentActivity会在getViewModelStore()回调中,创建ViewModelStore
    如果当前版本FragmentActivity 未实现ViewModelStoreOwner接口,则会创建一个空的Fragment->HolderFragment,来使ViewModelStore绑定其生命周期。
 public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }

FragmentActivity中的getViewModelStore()回调

 @NonNull
    public ViewModelStore getViewModelStore() {
        if (this.getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
        } else {
            if (this.mViewModelStore == null) {
                FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
                if (nc != null) {
                    this.mViewModelStore = nc.viewModelStore;
                }

                if (this.mViewModelStore == null) {
                    this.mViewModelStore = new ViewModelStore();
                }
            }

            return this.mViewModelStore;
        }
    }

从上面代码中可以推断出,在屏幕发生变化时,会保存ViewModelStore,再次创建时,会优先从缓存中读取。如果没有,则创建一个新的ViewModelStore

3.创建HolderFragment (以宿主为Activity为例)

HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);
            mNotCommittedActivityHolders.put(activity, holder);//以activity为键,保证同一个activity对应的holderFragment对象相同
            return holder;
        }
 public HolderFragment() {
        setRetainInstance(true);
    }
  @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

ViewModelStore.java执行ViewModel.onCleared()

 public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
  • mNotCommittedActivityHolders 是类型为 Map<Activity, HolderFragment> ,所以同一个宿主Activity对应的HolderFragment为同一个。同理,同一个宿主Fragment的对应的HolderFragment也为同一个。所以其ViewModelStore对象为同一个。
  • 如果HolderFragment不存在,则创建一个新的,将其保存到mNotCommittedActivityHolders
  • 调用Fragment#setRetaininstance(true)允许我们跳过销毁和重新创建的周期。指示系统保留当前的fragment实例。这样,实现HolderFragment中的ViewModelStore不会随着屏幕转换而发生变化。
  • HolderFragment 执行onDestroy时,调用ViewModelStore.clear(),完成ViewModel的onClear()的回调。
get(@NonNull Class modelClass)

ViewModelProvider初始化之后,调用get(@NonNull Class<T> modelClass)获取ViewModel实例

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

由上面两部分代码可知,get(@NonNull Class<T> modelClass)参数传入ViewModel.class后,以Class.getCanonicalName()为键,从ViewModelStore中取出相应ViewModel实例并返回。如果ViewModelStore中不存在,调用mFactory.create(modelClass)创建一个新的ViewModel对象,将其放到ViewModelStore的容器中。
调用mFactory.create(modelClass)会执行ViewModelProvider.AndroidViewModelFactory.create(@NonNull Class<T> modelClass),通过反射进行ViewModel实例化。如果目标ViewModel继承自AndroidViewModel,则调用AndroidViewModel的构造函数,如果是继承自ViewModel,则调用ViewModel 无参构造函数

 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }

至此ViewModel 实例化完成

END