在開始說ViewModel之前我們先來一些我們經(jīng)常要考慮問題:
1.Activity屏幕旋轉(zhuǎn)怎么處理保留和處理數(shù)據(jù)昆码?通過onSaveInstanceState鳍鸵?那如果數(shù)據(jù)比較大呢楞艾?
2.如果Acntivity/Fragment持有的后臺(tái)線程在未結(jié)束之前我們按了返回,任務(wù)結(jié)束后操作UI是不是各種NullPointerException?
3.如果我們正在執(zhí)行一個(gè)很耗時(shí)的任務(wù)历造,我們?cè)鯓幽茏龅皆谛D(zhuǎn)屏幕的時(shí)候不中斷這個(gè)任務(wù)而后能返回正解的結(jié)果呢直秆?
以上種種跟生命周期相關(guān)的問題都是比較常見又不得不面對(duì)的,那有沒有很優(yōu)雅的處理方式呢巧骚?
有赊颠,就是ViewModel
ViewModel官方介紹:
ViewModel類旨在以生命周期意識(shí)的方式存儲(chǔ)和管理與UI相關(guān)的數(shù)據(jù)。
ViewModel類允許數(shù)據(jù)在配置更改(例如屏幕旋轉(zhuǎn))后繼續(xù)存在劈彪。
我們先來看下應(yīng)用場(chǎng)景
我們?cè)谪Q屏狀態(tài)下啟動(dòng)了一個(gè)耗時(shí)任務(wù)竣蹦,然后翻轉(zhuǎn)手機(jī)到橫屏狀態(tài)
通過圖片我們看到在切換到橫屏?xí)r耗時(shí)任務(wù)并沒有中斷,在Activity重新創(chuàng)建后還能收到它返回的數(shù)據(jù)沧奴。我們來看下這過程Activity生命周期的變化:
通過圖片我們看到Activity確實(shí)已經(jīng)Destroy然后又重新Cread了痘括。然后如果我們直接按返回鍵退出這個(gè)Activity
你會(huì)看到Activity被Destroy的同時(shí),ViewModel也跟著Clear了滔吠。
說明我們的ViewModel不僅僅能保存數(shù)據(jù)纲菌,能還保留任務(wù),而且對(duì)生命周期還是可感知的疮绷。
那ViewModel是怎么做到這些的呢翰舌?我們先從簡(jiǎn)單的使用開始:
導(dǎo)入ViewModel:
//androidx的版本,包含ViewModel和LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
//非androidx版本
implementation "android.arch.lifecycle:extensions:1.1.1"
創(chuàng)建一個(gè)繼承自ViewModel的類:
public class MViewModel extends ViewModel
獲取一個(gè)ViewModel實(shí)例:
//在Activity中
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
//在Fragment中,傳this是獲取和此Fragment綁定的ViewModel,
//傳getActivity則獲取到的是跟activity中的是同一個(gè)ViewModel
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
mViewModel = ViewModelProviders.of(getActivity).get(MViewModel.class);
我們看下完整的代碼
自定義MViewModel(提前用到了LiveData可以先不用管冬骚,后面會(huì)講到)
public class MViewModel extends ViewModel {
MutableLiveData<String> mString;
MutableLiveData<String> msgString;
public MutableLiveData<String> getString(){
if(mString==null){
mString=new MutableLiveData<>();
}
return mString;
}
public MutableLiveData<String> getMsgString(){
if(msgString==null){
msgString=new MutableLiveData<>();
}
return msgString;
}
public void startTask(){
new Thread(){
@Override
public void run() {
//請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)椅贱、數(shù)據(jù)庫(kù)懂算、加載大圖等。
//如果在Activity轉(zhuǎn)屏的時(shí)候取消這些任務(wù)庇麦,那恢復(fù)的時(shí)候就要重新加載犯犁,勢(shì)必浪費(fèi)資源
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//此處用的是LiveData,如果我們不用LiveData可能是通過EventBus之類的把數(shù)據(jù)傳遞出去的
mString.postValue("我是來自3秒后的數(shù)據(jù)");
super.run();
}
}.start();
}
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
//做一些數(shù)據(jù)清理工作
Log.e("MViewModel","onCleared");
}
}
MainActivity中獲取了一個(gè)ViewModel
public class MainActivity extends AppCompatActivity {
private MViewModel mViewModel;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView=findViewById(R.id.desc);
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
//訂閱數(shù)據(jù)變化,可以暫時(shí)理解為EventBus的消息訂閱
mViewModel.getString().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Log.e("MainActivity", "耗時(shí)任務(wù)結(jié)束返回?cái)?shù)據(jù)");
mTextView.setText(s);
}
});
mViewModel.getMsgString().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Log.e("MainActivity", "Fragment1發(fā)過來的數(shù)據(jù)");
mTextView.setText(s);
}
});
getSupportFragmentManager().beginTransaction().replace(R.id.container, new TestFragment(),
TestFragment.class.getName()).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.container2, new TestFragment2(),
TestFragment.class.getName()).commit();
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("MainActivity", "開始耗時(shí)任務(wù)");
mViewModel.startTask();
}
});
}
}
為了說明ViewModel的作用域女器,我們?cè)俳ㄒ粋€(gè)TestFragment
public class TestFragment extends Fragment {
private static final String TAG = "TestFragment";
private View view;
private MViewModel mMViewModel;
private Button mBtn;
private TextView mDesc;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_test, container, false);
mBtn = view.findViewById(R.id.btn);
mDesc = view.findViewById(R.id.desc);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//這里傳入了getActivity(),返回的是跟Activity同一個(gè)ViewModel.
mMViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
mMViewModel.getString().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
mDesc.setText(s);
}
});
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMViewModel.getMsgString().setValue("Fragment1 發(fā)送的數(shù)據(jù)");
}
});
}
}
通過上面的代碼你會(huì)發(fā)現(xiàn)ViewModel使用起來非常的簡(jiǎn)單酸役,只需要繼承ViewModel即可,其余的生命周期之類的就完全不用我們考慮驾胆,它自己就能處理涣澡。除此之外同一Activity內(nèi)的Fragment還能不通過Activity直接進(jìn)行數(shù)據(jù)交互和共享,實(shí)現(xiàn)了真正意義上的解耦丧诺。
原理解析
1創(chuàng)建過程解析
1.1 ViewModelProviders.of()
ViewModelProviders 類提供了4個(gè)靜態(tài)工廠方法 of() 創(chuàng)建新的 ViewModelProvider 對(duì)象入桂。
public static ViewModelProvider of(@NonNull Fragment fragment) {
return of(fragment, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
//默認(rèn)的factory
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
這里主要是ViewModelProvider、Fragemtn/Activity下的ViewModelStrore驳阎、Factory(從上你可以看出默認(rèn)都是ViewModelProvider.AndroidViewModelFactory)
1.2 activity.getViewModelStore()和fragment.getViewModelStore()
//ViewModelStoreOwner
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
//activity實(shí)現(xiàn)ViewModelStoreOwner
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) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
//需要注意的是這里抗愁,優(yōu)先從NonConfigurationInstances中取出,這就是為什么Activity在轉(zhuǎn)屏重建之后還能拿到原來的ViewModel的原因呵晚,后面我們看是怎么存的
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
//fragment實(shí)現(xiàn)ViewModelStoreOwner
public ViewModelStore getViewModelStore() {
if (getContext() == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
1.3 ViewModelStrore
我們看到上面的方法返回的都是一個(gè)ViewModelStore 對(duì)象蜘腌,并且是Fragemnt/Activity下的一個(gè)成員變量。ViewModelStore 類中維護(hù)一個(gè) Map<String, ViewModel> 對(duì)象存儲(chǔ)已創(chuàng)建的 ViewModel 對(duì)象饵隙,說明一個(gè)Activity或者Fragment下可以保存多個(gè)不同的ViewModel撮珠,并提供 put() 和 get() 方法。
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);
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
1.4 ViewModelProvider.Factory
Factory 接口定義了一個(gè)創(chuàng)建 ViewModel 的接口 create()金矛,ViewModelProvider 在需要時(shí)調(diào)用該方法新建 ViewModel 對(duì)象芯急。
public interface Factory {
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
Android 已經(jīng)內(nèi)置了2個(gè) Factory 實(shí)現(xiàn)類,分別是:
- AndroidViewModelFactory 實(shí)現(xiàn)類驶俊,可以創(chuàng)建 ViewModel 和 AndroidViewModel 子類對(duì)象娶耍。
- NewInstanceFactory 類,只可以創(chuàng)建 ViewModel 子類對(duì)象饼酿。
它們的實(shí)現(xiàn)都是通過反射機(jī)制調(diào)用 ViewModel 子類的構(gòu)造方法創(chuàng)建對(duì)象榕酒。
public static class NewInstanceFactory implements Factory {
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
AndroidViewModelFactory 繼承 NewInstanceFactory 類,是個(gè)單例嗜湃,支持創(chuàng)建 AndroidViewModel 子類對(duì)象奈应。
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private static AndroidViewModelFactory sInstance;
public static AndroidViewModelFactory getInstance(Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
public AndroidViewModelFactory(Application application) {
mApplication = application;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
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);
}
}
1.5 ViewModelProvider
ViewModelProviders.of()返回的是一個(gè)ViewModelProvider對(duì)象,這個(gè)類下面就兩個(gè)字段
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
通過上面的解析我們知道Factory是創(chuàng)建ViewModel的工廠购披,ViewModelStore是用來存儲(chǔ)ViewModel的,所以我們來看下其中的get方法
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;
}
至此肩榕,一個(gè)ViewModel便通過ViewModelProviders.of(activity/fragment).get()創(chuàng)建完成刚陡。
2生命周期和保存活過程解析
從上圖可知ViewModel跟實(shí)例化時(shí)傳入的Activity/Fragment的生命周期是保持一致的惩妇,但是從文章一開始我們就說到在屏幕旋轉(zhuǎn)的時(shí)候Acticity已經(jīng)被Destroy掉的情況下ViewModel卻依然存活并正常執(zhí)行其內(nèi)部的任務(wù)。原因就在于Activity的Configuration Changes筐乳。
2.1 Configuration Change概述
Configuration 這個(gè)類描述了設(shè)備的所有配置信息歌殃,這些配置信息會(huì)影響到應(yīng)用程序檢索的資源。包括了用戶指定的選項(xiàng)(locale和scaling)也包括設(shè)備本身配置(例如input modes蝙云,screen size and screen orientation).可以在該類里查看所有影響Configuration Change 的屬性氓皱。
橫豎屏切換是我們最常見的影響配置變化的因素,還有很多其他影響配置的因素有語言的更改(例如中英文切換)勃刨、鍵盤的可用性(這個(gè)沒理解)等
常見的引發(fā)Configuration Change的屬性:
橫豎屏切換:android:configChanges="orientation"
鍵盤可用性:android:configChanges="keyboardHidden"
屏幕大小變化:android:configChanges="screenSize"
語言的更改:android:configChanges="locale"
在程序運(yùn)行時(shí)波材,如果發(fā)生Configuration Change會(huì)導(dǎo)致當(dāng)前的Activity被銷毀并重新創(chuàng)建 ,即先調(diào)用onDestroy緊接著調(diào)用onCreate()方法身隐。 重建的目的是為了讓應(yīng)用程序通過自動(dòng)加載可替代資源來適應(yīng)新的配置廷区。
2.2 ViewModelStore的存取
當(dāng)設(shè)備發(fā)生Configuration Change之后,Activity在Destroy之前會(huì)調(diào)用onRetainNonConfigurationInstance方法贾铝,F(xiàn)ragmentActivity#onRetainNonConfigurationInstance()源碼如下
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
//這地方是調(diào)用Fragment的處理方法隙轻,進(jìn)去之后也是類似的處理方式
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
//mViewModelStore將ViewModelStrore保存
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
我們可以看到此方法將ViewModelStore對(duì)象保存了下來,然后在onCreate的時(shí)候取回
protected void onCreate(Bundle savedInstanceState) {
//...
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
mViewModelStore = nc.viewModelStore;
}
//...
}
此外回到我們之前說的FragmentActivity#getViewModelStore你會(huì)發(fā)現(xiàn)也做了相應(yīng)的取出操作
public ViewModelStore getViewModelStore() {
//省略 ...
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
2.3 銷毀過程
Configuration Change的過程不銷毀垢揩,但是從文章開始我們發(fā)現(xiàn)當(dāng)我們按返回鍵主動(dòng)結(jié)束這個(gè)Activity的時(shí)候ViewModel也跟著執(zhí)行onClear()了玖绿。我們來看下系統(tǒng)是如何區(qū)分的:
FragmentActivity#onDestroy
protected void onDestroy() {
super.onDestroy();
if (mViewModelStore != null && !isChangingConfigurations()) {
mViewModelStore.clear();
}
mFragments.dispatchDestroy();
}
FragmentActivity#onDestroy
public void onDestroy() {
mCalled = true;
FragmentActivity activity = getActivity();
boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
if (mViewModelStore != null && !isChangingConfigurations) {
mViewModelStore.clear();
}
}
由此我們可以看出FragmentActivity跟Fragment的onDestroy方法中都對(duì)Configuration Change做了相應(yīng)的判斷。
最后的總結(jié)
- 通過 ViewModelProviders 創(chuàng)建 ViewModelProvider 對(duì)象叁巨,調(diào)用該對(duì)象的 get() 方法獲取 ViewModel 對(duì)象镰矿。 當(dāng) ViewModelStore 里不存在想要的對(duì)象,ViewModelProvider 會(huì)使用 Factory 新建一個(gè)對(duì)象并存放到 ViewModelStore 里俘种。
- 當(dāng)發(fā)生 發(fā)生 Configuration Changes 時(shí)秤标,F(xiàn)ragmentActivity 利用 getLastNonConfigurationInstance()、onRetainNonConfigurationInstance() 方法實(shí)現(xiàn) -ViewModelStore 的保留與恢復(fù)宙刘,進(jìn)而實(shí)現(xiàn) ViewModel 對(duì)象的辈越活。
- 當(dāng) FragmentActivity 和 Fragment 被銷毀時(shí)悬包,會(huì)根據(jù)是否發(fā)生 Configuration Changes 來決定是否銷毀 ViewModel衙猪。
- 最重要的一點(diǎn)是在發(fā)生 Configuration Changes時(shí),ViewModel的存活時(shí)間是會(huì)比Activity/Fragment的存活時(shí)間要長(zhǎng)布近,所以我們不能在ViewModel中持有Activity/Fragment引用垫释,否則會(huì)產(chǎn)生泄漏。所以我們不能通過接口的形式(MVP)將數(shù)據(jù)返回給Activity/Fragment使用撑瞧,為此官方給出的方案便是結(jié)合LiveData使用棵譬,我們將在下一篇文章中講到。
- 如果ViewModel中要使用到Application可以通過繼承AndroidViewModel预伺,也是通過ViewModelProviders.of(this).get(MViewModel.class)方式實(shí)例化
下一篇文章LiveData使用與解析