介紹
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;
}
}
頁面如下圖
描述:LiveData持有String數據初始為hi,A和Bfragment分別從ViewModel中獲取LiveData并監聽其中數據,在Activity上有一按鈕,每次點擊更新String數據為其本身加上"~"。
行為:單機幾次按鈕,來回切換A和B按鈕,可以看到數據在Fragment間都是最新的(圖不貼,懶),翻轉屏幕,再次觀察,日志如下圖
從日志圖中可得到的信息如下:
- 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保存,再此需求時從從保存處恢復。
簡單原理圖
細節
當前位置
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() 找到,如下圖