Android?ViewModel創(chuàng)建不受橫豎屏切換影響原理詳解
ViewModel的創(chuàng)建方式
在我們項(xiàng)目中, 引入了viewModel 做MVI 設(shè)計(jì)模式的組成部分,它是JetPack 組件庫(kù)中的重要成員。今天來(lái)了解下它。
// 在 Activity 中使用
class MainActivity : AppCompatActivity() {
// 使用 Activity 的作用域
private val viewModel : MainViewModel by viewModels()
}
// 在 Fragment 中使用
class MainFragment : Fragment() {
// 使用 Activity 的作用域,與 MainActivity 使用同一個(gè)對(duì)象
val activityViewModel : MainViewModel by activityViewModels()
// 使用 Fragment 的作用域
val viewModel : MainViewModel by viewModels()
}
// ViewModel 創(chuàng)建工廠
private final Factory mFactory;
// ViewModel 存儲(chǔ)容器
private final ViewModelStore mViewModelStore;
// 默認(rèn)使用 NewInstanceFactory 反射創(chuàng)建 ViewModel
public ViewModelProvider(ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), ... NewInstanceFactory.getInstance());
}
// 自定義 ViewModel 創(chuàng)建工廠
public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
this(owner.getViewModelStore(), factory);
}
// 記錄宿主的 ViewModelStore 和 ViewModel 工廠
public ViewModelProvider(ViewModelStore store, Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
@NonNull
@MainThread
public <T extends ViewModel> T get(Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
// 使用類名作為緩存的 KEY
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
// Fragment
@NonNull
@MainThread
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
// 1. 先從 ViewModelStore 中取緩存
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}
// 2. 使用 ViewModel 工廠創(chuàng)建實(shí)例
viewModel = mFactory.create(modelClass);
...
// 3. 存儲(chǔ)到 ViewModelStore
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
// 默認(rèn)的 ViewModel 工廠
public static class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@NonNull
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
// 反射創(chuàng)建 ViewModel 對(duì)象
return modelClass.newInstance();
}
}
可以看到:
ViewModel 實(shí)例的方法最終是通過(guò) ViewModelProvider 完成的。ViewModelProvider 可以理解為創(chuàng)建 ViewModel 的工具類,它需要 2 個(gè)參數(shù):
參數(shù) 1 ViewModelStoreOwner:
它對(duì)應(yīng)于 Activity / Fragment 等持有 ViewModel 的宿主,它們內(nèi)部通過(guò) ViewModelStore 維持一個(gè) ViewModel 的映射表,ViewModelStore 是實(shí)現(xiàn) ViewModel 作用域和數(shù)據(jù)恢復(fù)的關(guān)鍵;
參數(shù) 2 Factory:
它對(duì)應(yīng)于 ViewModel 的創(chuàng)建工廠,缺省時(shí)將使用默認(rèn)的 NewInstanceFactory 工廠來(lái)反射創(chuàng)建 ViewModel 實(shí)例。
創(chuàng)建 ViewModelProvider 工具類后,你將通過(guò) get() 方法來(lái)創(chuàng)建 ViewModel 的實(shí)例。get() 方法內(nèi)部首先會(huì)通過(guò) ViewModel 的全限定類名從映射表(ViewModelStore)中取緩存,未命中才會(huì)通過(guò) ViewModel 工廠創(chuàng)建實(shí)例再緩存到映射表中。 正因?yàn)橥粋€(gè) ViewModel 宿主使用的是同一個(gè) ViewModelStore 映射表,因此在同一個(gè)宿主上重復(fù)調(diào)用 ViewModelProvider.get() 返回同一個(gè) ViewModel 實(shí)例。
// ViewModel 本質(zhì)上就是一個(gè)映射表而已
public class ViewModelStore {
// <String - ViewModel> 哈希表
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());
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModel 宿主是 ViewModelStoreOwner 接口的實(shí)現(xiàn)類,例如 ComponentActivity:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner ... {
// ViewModel 的存儲(chǔ)容器
private ViewModelStore mViewModelStore;
// ViewModel 的創(chuàng)建工廠
private ViewModelProvider.Factory mDefaultFactory;
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
// 已簡(jiǎn)化過(guò)程
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
}
由此,ViewModel 的創(chuàng)建其實(shí)跟activity 是相關(guān)聯(lián)的。
ViewModel 為什么不受 Activity 橫豎屏生命周期的影響
1、在 Activity 走到 onDestroy 方法時(shí),做了判斷 isChangingConfigurations

可以看到,在 ComponentActivity 構(gòu)造方法中添加了生命周期的判斷,當(dāng) Activity onDestroy 時(shí),如果是發(fā)生了橫豎屏切換,就不會(huì)走 getViewModelStore().clear(),清理操作,保證了ViewModel 持有的數(shù)據(jù)還能存在。
2、在 Activity 獲取 getViewModelStore 時(shí),


通過(guò)getLastNonConfigurationInstance() 獲取 NonConfigurationInstances 實(shí)例,從而得到這個(gè)實(shí)例中的 viewModelStore

而且,Activity 生命周期的變化都會(huì)走這個(gè)方法,來(lái)保證viewModelStore 不為空。

3、onRetainNonConfigurationInstance 調(diào)用
在 Activity 屏幕旋轉(zhuǎn)時(shí),onRetainNonConfigurationInstance()在onStop和onDestroy之間調(diào)用
onRetainNonConfigurationInstance() 是個(gè)重要的方法

這保證了 在銷毀前,viewModelStore 實(shí)例被拿到并交給 NonConfigurationInstances 實(shí)例,將 viewModelStore 賦值給他

總結(jié)
1、介紹了ViewModel的創(chuàng)建
- ViewModel 由創(chuàng)建工廠 Factory mFactory 反射創(chuàng)建而來(lái),并被放到存儲(chǔ)容器 ViewModelStore 中
2、ViewModel生命周期和橫豎屏場(chǎng)景下不被銷毀的原因
在 Activity 走到 onDestroy 方法時(shí),做了判斷 isChangingConfigurations,如果發(fā)生橫豎屏,不會(huì)清理ViewModelStore,所以ViewModel還在
銷毀前 onRetainNonConfigurationInstance() 被觸發(fā),將原來(lái)的viewModelStore 賦值給 NonConfigurationInstances實(shí)例。
從Activity重建后 調(diào)用 getViewModelStore方法,內(nèi)部 通過(guò) getLastNonConfigurationInstance() 獲取 NonConfigurationInstances , NonConfigurationInstances 中保存的viewModelStore 實(shí)例被拿到,而viewModelStore 中保存著 ViewModel 。
以上就是Android ViewModel創(chuàng)建不受橫豎屏切換影響原理詳解的詳細(xì)內(nèi)容,更多關(guān)于Android ViewModel創(chuàng)建的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android實(shí)現(xiàn)驗(yàn)證碼按鈕
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)驗(yàn)證碼按鈕功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android編程實(shí)現(xiàn)簡(jiǎn)單的UDP Client實(shí)例
這篇文章主要介紹了Android編程實(shí)現(xiàn)簡(jiǎn)單的UDP Client,結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)UDP Clinet客戶端的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-04-04
android仿知乎ScrollView滾動(dòng)改變標(biāo)題欄透明度
這篇文章主要為大家詳細(xì)介紹了android仿知乎ScrollView滾動(dòng)改變標(biāo)題欄透明度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
Android之PreferenceActivity應(yīng)用詳解(2)
看到很多書中都沒(méi)有對(duì)PreferenceActivity做介紹,而我正好又在項(xiàng)目中用到,所以就把自己的使用的在這總結(jié)一下,也方便日后查找2012-11-11
Android操作系統(tǒng)的架構(gòu)設(shè)計(jì)分析
這篇文章主要介紹了Android操作系統(tǒng)的架構(gòu)設(shè)計(jì)分析,Android系統(tǒng)架構(gòu)分為L(zhǎng)inux內(nèi)核驅(qū)動(dòng)、C/C ++框架、Java框架、Java應(yīng)用程序,本文分別講解了它的作用,需要的朋友可以參考下2015-06-06
android通過(guò)led實(shí)現(xiàn)手電筒功能
這篇文章主要為大家詳細(xì)介紹了android通過(guò)led實(shí)現(xiàn)手電筒功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
Android實(shí)現(xiàn)橫向滑動(dòng)卡片效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)橫向滑動(dòng)卡片效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12

