ViewModel
不接受任何形式的轉(zhuǎn)發(fā),如有需要請聯(lián)系作者,謝謝理解!
Android Jetpack 是一系列助力您更容易打造優(yōu)秀 Android 應(yīng)用的工具和組件。這些組件能幫助您遵循最佳實踐啡浊、免除編寫繁復(fù)的樣板代碼并簡化復(fù)雜任務(wù)觅够,從而使您可以專注于最核心的代碼邏輯。 Jetpack 中的架構(gòu)指南由 Android 開發(fā)中四個關(guān)鍵領(lǐng)域中的一系列代碼庫和工具提供支持虫啥。它們分別是基礎(chǔ)蔚约、架構(gòu)、行為和 UI涂籽。每個 Jetpack 組件均可單獨采用苹祟,但它們依然可以流暢地協(xié)作。其中的 Lifecycle 庫,可以有效避免內(nèi)存泄漏和解決常見的 Android 生命周期難題,今天將介紹 Lifecycle 庫中的 ViewModel 類 评雌。
介紹
簡單來說,ViewModel 是用來保存應(yīng)用 UI 數(shù)據(jù)的類,而且它會在配置變更后繼續(xù)存在树枫。它可以脫離 View 單純做 Junit 的測試,更方便大家做單元測試景东。ViewModel 有這樣的特性砂轻,但是它在實際項目能夠有何表現(xiàn),還需要看待各位是如何使用的斤吐。一般會和 Livedata搔涝、DataBinding 等其他組件進行組合使用,下面會介紹具體可以解決什么問題和措,和一般項目中會建議如何使用庄呈。在介紹的過程中,還會穿插一些架構(gòu)的思想以及如何看源碼分析源碼的思路派阱。
解決了什么問題
手機屏幕旋轉(zhuǎn)是配置變更的一種诬留,當(dāng)旋轉(zhuǎn)屏幕時, Activity 會被重新創(chuàng)建。如果數(shù)據(jù)沒有被正確的保存和恢復(fù)文兑,就有可能丟失盒刚。從而導(dǎo)致莫名其妙的 UI 錯誤,甚至應(yīng)用崩潰绿贞。Demo 請點擊這里
相反的因块, ViewModel 會在配置更改后繼續(xù)存在,所以樟蠕,如果將應(yīng)用所有的 UI 數(shù)據(jù)保存在 ViewModel 中贮聂,而不是 Activity 中,這樣就可以保證數(shù)據(jù)不會受到配置變更的影響了寨辩。
新手常見犯的錯誤是將很多業(yè)務(wù)變量,邏輯和數(shù)據(jù)都擺在 Activity 或 Fragment 中歼冰,這樣的代碼比較混亂和難以維護靡狞,這種開發(fā)模式違犯了單一責(zé)任的原則。而 ViewModel 可以有效地劃分責(zé)任隔嫡,它可以用來保存 Activity 的所有 UI 數(shù)據(jù),然后 Activity 僅負責(zé)了解如何在屏幕上顯示數(shù)據(jù)和接收用戶互動甸怕,但是 Activity 不會處理這些互動所帶來的業(yè)務(wù)邏輯(如:點擊獲取短信驗證碼按鈕后的網(wǎng)絡(luò)請求)。但不要將太多的邏輯處理腮恩,數(shù)據(jù)獲取放到 ViewModel 類里面處理梢杭,它僅僅作為 UI 數(shù)據(jù)的保存!
簡單使用
上面提到秸滴,不要將太多的邏輯處理武契、數(shù)據(jù)存儲獲取放到 ViewModel 類里面處理,它僅僅作為 UI 數(shù)據(jù)的保存荡含!數(shù)據(jù)存儲獲取可以創(chuàng)建 Repository 類咒唆, UI 數(shù)據(jù)處理可以使用 Presenter 類。Demo 請點擊這里
ViewModel 的使用方式如下:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
//繼承系統(tǒng) ViewModel 類
class UserProfileViewModel:ViewModel(){
//將跟 UI 界面有關(guān)的數(shù)據(jù)放至 UserProfileViewModel 中
val user = User(name="",company="")
}
//在 Activity 中實例化 UserProfileViewModel 變量
override fun onCreate(saveInstanceState:Bundle?){
//Setup Activity
val userViewModel = ViewModelProviders.of(this).get(UserProfileViewModel::class.java)
userViewModel.user.name = "Edgar Ng"
}
實際應(yīng)用
ViewModel 一般會搭載其他組件(如:LiveData释液、DataBinding 等)一同使用全释,本系列文章會專門出一篇文章就 ViewModel 及其他組件搭配使用進行說明講解,請點擊此處误债。
本章也會通過購買VIP會員這個業(yè)務(wù)功能點來簡述實際項目中會如何使用 ViewModel 浸船。具體代碼鏈接請點擊這里
該 Demo 總得來說分為三層:展示層(Presentation Layer)、域?qū)樱―omain Layer)寝蹈、數(shù)據(jù)層(Data Layer)李命,如下圖所示。
- 展示層:View躺盛,ViewModel
- 域?qū)樱簎se cases
- 數(shù)據(jù)層:repositories
展示層無法直接與數(shù)據(jù)層溝通项戴,ViewModel 只可以通過一個或者多個 UseCase 去獲取 repository,這種分層的做法有助于降低耦合度和提高可測試性槽惫,同時方便管理周叮,讓所有的 UseCase 都在耗時線程中執(zhí)行辩撑,保證在 UI 線程中沒有耗時數(shù)據(jù)的入口。
展示層具體使用以下技術(shù):Views + ViewModels + LiveData
ViewModels 從 LiveData 那里獲取數(shù)據(jù)提供給 Views仿耽,通過 Data Binding 真正的 UI 控件就可以自動完成數(shù)據(jù)顯示合冀,可以從 Activity 和 Fragment 那些繁雜而單調(diào)的樣式代碼中解放出來。
我們以購買會員為例项贺,說明如何使用 ViewModel君躺、UseCase、Repository开缎。首先涉及到 BuyVIPFragment.kt
,VIPViewModel.kt
,VIPViewModelFactory.kt
,VIPRepository.kt
等類棕叫。
//購買 VIP Fragment 類
class BuyVIPFragment : Fragment() {
private lateinit var vipViewModel: VIPViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activity?.let { activity ->
//通過 ViewModelProviders.of() 獲得 VIPViewModel
//因為使用的是 ViewModelProviders.of(activity) ,所以獲取到的是上一個 Fragment 共有的 ViewModel,從而到達兩個 Fragment 的數(shù)據(jù)可以進行同步更新
vipViewModel =
ViewModelProviders.of(activity, VIPViewModelFactory(activity.application!!))
.get(VIPViewModel::class.java)
//liveData 的監(jiān)聽回調(diào)方法(LiveData 用法不在這里詳細講解)
vipViewModel.vipAction.observe(this, Observer<VIPDto> {
btnBuyVIP.visibility = View.VISIBLE
txtUserName.text = it.userName
txtDeadlineDate.text = SimpleDateFormat("yyyy-MM-dd").format(it.deadlineDate)
})
}
//觸發(fā)購買 VIP 方法
btnBuyVIP.setOnClickListener {
btnBuyVIP.visibility = View.GONE
vipViewModel.buyVIP()
}
}
}
//viewmodel 工廠生成類
class VIPViewModelFactory(val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
//新建 VIPViewModel奕删,并觸發(fā)獲取數(shù)據(jù)方法 getData()
//網(wǎng)上有些例子是不使用 Factory俺泣,在獲取 VIPViewModel 處,再調(diào)用初始化方法完残。這樣也可以實現(xiàn)伏钠,不過如果是多個 Fragment 共用 ViewModel 就有可能會有數(shù)據(jù)重復(fù)加載的問題
return VIPViewModel(application).also { it.getData() } as T
}
}
//viewmodel 類
class VIPViewModel(application: Application) : AndroidViewModel(application) {
private val _VIPAction = MediatorLiveData<VIPDto>()
//對外 LiveData 類,ViewModel 與 LiveData 結(jié)合使用谨设,方便 UI 控制器進行數(shù)據(jù)監(jiān)聽及更新熟掂,數(shù)據(jù)有更改時會通知觀察者
val vipAction: LiveData<VIPDto>
get() = _VIPAction
//獲取數(shù)據(jù)
fun getData() {
//使用 UseCase 獲取數(shù)據(jù),一般 UseCase 的生命周期與 ViewModel 一樣長扎拣,所以他們之間也可以不使用 LiveData 赴肚, 不過這里也使用了 LiveData 進行轉(zhuǎn)化了
val useCase = GetVIPUseCase(SharePreferencesHelper(getApplication(), "VIP"))
_VIPAction.addSource(useCase.observe()) {
//一般會這里處理一些數(shù)據(jù)成功失敗狀態(tài)
if (it.isSuccess) {
_VIPAction.value = it.getOrThrow()
}
}
useCase.execute(Unit)
}
//購買 VIP
fun buyVIP() {
val useCase = BuyVIPUseCase(SharePreferencesHelper(getApplication(), "VIP"))
_VIPAction.addSource(useCase.observe()) {
if (it.isSuccess) {
_VIPAction.value = it.getOrThrow()
}
}
_VIPAction.value?.let { useCase.execute(it) }
}
}
//獲得 VIP 用戶數(shù)據(jù)
class GetVIPUseCase(private val sharePreferencesHelper: SharePreferencesHelper) : MediatorUseCase<Unit, VIPDto>() {
override fun execute(parameters: Unit) {
//從 Repository 獲取數(shù)據(jù)
val vipRepositoryData = VIPRepository(sharePreferencesHelper).getVIP()
//一般還會有成功、失敗兩種狀態(tài)
val switchMapFunction = Function<VIPDto, LiveData<Result<VIPDto>>> {
MediatorLiveData<Result<VIPDto>>().apply { value = Result.success(it) }
}
//敲黑板E羟铩尊蚁!這是重點也是難點!侣夷!
//1.為什么 UseCase 從 Repository 處獲取數(shù)據(jù)要使用 LiveData横朋?
//因為生命周期的原因, UseCase 的生命周期一般與 ViewModel 一樣長百拓,
//ViewModel 的生命周期一般會伴隨 UI 控制器的銷毀而銷毀琴锭。
//有時候獲取數(shù)據(jù)時間會很漫長,漫長到 UI 控制器銷毀了之后才返回來衙传。
//這時候就有可能會導(dǎo)致內(nèi)存泄漏或者回調(diào)的時候會出現(xiàn) View/ViewModel 不存在的情況决帖。
//2.在 UseCase 中如何使用 LiveData?
//通過使用 Transformations.switchMap() 的目的是可以將上一層( UI 控制器對ViewModel 的 LiveData 觀察狀態(tài)傳遞給 UseCase 蓖捶。
//因為 LiveData.observe() 方法需要 LifecycleOwner,
//但是 UseCase 沒有 LifecycleOwner地回,
//所以通過這種辦法可以進行轉(zhuǎn)化傳遞
result.addSource(
Transformations.switchMap(
vipRepositoryData,
switchMapFunction
) as MediatorLiveData<Result<VIPDto>>
) {
result.value = it
}
}
}
//常見的 Repository 類
class VIPRepository(private val sharePreferencesHelper: SharePreferencesHelper) {
fun buyVIP(user: VIPDto): LiveData<VIPDto> {
val result = MutableLiveData<VIPDto>()
//異步賦值的
GlobalScope.launch(Dispatchers.Main) {
async(Dispatchers.IO) {
delay(1000)
val calendar = Calendar.getInstance()
calendar.time = user.deadlineDate
calendar.add(Calendar.MONTH, 1)
val deadLineDate = calendar.time.toString()
val userName = user.userName
val contentValue = SharePreferencesHelper.ContentValue("deadLineDate", deadLineDate)
val contentValue2 = SharePreferencesHelper.ContentValue("userName", userName)
sharePreferencesHelper.putValues(contentValue, contentValue2)
async(Dispatchers.Main) {
result.value = VIPDto(userName, Date(deadLineDate))
}
}.await()
}
return result
}
fun getVIP(): LiveData<VIPDto> {
val result = MutableLiveData<VIPDto>()
//異步賦值的
GlobalScope.launch(Dispatchers.Main) {
async(Dispatchers.IO) {
delay(1500)
val deadLineDate = sharePreferencesHelper.getString("deadLineDate") ?: Date().toString()
val userName = sharePreferencesHelper.getString("userName") ?: "EdgarNg"
async(Dispatchers.Main) {
result.value = VIPDto(userName, Date(deadLineDate))
}
}.await()
}
return result
}
}
通過查看代碼可以知道,最后的狀態(tài)是下圖這樣的:
這里留個小功課,當(dāng) APP 在購買 VIP 頁面刻像,翻轉(zhuǎn)屏幕后畅买,APP 會”回到“上一個頁面,請回答為什么细睡,并提出解決辦法谷羞。
ViewModel 的 Unit test (TODO)
內(nèi)部實現(xiàn)
偏向源碼以及設(shè)計思想分析,不感興趣的同學(xué)可以跳過這章溜徙,謝謝湃缎!
不過真得很精彩的窩,真得不看看嗎蠢壹?
源碼來源編文時最新 lifecycle-viewmodel-2.1.0-alpha02-sources.jar嗓违,截圖已經(jīng)附上行號,方便比對
為了更好地詳細了解 ViewModel知残,我們可以帶著幾個問題去學(xué)習(xí):
- ViewModel 是怎么生成的?
- ViewModel 為什么可以達到這么神奇的效果靠瞎,Activity/Fragment 都重新生成居然還可以活下來,它是怎么做到的?
1. 常見的 ViewModel 實現(xiàn)方式:
val ViewModel1 = ViewModelProviders.of(activity,factory).get(ViewModel::class.java)
val ViewModel2 = ViewModelProviders.of(fragment,factory).get(ViewModel::class.java)
ViewModel 主要實現(xiàn)機制采用了工廠模式求妹,傳入 Factory 去實例化指定的ViewModel類,如果不傳 Factory佳窑,則使用默認的制恍,如下圖。
具體的調(diào)用流程如下神凑,由此可以看出ViewModel的生成其實還是由我們傳入的 factory.create()
方法來生成
Ps:請注意 ViewModelProvider 和 ViewModelProviders净神,是兩個獨立的類。具體可以從下列截圖看出溉委,ViewModelProviders 是 LifeCycle 庫的擴展類鹃唯,高內(nèi)聚低耦合,核心的東西放在一個類瓣喊,有可能變化的擴展的東西放在另外一個地方進行擴展坡慌,這也可以啟發(fā)到我們新手在設(shè)計框架時要考慮到擴展性,尤其是做 SDK 或者底層架構(gòu)的同學(xué)可以參考學(xué)習(xí)這一塊的設(shè)計方式藻三。
2.為什么 ViewModel 的生命周期這么長(比一般的 UI 數(shù)據(jù)長)
首先我們重溫一下 ViewModel 的生命周期(如上圖)洪橘,ViewModel 的生命周期不會因為配置變更(Activity 的多次調(diào)用onCreate()
方法)會終止,反而會和 Activity 的 onDestroy()
方法一起結(jié)束棵帽。為什么可以這樣子熄求?!請接著往下看逗概。
要想知道 ViewModel 的生命周期弟晚,思路上當(dāng)然是從 ViewModel 那里獲得答案(其實從 UI 控制器那里獲取更快,不過這算作弊)。根據(jù) ViewModel 生成的流程圖可以知道卿城,是調(diào)用了 ViewModelProvider.get(@NonNull String key, @NonNull Class<T> modelClass)
返回生成 ViewModel 的枚钓,查看源碼 get 方法(上圖)可以知道,一共有兩處地方生成 ViewModel 的藻雪,ViewModel viewModel = mViewModelStore.get(key);
和 viewModel = mFactory.create(key, modelClass);
秘噪,但是生成完了之后都通過 mViewModelStore.put(key, viewModel);
,通過方法名稱應(yīng)該就是一個緩存器勉耀,查看 mViewModelStore.put(key, viewModel)
的源碼后指煎,發(fā)現(xiàn)就是常見的內(nèi)部緩存了一個Map進行對ViewModel的管理。
那么這個 mViewModelStore
是什么便斥?為什么要緩存 viewModel
呢至壤?
這個 mViewModelStore
就是實例化 ViewModelProvider 時候傳進來的(請見 ViewModel 生成的流程圖,由 activity.getViewModelStore()
或 fragment.getViewModelStore()
獲得)枢纠。
了解 mViewModelStore
是什么之后像街,我們就解決第二個疑問? UI 控制器中的屬性 mViewModelStore
為什么要緩存viewModel晋渺?難道是想重新拿出來用镰绎,來達到 ViewModel
的長生命周期?(因為剛剛上上圖提到第一處生成 ViewModel
的就是從 mViewModelStore
那里 get(key)
出來的)接下來我們就去了解 mViewModelStore
在 Activity 和 Fragment 中的作用了木西,我們先了解 Activity 的畴栖。
在 Activity 中能夠看到 getViewModelStore()
方法,mViewModelStore 有三種方式八千,1.自身不為 null 吗讶;2. nc.viewModelStore
;3. new ViewModelStore();
。通過查看 mViewModelStore
在整個 Activity
類中恋捆,只有這個方法用到照皆,所以 mViewModelStore
只能從2 3中獲得實例。3不用說沸停,直接看2 --> getLastNonConfigurationInstance()
從名字上看相當(dāng)有嫌疑膜毁!
點擊 getLastNonConfigurationInstance()
查看源碼,發(fā)現(xiàn)描述如下圖星立。通過描述可以知道爽茴,我們沒有找錯!绰垂!獲得之前由{@link #onRetainNonConfigurationInstance()}返回的 non-configuration instance data 室奏。這將從初始化 {@link #onCreate} 和 {@link #onStart} 調(diào)用新實例中獲得 non-configuration instance data ,允許您從前一個實例中提取任何有用的動態(tài)狀態(tài)劲装。話雖如此胧沫,但是還是想知道 mLastNonConfigurationInstances
它是怎么來的昌简?為什么可以把上一個 Activity 的 NonConfigurationInstances 數(shù)據(jù)拿到現(xiàn)在的 Activity 來
根據(jù)描述,是從 onRetainNonConfigurationInstance()
方法那里來的绒怨,但是查看了 Activity
的這個方法纯赎,發(fā)現(xiàn)方法里面是 null
!然后難道是子類做了實現(xiàn)南蹂?為什么會這么想犬金?
因為 public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory)
中使用的是 FragmentActivity
而不是 Activity
,在方法設(shè)計上會偏向使用基類來進行參數(shù)傳遞六剥。而且之前的 mViewModelStore
也是由 FragmentActivity
的父類 ComponentActivity
生成的晚顷,而且 NonConfigurationInstances
類也是 ComponentActivity
的內(nèi)部類。所以我們這才推斷是不是 Activity
的子類實現(xiàn)了疗疟。同時我們也可以學(xué)習(xí)到:
- 要對
Activity
该默、ComponentActivity
、FragmentActivity
策彤、AppCompatActivity
之間的異同有所涉獵栓袖,尤其在設(shè)計底層框架以及接口的時候,要慎重選擇店诗,不然會設(shè)計出來不好用裹刮,處處受阻。- 設(shè)計底層的時候庞瘸,類適當(dāng)?shù)姆謱永^承有助于架構(gòu)的清晰解耦必指。
我們只需查看 FragmentActivity
和 ComponentActivity
哪個類實現(xiàn)了 onRetainNonConfigurationInstance()
就可以了。
查看 FragmentActivity
的父類 ComponentActivity
的時候可以見到
TODO:
已知是調(diào)用ComponentActivity的onRetainNonConfigurationInstance()來保存了NonConfigurationInstances數(shù)據(jù)的恕洲,這就涉及到APP的啟動、AMS等一系列比較復(fù)雜的過程梅割,推薦點擊ConfigurationChanged流程梳理(屏幕旋轉(zhuǎn)霜第、語言及字體切換)
- 這個方法是什么時候被調(diào)用的?
- 它是如何將數(shù)據(jù)給下一個Activity的户辞?
所以這也同時回答了“ ViewModel 為什么不能綁定 Activity泌类、Fragment 的問題?”
而 ViewModel 什么時候才會被 clear 掉呢底燎?根據(jù) ViewModel 的生命周期圖標(biāo)刃榨,應(yīng)該是 activity 調(diào)用 onFinish()
會調(diào)用 ViewModel 的 clear()
。我們進一步查看哪里調(diào)用了 ViewModel 的 clear()
方法双仍,可知會從 ComponentActivity.Java
和 FragmentManagerViewModel.java
處有所調(diào)用枢希,我們先分析 ComponentActivity.Java
。
由下圖可以看到朱沃,當(dāng) ComponentActivity 是 ON_DESTROY
狀態(tài)時苞轿,而且不是配置變更
時茅诱,會調(diào)用 ViewModelStore.clear()
方法清空與之綁定的 ViewModel 數(shù)據(jù)。
由下圖 FragmentManagerViewModel 清空數(shù)據(jù)分析可見搬卒,當(dāng) Fragment 應(yīng)該 Destroy 時,而且不是配置變更
時瑟俭,會調(diào)用 ViewModelStore.clear()
方法清空與之綁定的 ViewModel 數(shù)據(jù)。
所以這就解析了契邀,ViewModel 的生命周期為什么可以跟 Activity 一樣長摆寄,以及為什么可以在配置變更后還可以繼續(xù)復(fù)用了。
總結(jié)
- ViewModel 本身只是一個保存數(shù)據(jù)的類坯门,需要搭配 livedata微饥、databinding 才能發(fā)揮效果。單獨用作用不大田盈,不過好處是解耦畜号,方便 unit test,效果很明顯允瞧!這篇只是推廣初級文章简软,很多注意的細節(jié)點,可以點擊這里
- ViewModel 盡量不要綁定跟Android包有關(guān)的類述暂,有可能造成內(nèi)存泄漏(具體請見[內(nèi)部實現(xiàn)]章節(jié))以及會給 Unit Test 造成困難痹升,Google 建立 JetPack 的其中一個目的就是解耦可測試!
源碼地址
https://github.com/EdgarNg1024/kaixue-docs/tree/master/viewmodel
https://github.com/google/iosched/
參考
- Android Jetpack - ViewModel | 中文教學(xué)視頻
- ViewModels and LiveData: Patterns + AntiPatterns (譯文)[強烈推薦畦韭,作者應(yīng)該是 Google 里面有參與 ViewModel 的實現(xiàn)開發(fā)的疼蛾,所以文章的推薦用法應(yīng)該是最貼近 ViewModel 開發(fā)思想]
- ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders (譯文)[強烈推薦,作者應(yīng)該是 Google 里面有參與 ViewModel 的實現(xiàn)開發(fā)的艺配,所以文章的推薦用法應(yīng)該是最貼近 ViewModel 開發(fā)思想]
- Google I/O 2018 app?—?Architecture and Testing[強烈推薦察郁,作者應(yīng)該是 Google 里面有參與 ViewModel 的實現(xiàn)開發(fā)的,所以文章的推薦用法應(yīng)該是最貼近 ViewModel 開發(fā)思想]
- SharePreference數(shù)據(jù)存儲工具類[借用了這哥們的SharePreference工具類]
撰稿人
Edgar Ng(https://github.com/EdgarNg1024)