Android-Jetpack

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

2018-07-10  本文已影响1809人  MxsQ

介绍

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

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

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实例并捕捉了异常。先做个小结:

我们知道,在屏幕旋转时,如果没有对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。

这里小结一下:

以上,就是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的数据如何共享以及持久化,一下为要点:

简单原理图


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
上一篇下一篇

猜你喜欢

热点阅读