Android開發(fā)Jetpack組件ViewModel使用講解
前言
學(xué)習(xí)ViewModel之前首先我們得簡單了解下MVP和MVVM,因為ViewModel是MVVM中的一個元素
MVP MVVM

在MVP中View想要調(diào)用Model數(shù)據(jù)層,需要經(jīng)過中間層Presenter, 這樣就實現(xiàn)了View和Model的解耦,這也是MVP和MVC的差別; 但是如果一個Activity中有太多交互,那么我們的View接口數(shù)量就會很龐大達到十幾個也不足為奇,并且在View層調(diào)用了Presenter之后,會反過來調(diào)用View層,這樣顯得繁瑣;而MVVM的出現(xiàn)就解決了這個問題
說到MVVM的話,我們放上Google的架構(gòu)圖

MVVM中的VM指的就是ViewModel; 從上圖為沒看到,因為ViewModel中持有了LiveData,而LiveData是一個可觀察的數(shù)據(jù)類型,在LiveData原理篇中,我們做了詳細的分析;在View層中,將被觀察的數(shù)據(jù)LiveData訂閱,并提供了一個觀察者Observer,當數(shù)據(jù)發(fā)生變化的時候,就會回調(diào)Observer中的onChanged()方法,從而更新UI, 這個過程是系統(tǒng)源碼幫我們處理的,所以就沒有上面Presenter中調(diào)用View的那一步了
ViewModel概述
應(yīng)用的某個 Activity 中可能包含用戶列表,因配置更改而重新創(chuàng)建 Activity 后,新 Activity 必須重新提取用戶列表; 對于簡單的數(shù)據(jù),Activity 可以使用onSaveInstanceState() 方法從 onCreate() 中的捆綁包恢復(fù)其數(shù)據(jù),但此方法僅適合可以序列化再反序列化的少量數(shù)據(jù),而不適合數(shù)量可能較大的數(shù)據(jù),如用戶列表或位圖,使用ViewModel可以解決這個問題
另外,界面控制器經(jīng)常需要進行異步調(diào)用,這些調(diào)用可能需要一些時間才能返回結(jié)果; 界面控制器需要管理這些調(diào)用,并確保系統(tǒng)在其銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄露;此項管理需要大量的維護工作,并且在因配置更改而重新創(chuàng)建對象的情況下,會造成資源的浪費,因為對象可能需要重新發(fā)出已經(jīng)發(fā)出過的調(diào)用,使用ViewModel可以解決這個問題
諸如 Activity 和 Fragment 之類的界面控制器主要用于顯示界面數(shù)據(jù)、對用戶操作做出響應(yīng)或處理操作系統(tǒng)通信(如權(quán)限請求); 如果要求界面控制器也負責(zé)從數(shù)據(jù)庫或網(wǎng)絡(luò)加載數(shù)據(jù),那么會使類越發(fā)膨脹。為界面控制器分配過多的責(zé)任可能會導(dǎo)致單個類嘗試自己處理應(yīng)用的所有工作,而不是將工作委托給其他類;以這種方式為界面控制器分配過多的責(zé)任也會大大增加測試的難度
ViewModel 類旨在以注重生命周期的方式存儲和管理界面相關(guān)的數(shù)據(jù)。ViewModel 類讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)存在
ViewModel使用
ViewModel的使用比較簡單,我們想要使用使用的話直接繼承ViewModel或者繼承AndroidViewModel即可; AndroidViewModel源碼如下,他們倆的區(qū)別,是AndroidViewModel中多了一個Application的成員變量以及以Application為參數(shù)的構(gòu)造方法,如果你需要Application的話,就直接繼承AndroidViewModel即可
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
這里以我寫的一個Demo為例,這個Demo可以在Github上找到,鏈接如下JetPack Demo,這個Demo用到了所有Jetpack的組件,是學(xué)習(xí)Jetpack的輔助資料,需要的小伙伴可以下載和star。我們自定義一個AppsViewModel如下:
class AppsViewModel(appsRepository: AppsRepository) : ViewModel() {
val apps: LiveData<List<AppEntity>> = appsRepository.loadApps()
}
因為我們這里傳入了一個參數(shù),所以需要定義一個Factory,代碼如下:
class AppsViewModelFactory(private val repository: AppsRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return AppsViewModel(repository) as T
}
}
接下來就是使用了:
class AppsListFragment : Fragment() {
private lateinit var viewModel: AppsViewModel
//-----1-----
private val viewModel: AppsViewModel by viewModels {
FactoryProvider.providerAppsFactory(requireContext())
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
//-----2-----
viewModel = ViewModelProviders.of(this,FactoryProvider.providerAppsFactory(requireContext()))
.get(AppsViewModel::class.java)
//-----3-----
viewModel.apps.observe(viewLifecycleOwner, Observer {
//Update UI
})
}
}我們先聲明了一個變量viewModel,我們可以通過注釋1處的 by viewModels提供一個自定義的Factory,但是需要添加一個依賴:implementation "androidx.fragment:fragment-ktx:1.2.2;或者采用注釋2的方式直接使用ViewModelProviders.of(fragment,factory).get(class)的形式獲取實例。 然后就直接使用了,在注釋3處使用viewmodel.apps.observe將其加入生命周期觀察中,如果數(shù)據(jù)發(fā)生變化就會調(diào)用Observer的回調(diào),從而更新UI
ViewModel源碼
上面我們采用ViewModelProviders.of(...).get(class)方法獲取ViewModel,我們就從這里開始源碼開始分析,我們先看下這個類的源碼:
@Deprecated
public class ViewModelProviders {
@Deprecated
public ViewModelProviders() {
}
@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
return new ViewModelProvider(fragment);
}
@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return new ViewModelProvider(activity);
}
@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
if (factory == null) {
factory = fragment.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
if (factory == null) {
factory = activity.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
@SuppressWarnings("WeakerAccess")
@Deprecated
public static class DefaultFactory extends ViewModelProvider.AndroidViewModelFactory {
@Deprecated
public DefaultFactory(@NonNull Application application) {
super(application);
}
}
}我們看到此類中提供了四個方法,其實著四個都以一樣,我們以第四個為例分析;第一個參數(shù)是Activity,第二個參數(shù)是一個Factory,默認情況下我們是不需要傳入Factory這個參數(shù)的。如果不傳入的話,我們跟進構(gòu)造方法就能看到默認是用NewInstanceFactory來作為Factory的,看到內(nèi)部的create方法通過反射生成了一個ViewModel,實例源碼如下:
public static class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
/**
* Retrieve a singleton instance of NewInstanceFactory.
*
* @return A valid {@link NewInstanceFactory}
*/
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
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);
}
}
}- 如果傳入Factory參數(shù)的話,就會用我們自定義的Factory作來生成ViewModel。
of方法調(diào)用結(jié)束返回的是ViewModelProvider, 然后調(diào)用的是get方法,我們看下這個類的部分源碼:
public class ViewModelProvider {
private static final String DEFAULT_KEY ="androidx.lifecycle.ViewModelProvider.DefaultKey";
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
//-----1-----
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
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)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
}上面截取的部分源碼其實是ViewModelProvider最重要的方法了,我們先看此類中有兩個重要的成員變量,其中一個是mFactory, 注釋1處看到如果我們沒有傳入factory的話,默認實現(xiàn)的是NewInstanceFactory, 印證了我們之前的說法。還有一個是ViewModelStore, 它是怎么來的呢?因為ComponentActivity中實現(xiàn)了接口ViewModelStoreOwner,在ViewModelProvider的構(gòu)造方法中調(diào)用owner.getViewModelStore(),這個owner就是ComponentActivity自身,然后獲取到了ViewModelStore這個變量,實際調(diào)用的源碼如下:
@Override
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.");
}
//-----1-----
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;
}在注釋1處,判斷如果mViewModelStore == null的話,就會調(diào)取getLastNonConfigurationInstance嘗試獲取,如果獲取到了就將獲取到的賦值給mViewModelStore返回。這里就涉及到一個重要的知識點了,為什么說ViewModel在橫豎屏切換的時候能夠持久的保存數(shù)據(jù),不需要像之前一樣調(diào)用onSaveInstanceState? 因為在Activity被銷毀的時候,還會調(diào)用另外一個方法onRetainNonConfigurationInstance, 我們看它在ComponentActivity中的源碼實現(xiàn):
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
//----1-----
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}我們看到在注釋1的地方將我們之前存在的viewModelStore存儲到NonConfigurationInstances中了,然后在調(diào)用getViewModelStore的時候調(diào)用getLastNonConfigurationInstance這樣就保證了Activity銷毀之前和之后的viewModelStore是同一個,那它里面存儲的ViewModel值也就是同樣的了。所以ViewModel的生命周期可以用下圖來概括:

接下來我們分析get方法:
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey"
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);
}首先獲取類的全稱的字符串名字,和DEFAULT_KEY拼湊成一個Key,然后調(diào)用get的重載方法如下:
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
//-----1-----
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
//-----2-----
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}注釋1處判斷我們的modelClass是不是屬于ViewModel類型的,并且判斷mFactory的類型是否屬于OnRequeryFactory類型,如果是的話,就返回值; 在注釋2處使用Factory通過反射創(chuàng)建一個viewModel, 然后將其存入mViewModelStore中。我們看下ViewModelStore的源碼:
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);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}ViewModelStore的源碼很簡單,內(nèi)部持有了一個HashMap對象,用來存放ViewModel。ViewModel的創(chuàng)建到此就結(jié)束了。然后就是使用的問題, 使用如下
override fun onActivityCreated(savedInstanceState: Bundle?) {
viewModel = ViewModelProviders.of(this,FactoryProvider.providerAppsFactory(requireContext()))
.get(AppsViewModel::class.java)
viewModel.apps.observe(viewLifecycleOwner, Observer {
//Update UI
})
}ViewModel的使用涉及到LiveData和Lifecycle部分,這里就不再多說了
到此這篇關(guān)于Android開發(fā)Jetpack組件ViewModel使用講解的文章就介紹到這了,更多相關(guān)Android Jetpack組件ViewModel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android EditText默認不彈出輸入法的實現(xiàn)方法
下面小編就為大家分享一篇Android EditText默認不彈出輸入法的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Flutter實現(xiàn)PopupMenu彈出式菜單按鈕詳解
這篇文章主要介紹了Flutter實現(xiàn)PopupMenu彈出式菜單按鈕,PopupMenuButton是一個用于創(chuàng)建彈出菜單的小部件,當用戶點擊觸發(fā)按鈕時,PopupMenuButton會在屏幕上方或下方彈出一個菜單,感興趣想要詳細了解可以參考下文2023-05-05
解決EditText編輯時hint 在6.0 手機上顯示不出來的問題
下面小編就為大家?guī)硪黄鉀QEditText編輯時hint 在6.0 手機上顯示不出來的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05

