前言
在Android
開發(fā)過程中舞丛,為了避免Activity
過于臃腫,我們采用了LifeCycle
來解耦生命周期相關(guān)的業(yè)務(wù)邏輯果漾。但是在開發(fā)中我們?nèi)匀恢铝τ诮鉀Q另一個問題球切,那就是數(shù)據(jù)和ui分離
。今天就來講講用來解決Activity
臃腫及其他相關(guān)問題的ViewModel
的使用
今天涉及知識由有:
- 為什么要用
ViewModel
-
ViewModel
初始化方式 - 封裝
BaseViewModel
的使用 -
ViewModel
使用注意事項 -
BaseViewModle
源碼
一. 為什么要用 ViewModel
這里主要講講使用ViewModel
的好處:
- 達(dá)到數(shù)據(jù)绒障,圖形分離(解耦)
-
ViewModel
實現(xiàn)數(shù)據(jù)持有不丟失 - 基于第二點吨凑,還能實現(xiàn)
Activity
與Fragment
間數(shù)據(jù)共享
二. ViewModel 初始化方式
網(wǎng)上多出現(xiàn)ViewModel
的使用需要引入jar
,但我在實際使用過程中,發(fā)現(xiàn)不需要添加額外依賴户辱,顯示ViewModel
是在androidx.lifecycle
包下鸵钝。
以ViewModel
在MainActivity
中使用為例,首先我們要建一個MainViewModel
繼承自ViewModel
,然后在MainActivity
中實例化它如下:
mainViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)
這里采用的是工廠模式初始化庐镐。this
是當(dāng)前Activity
實例恩商,從初始化方法的源碼中可以看出
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
參數(shù)應(yīng)該為ViewModelStoreOwner owner
,而追蹤MainActivity
源碼可發(fā)現(xiàn):
MainActivity -> AppCompatActivity -> FragmentActivity -> ComponentActivity
然后看ComponentActivity
:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller {
可以發(fā)現(xiàn)ComponentActivity
是ViewModelStoreOwner
實現(xiàn)類,即MainActivity
是ViewModelStoreOwner
實例必逆,所以初始化時可以用this
代替ViewModelStoreOwner owner
參數(shù)怠堪。
當(dāng)然,我們還可以用以下方式在MainActivity
中初始化ViewModel
對象:
mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)
當(dāng)然名眉,我們還可以通過自定義工廠模式來實現(xiàn)ViewModel
的初始化研叫,這個接下來講。
三. 封裝 BaseViewModel 的使用
既然有以上兩種初始化ViewModel
的方式璧针,我們?yōu)樯哆€要自定義工廠模式來實現(xiàn)ViewModel
的初始化呢嚷炉?原因是我們在開發(fā)的時候不免會涉及到ViewModel
傳值的問題,很顯然探橱,以上兩種方式均不能在初始化時將MainActivity
的數(shù)據(jù)傳輸?shù)?code>ViewModel中去申屹。于是我封裝了一個BaseViewModel
用以實現(xiàn)MainActivity
向ViewModel
傳值的目的。下面以簡單例子來講解ViewModel
的好處隧膏。先繼承BaseViewModel
實現(xiàn)MainViewModel
用以緩存界面MainActivity
中數(shù)據(jù):
class MainViewModel : BaseViewModel {
constructor()
constructor(savedInstanceState: Bundle?, any:Any?):super(savedInstanceState,any){
}
var mNumber:Int=5
}
由于我對BaseViewModel
做了處理哗讥,所以我們在繼承BaseViewModel
實現(xiàn)MainViewModel
時,必須寫一階構(gòu)造函數(shù)constructor()
胞枕,否則會在使用過程中報錯杆煞。
接著看MainActivity
代碼:
@RequiresApi(Build.VERSION_CODES.N)
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var mBingding: ActivityMainBinding
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBingding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBingding.root)
initData(savedInstanceState)
setListener()
}
private var initData:(Bundle?)->Unit = {savedInstanceState->
mBingding.tvName.text = "我是誰"
//初始化mainViewModel
mainViewModel=BaseViewModel.getViewModel(this,object: BaseViewModel.BaseViewModelFactory(savedInstanceState,"小學(xué)") {
override fun getChildViewModel(savedInstanceState: Bundle?, any: Any?): BaseViewModel {
return MainViewModel(savedInstanceState,any)
}
},MainViewModel::class.java) as MainViewModel
mBingding.tvName.text = mainViewModel.mNumber.toString()
}
private var setListener = {
mBingding.mBtnTest.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.mBtnTest -> {
LogUtil.i("====我點擊了=====")
plusNumber()
}
else -> {
}
}
}
private var plusNumber = {
mainViewModel.mNumber=mainViewModel.mNumber+1
mBingding.tvName.text=mainViewModel.mNumber.toString()
}
override fun onDestroy() {
super.onDestroy()
}
}
運行效果圖如下:
這里我們可以發(fā)現(xiàn),通過
ViewModel
來保存數(shù)據(jù)以后,我們在屏幕旋轉(zhuǎn)時决乎,之前的數(shù)據(jù)得以保留队询,而不是回到初始值。這就是ViewModel
數(shù)據(jù)持有的獨特魅力构诚。
四. ViewModel 使用注意事項
4.1 ViewModel 生命周期
ViewModel
生命周期在網(wǎng)上有一幅圖
即
Activity
在系統(tǒng)配置變化導(dǎo)致的重建不會銷毀ViewModel
蚌斩,ViewModel
對象會保留并關(guān)聯(lián)到新的Activity,只有在Activity
正常銷毀(系統(tǒng)不會重建Activity
)時,ViewModel
才會被銷毀。通過代碼:
mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)
追蹤源碼:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
繼續(xù)看this
方法:
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
可發(fā)現(xiàn)mViewModelStore
被賦值范嘱,然后接著看.get(MainViewModel::class.java)
這部分:
@NonNull
@MainThread
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);
}
接著追get(DEFAULT_KEY + ":" + canonicalName, modelClass)
:
@NonNull
@MainThread
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;
}
可以看到mViewModelStore
中若有viewModel
則直接取送膳,沒有的話,則由mFactory
創(chuàng)建丑蛤。mFactory
創(chuàng)建方法如下:
@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);
}
}
至此叠聋,我們可以發(fā)現(xiàn),一個Activity
中只有一個ViewModelStore
受裹,ViewModelStore
可存儲多個ViewModel
對象碌补,ViewModel
初始化流程是如果ViewModelStore
無ViewModel
則new
一個ViewModel
并放到ViewModelStore
中存儲,如果ViewModelStore
有則直接取出來用名斟。
4.2 ViewModel 不能持有的對象
鑒于ViewModel
生命周期跨越的長度比較大(包含一個Activity
的整個生命周期),我們可以利用ViewModel
來做Activity
與Fragment
間的傳值處理魄眉。但是也由于生命周期過長的問題砰盐,我們不能在ViewModel
中出現(xiàn)以下兩種對象:
-
ui
控件實例 -
Activity/Fragment
實例
若ViewModel
中出現(xiàn)UI
控件,會導(dǎo)致程序崩潰坑律,出現(xiàn)Activity/Fragment
實例岩梳,輕則內(nèi)存泄漏,重則崩潰
4.3 ViewModel 中使用 Application
ViewModel
中不能出現(xiàn)Activity/Fragment
實例晃择,那我們在ViewModel
中要使用Context
對象怎么辦冀值?這時我們不能用Activity/Fragment
的context
,而要使用Application
的context
,我們可以讓自己的ViewModel
繼承AndroidViewModel
類,類似下面這樣:
class NeViewModel(application: Application) : AndroidViewModel(application) {
}
由于AndroidViewModel
是ViewModel
的一個子類宫屠,所以也可以用
neViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(NeViewModel::class.java)
和
neViewModel= ViewModelProvider(this).get(NeViewModel::class.java)
的方式初始化列疗。
4.4 ViewModel 與 savedInstanceState
我們知道savedInstanceState
能在activity
異常時提供數(shù)據(jù)的瞬時保存與恢復(fù),現(xiàn)在ViewModel
也能達(dá)到數(shù)據(jù)瞬時存儲和恢復(fù)的目的浪蹂,那是不是意味著我們有了ViewModel
后就不需要savedInstanceState
了呢抵栈?
答案是否定的。為了程序的健壯性坤次,我們得同時使用savedInstanceState
和ViewModel
古劲。ViewModel
存儲數(shù)據(jù)較多,數(shù)據(jù)持有環(huán)境一般為Activity
配置發(fā)生變化導(dǎo)致的重啟缰猴,如果Activity
內(nèi)存溢出導(dǎo)致崩潰重啟产艾,ViewModel
數(shù)據(jù)也會丟失,這時就需要savedInstanceState
協(xié)助。但是savedInstanceState
是一個Bundle
闷堡,只能存儲少數(shù)數(shù)據(jù)隘膘。所以需要ViewModel
和savedInstanceState
結(jié)合使用。
五. BaseViewModle 源碼
下面給出BaseViewModle
源碼: