美文网首页Android-Jetpack
数据共享与持久化——ViewModel 的使用与原理

数据共享与持久化——ViewModel 的使用与原理

作者: MxsQ | 来源:发表于2018-07-10 15:45 被阅读1809次

    介绍

    ViewModel属于ACC框架组件之一,用以解决数据持久与共享问题,此外,也将数据的相关行为从UI中分离出来。

    前言

    对于ViewModel的使用以及原理,可能需要对Lifecycle和LiveData有一些理解,不然可能会影响对某些内容的理解。以下为可参考资料。

    正文

    案例

    public class MyData extends LiveData<String> {
    
        private static final String TAG = "T-MyData"; 
    
        public  MyData(){
            setValue("hi");
            Log.d(TAG, "create new liveData ");
        }
    
        @Override
        protected void onActive() {
            super.onActive();
            Log.d(TAG, "onActive ");
        }
    
        @Override
        protected void onInactive() {
            super.onInactive();
            Log.d(TAG, "onInactive ");
        }
    
        public void changeValue(String value){
            setValue(value);
        }
    
    }
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "T-MainActivity";
    
        private TabLayout nav;
        private Fragment nowFragment;
    
        private Fragment[] fs = new Fragment[]{
                new AFragment(),
                new BFragment()};
    
        private MViewModel mViewModel;
        MyData data;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Log.d(TAG, "activity onCreate ");
    
            nav = findViewById(R.id.nav);
            nav.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
                @Override
                public void onTabSelected(TabLayout.Tab tab) {
                    if (tab.getText().equals("A")){
                        nowFragment = fs[0];
                    }else {
                        nowFragment = fs[1];
                    }
                    getSupportFragmentManager().beginTransaction().replace(R.id.container, nowFragment).commit();
                }
    
                @Override
                public void onTabUnselected(TabLayout.Tab tab) {
    
                }
    
                @Override
                public void onTabReselected(TabLayout.Tab tab) {
    
                }
            });
            nav.addTab(nav.newTab().setText("A"));
            nav.addTab(nav.newTab().setText("B"));
    
            mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
            findViewById(R.id.attack).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MyData data = mViewModel.getLiveData();
                    data.changeValue(data.getValue() + "~");
                }
            });
        }
    }
    
    public class AFragment extends Fragment {
    
        View mainView;
        private TextView text;
    
        private MViewModel mViewModel;
    
        public AFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            mainView = inflater.inflate(R.layout.fragment_a, container, false);
            text = mainView.findViewById(R.id.A_text);
    
            mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
            mViewModel.getLiveData().observe(this, new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
                    text.setText("A--" + s);
                }
            });
    
            return mainView;
        }
    
    }
    
    public class BFragment extends Fragment {
    
        private View mainView;
        private TextView text;
    
        private MViewModel mViewModel;
    
        public BFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            mainView = inflater.inflate(R.layout.fragment_b, container, false);
            text = mainView.findViewById(R.id.B_text);
    
            mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
            mViewModel.getLiveData().observe(this, new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
                    text.setText("B--" + s);
                }
            });
            return mainView;
        }
    }
    
    public class MViewModel extends AndroidViewModel {
    
        private MyData data;
    
        public MViewModel(Application application) {
            super(application);
            data = new MyData();
        }
    
        public MyData getLiveData(){
            return data;
        }
    }
    

    页面如下图


    ViewModel.jpg

    描述:LiveData持有String数据初始为hi,A和Bfragment分别从ViewModel中获取LiveData并监听其中数据,在Activity上有一按钮,每次点击更新String数据为其本身加上"~"。
    行为:单机几次按钮,来回切换A和B按钮,可以看到数据在Fragment间都是最新的(图不贴,懒),翻转屏幕,再次观察,日志如下图


    viewmodel日志.jpg

    从日志图中可得到的信息如下:

    • Activity与Fragment被重建
    • LiveData对Fragment的绑定关系被重建
    • ViewModel没有被重建,持有原来的数据对象
      从运行情况可以得知:
    • 数据保持共享

    ViewModel是如何做到数据持久化以及数据共享的?以下为讲解

    提醒

    由于版本问题,ViewModel对较低版本的SDK做了兼容,因此在实现原理上分有两种做法。在此提前点明情况,方便以下的叙述顺序流畅。

    原理一(此SKD为27)

    入口

    mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
    
        public static ViewModelProvider of(@NonNull FragmentActivity activity) {
            return of(activity, null);
        }
        public static ViewModelProvider of(@NonNull FragmentActivity activity,
                @Nullable Factory factory) {
            // 获取当前程序所依托的Application
            Application application = checkApplication(activity);
            if (factory == null) {
                // 获取AndroidViewModelFactory,单例
                factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
            }
            // ViewModelStores.of()返回了ViewModelStore
            return new ViewModelProvider(ViewModelStores.of(activity), factory);
        }
    

    这里只要注意,of()返回了ViewModelProvider,其持有ViewModelStore信息和AndroidViewModelFactory。 而ViewModelStore其实规划了自身将被如何存储

    当前位置
    ViewModelProviders.of()
    - ViewModelProvider()
    -- ViewModelStores.of()
    
        public static ViewModelStore of(@NonNull FragmentActivity activity) {
            // 当前SDK下,运行到这里就返回了,证据在下一张代码引用图
            if (activity instanceof ViewModelStoreOwner) {
                return ((ViewModelStoreOwner) activity).getViewModelStore();
            }
            return holderFragmentFor(activity).getViewModelStore();
        }
    
    public class FragmentActivity extends BaseFragmentActivityApi16 implements
            ViewModelStoreOwner,
    

    既然FragmentActivity实现了ViewModelStoreOwner,那么对应的获取方式如下

    当前位置
    FragmentActivity. 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.");
            }
            // 为空新建
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
            return mViewModelStore;
        }
    

    可见,FragmentActivity自身是持有ViewModelStore

    以上构造出了ViewModelProvider,紧接着去获取具体的ViewModel

    当前位置ViewModelProvider
        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) {
            // 从ViewModelStore中获取viewModel
            ViewModel viewModel = mViewModelStore.get(key);
            
            // 获取到,返回
            if (modelClass.isInstance(viewModel)) {
                return (T) viewModel;
            } else {
                if (viewModel != null) {
                }
            }
            // 创建viewModel,factory为AndroidViewModelFactory
            viewModel = mFactory.create(modelClass);
            // 将ViewModel与类名绑定,并保存
            mViewModelStore.put(key, viewModel);
            return (T) viewModel;
        }
    

    以上代码就获取到了具体的ViewModel,其中AndroidViewModelFactory.create()代码仅仅是通过反射创建了ViewModel实例并捕捉了异常。先做个小结:

    • ViewModelProvider持有ViewModelStore信息
    • VIewModelStore规划自身的提取方式并持有ViewModel信息
    • 通过类作为key获取具体的ViewModel实现共享

    我们知道,在屏幕旋转时,如果没有对Activity做相应的配置更变设置,Activity是会被重建的,而Activity被销毁时,相应持有的数据理应被释放。那ViewModel是如何逃过一劫的?

    思考

    在源码看到这的时候,实际上正面线索已无法跟踪,因为在庞大的Activity架构之中,很难快速地找到事件发源地。那现在,如何找到ViewModel持久的线索呢?在当前条件下,FragmentActivty是直接持有了ViewModelStore的信息,那么在配置更变需要重建时,必定要对ViewModelStore做一些处理,因此选择了跟踪FragmentActity.mViewModelStore。

    果然,定位到了以下位置

    FragmentActivity
    
        public final Object onRetainNonConfigurationInstance() {
           .......
            NonConfigurationInstances nci = new NonConfigurationInstances();
            nci.custom = custom;
            nci.viewModelStore = mViewModelStore;
            nci.fragments = fragments;
            return nci;
        }
    

    在配置更改需要重建页面的时候,系统会去保存现场以便恢复,在这个函数中,ViewModelStore被作为状态之一被保存在NonConfigurationInstances之中。依次类推,有保存就有取出,继续跟踪,定位到了以下位置

    FragmentActivity
    
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            .....
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
          .....
      }
    

    可见,在生命周期onCreate,备份的ViewModelStore被取出。因此,当重建后,再次通过ViewModelProvider.get()去获取ViewModel时候,会直接获取到此ViewModelStore并取出ViewModel,不会再通过AndroidVIewModelFactory重建ViewModel。

    这里小结一下:

    • 在配置更变需要重建页面时,ViewModelStore会在重建前交由NonConfigurationInstances保管,并在重建后取出恢复。

    以上,就是ViewModel在高SDK下的数据共享与持久化的原理。

    接下来,是适配低版本的。

    原理二 (实例SDK为25)

    之前说过,ViewModelStore规划了自身将被如何存储,而且差异也在于低版本的SDK的Activity的各父类并不是ViewModelStoreOwner,回看代码ViewModelStore.of()

        public static ViewModelStore of(@NonNull FragmentActivity activity) {
            if (activity instanceof ViewModelStoreOwner) {
                return ((ViewModelStoreOwner) activity).getViewModelStore();
            }
            // 当前SDK下,运行到此返回
            return holderFragmentFor(activity).getViewModelStore();
        }
    

    以上代码从HolderFragment取得ViewModelStore

    当前位置
    HolderFragment
    
        private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
    
        public static HolderFragment holderFragmentFor(FragmentActivity activity) {
            return sHolderFragmentManager.holderFragmentFor(activity);
        }
    
    当前位置
    HolderFragement.HolderFragmentManager
    
            HolderFragment holderFragmentFor(FragmentActivity activity) {
                FragmentManager fm = activity.getSupportFragmentManager();
                // 查找合适的HolderFragment
                HolderFragment holder = findHolderFragment(fm);
                // 查找到返回
                if (holder != null) {
                    return holder;
                }
                // 通过key(activity)取出HolderFragment
                holder = mNotCommittedActivityHolders.get(activity);
               // 取到返回
                if (holder != null) {
                    return holder;
                }
                
                if (!mActivityCallbacksIsAdded) {
                    mActivityCallbacksIsAdded = true;
                    activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
                }
                // 注入HolderFragment
                holder = createHolderFragment(fm);
                // 绑定HolderFragment和key
                mNotCommittedActivityHolders.put(activity, holder);
                return holder;
            }
    

    以上代码代码可知,HolderFragment静态对象HolderFragmentManager,持有HolderFragment对象与key的对应管理。这里因为HolderFragment被注入,需要看一下初始工作。

    当前位置HolderFragment
        private ViewModelStore mViewModelStore = new ViewModelStore();
    
        public HolderFragment() {
            setRetainInstance(true);
        }
    

    新的HolderFragment新建时自身持有了ViewMolderStore,之前通过ViewModelStores.of()获取的,就是这个ViewMolderStore。

    能区别出,在原理一种,具有生命周期的对象,本身会持有ViewModelStore,而在原理二中,会通过注入HolderFrament,去间接持有ViewModelStore。其他流程是一致的。

    现在,就还剩一个问题,简介持有的ViewModelStore,如何保持持久化?

    注意到,在初始化HolderFragment是,设置了mRetainInstance,如下

        /**
         * Control whether a fragment instance is retained across Activity
         * re-creation (such as from a configuration change).  This can only
         * be used with fragments not in the back stack.  If set, the fragment
         * lifecycle will be slightly different when an activity is recreated
         */
        public void setRetainInstance(boolean retain) {
            mRetainInstance = retain;
        }
    

    注释大意为:在Activity 销毁-重建时控制是否是有fragment实例。仅在fragment不在back stack时生效。当mRetainInstance设置为trues时,生命周期表现行为与重建时有轻微不同。

    简单来说,HolderFragment并没有被销毁,而当再次通过key去取出对应的HolderFragment时,就能取出。

    至于HolderFragment为什么没有被销毁,那就需要了解FragmentManager如何去管理Fragment了,这就扯远了。

    总结

    通过以上的梳理分析,算是讲明了ViewModel的数据如何共享以及持久化,一下为要点:

    • 通过ViewModelProvider持有ViewModelStore和Factory,并主要用来获取对应的ViewModelStore
    • ViewModelStore存储了key-value形势的类与ViewModel的对应,实现了数据的共享,Factory负责在需要时创建出ViewModel
    • ViewModelStore被Activity或Fragment持有, 或通过注入HolderFragment间接持有
    • Activity因配置原因销毁-重建时,ViewModelStore被NonConfigurationInstances保存或被HolderFragment保存,再此需求时从从保存处恢复。

    简单原理图


    ViewModel原理-2.png

    细节

    当前位置
    HolderFragment
    
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            sHolderFragmentManager.holderFragmentCreated(this);
        }
    
    
    当前位置
    HolderFragment.HolderFragmentManager
    
            void holderFragmentCreated(Fragment holderFragment) {
                // 获取作为依托的父fragment
                Fragment parentFragment = holderFragment.getParentFragment();
                if (parentFragment != null) {
                    // 释放父fragment
                    mNotCommittedFragmentHolders.remove(parentFragment);
                    parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                            mParentDestroyedCallback);
                } else {
                    //释放Activity
                    mNotCommittedActivityHolders.remove(holderFragment.getActivity());
                }
            }
    
    

    在Activity销毁-重建状态下,虽然ViewModelStore跟随HolderFragment被保存了,但是此时的与HolderFragment绑定的Activity或Fragment已不再是当时候的对象,因此,会存在内存泄漏问题。因此,在HolderFragment生命周期onCreate()里解决这一问题。

    注意到,HolderFragment与Activity或Fragment间的对应关系链已不存在,那么再去获取对应的HolderFragment是,会通过holderFragmentFor() ->findHolderFragment() 找到,如下图


    viewModel获取Holder.jpg

    相关文章

      网友评论

        本文标题:数据共享与持久化——ViewModel 的使用与原理

        本文链接:https://www.haomeiwen.com/subject/gecfpftx.html