公司的項目,一直是以Activity爲主體,類MVC模式的進行開發的,加入如今的公司一年以來,由於如今接手項目比較老,所以以前一直是在作項目新的開發加填之前的老坑。項目的主頁甚至還在用已經廢棄了好久的TabActivity,以前下定決心改把主頁修改爲了Activity+多Fragment的模式,之前的界面,Activity的邏輯過於複雜,正好接着此次重構的機會也和同事瞭解學習一下MVVM,爲以後的開發算是作一個本身的準備吧。java
Google爲了MVVM,提供了很多的🌰以及框架,如今Google主推的就是Jetpack了,MVVM的核心就是數據綁定,這個在Android裏,由於XML做爲view的功能極其孱弱,Google在Jetpack裏提供了Databinding的組件,讓XML和ViewModel進行數據綁定,經過綁定,若是ViewModel的數據變化,UI便可出現對應的響應。android
XML內使用DataBindinggit
<variable
name="viewmodel"
type="com.acclex.ViewModel" />
<TextView
android:text="@{viewmodel.user.name}"
複製代碼
爲了讓ViewModel去操做數據,方便Activity和XML觀察數據的改變,Google還提供了LiveData這個框架,它的本質是一個相似RxJava的實現觀察者模式的框架,大體使用方式以下github
ViewModel內數據庫
private val _user: MutableLiveData<User>
val user:LiveData<User>
get() = _user
// 更改數據
private fun updateUser() {
_user.value = User()
}
複製代碼
Activity或者Fragment內設計模式
viewModel.user.observe(this, Observer<User> { user ->
user?.let {
//todo do something
}
})
複製代碼
很簡單有效就能夠實現觀察者模式,讓view層和ViewModel層解耦,經過這種方式去處理數據的變更,和RxJava實現的功能是一致的,所以MVVM也可使用RxJava實現同樣的功能。經過觀察者模式,可讓view與數據解耦開來,Activity以及Fragment不須要再去處理任何與數據相關的事情。緩存
LiveData仍是有一些好處的,由於它是Google開發封裝的,它自帶了生命週期的管理,由於它observe的直接是LifecycleOwner這個對象,若是LifecycleOwner的對象被銷燬,LiveData則會本身去clean掉,我的認爲和生命週期綁定,這是一個很棒的優勢,更多的優勢,Google的官方文檔有詳細的介紹:developer.android.google.cn/topic/libra…bash
Jetpack內還提供了ViewModel讓開發者去使用,ViewModel其實就是對業務邏輯和業務數據的操做,在ViewModel裏,不會也不能夠持有任何View的引用,定義了一個ViewModel後,咱們一般在View層使用val viewModel = ViewModelProviders.of(this).get(ViewModel::class.java)
這樣的代碼去獲取ViewModel的實例,這個this能夠是Activity或者Fragment,ViewModel被初始化後會一直保留在內存內,直到它所做用域也就是Fragment觸發detached或者Activity觸發finishes,它纔會被回收。 若是咱們須要在初始化ViewModel的時候傳入構造參數,那麼咱們必需要寫一個繼承自ViewModelProvider.NewInstanceFactory
的類,代碼以下框架
class SampleViewModelFactory(
private val model: Model
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(SampleViewModel::class.java) ->
SampleViewModel(model)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
複製代碼
這些都是爲了方便開發者使用MVVM模式,Google在Jetpack內提供給咱們的一些組件,單獨來看,這些組件的使用方法,學習成本並不高,同時並無涉及到Model層單獨作一個組件封裝,由於Model能夠說是最自由,也是定製最多的組件。以前在我寫MVVM的demo的時候,我並無單獨的寫出一個Model,甚至將獲取數據寫在了ViewMode裏,讓ViewModel去獲取解析數據,並處理數據,以後的繼續學習,特別是閱讀了Google的android-architecture的源代碼以後,以前的思路能夠說是徹底錯誤的,接下來咱們就來談談關於MVVM內Model層的定義與使用ide
不論是MVC,MVP,MVVM的設計模式內,均存在Model層,可見Model層是極其重要的。可是在Android開發內,Model層反而是可能存在感最薄弱的一層,由於如今獲取數據的代碼,不論是聯網獲取,或者是讀取數據庫,或者是讀取本地的數據,代碼已經精簡到短短几行就能夠實現,不少開發的時候,不自覺的把這些方式寫在了Activity、Fragment內,又或者是寫在了ViewModel或者是Presenter內,以前在讀一個MVVM實現的時候,就直接將獲取數據寫在了ViewModel內。
那麼這樣寫,會致使什麼問題?若是是簡單的數據以及相對簡單的邏輯,它並不會形成太多的影響,可讀性也沒有收到不少的影響,可是若是須要進行單元測試的話,數據耦合在了邏輯裏,會對單元測試形成極大的影響。這是我對這個問題的見解。(ps:小弟技術菜,沒有想到別的一些問題,只能看出這一點影響可能較大的問題,有補充歡迎評論留言,謝謝!)
按照規範標準來看,Model層是負責數據存儲,數據處理,以及獲取數據。可是Google不像ViewModel、LiveData、DataBinding提供了現成的規範以及標準,所以我對Model層實際上是有一些問題的
這能夠說是Model層最關鍵的問題了,由於這關係到Model層的實現。這點我以爲仍是須要參照代碼來講明的。
恰好在此次項目的新的開發任務,我部分模塊採用了MVVM去實現,而且嘗試了一下本身進行Model的設計,所以直接上代碼,說一下個人理解。
interface BaseModel {
interface ModelDataCallBack<T> {
/**
* 成功的回調函數
* @param result 成功返回須要的類型
*/
fun onSuccess(result: T)
/**
* 失敗的回調函數
* @param errorLog 失敗後傳遞回去的錯誤的數據
*/
fun onFailure(errorLog: String)
}
}
複製代碼
一個很簡單的基礎的Model,接口ModelDataCallBack負責回調結果給ViewModel處理結果,由於每一個ViewModel、Model須要的數據不同,所以回傳的結果是由初始化傳進來的泛型決定的。失敗的話,在我設想裏,應該是返回一個解析的結果或者異常log,也許是彈出一個Toast或者是一些別的邏輯,所以返回失敗的結果定義成了返回一個String。 所以ViewModel、Model具體實現的代碼大體以下
class SampleModel : BaseModel {
fun getData(callBack: BaseModel.ModelDataCallBack<List<User>>){
// 若是成功
callBack.onSuccess(listOf(User("A",15)))
// 失敗
callBack.onFailure("數據獲取失敗")
}
}
複製代碼
class SampleViewModel(private val model:SampleModel) : ViewModel {
private val _list = MutableLiveData<List<User>>()
val list: LiveData<List<User>>
get() = _list
private fun updateUser() {
model.getData(object :BaseModel.ModelDataCallBack<List<User>>{
override fun onSuccess(result: List<User>) {
list.value = result
}
override fun onFailure(errorLog: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
}
}
複製代碼
如上代碼,能夠達成ViewModel負責處理邏輯,而Model負責獲取數據,ViewModel內接到成功或者失敗的回調,能夠觸發LiveData的數據更新,View層能夠經過觀察LiveData內的數據,作到UI的更新或者變換。 這是我設想的一個簡單的Model層,若是在開發中,一個Model內有許多的獲取數據的方法或者接口,那麼會產生大量的接口回調,若是是用kotlin的話,可使用高階函數直接傳入成功或者失敗的回調,相似以下代碼
fun getData(success:(List<User>) -> Unit,
fail:(String) ->Unit){
success.invoke(listOf(User("A",15)))
fail.invoke("數據獲取失敗")
}
複製代碼
在個人想法裏,大量的接口回調基本是不可能避免的,若是有dalao有更好的方案,歡迎評論區提出,感謝!
上述代碼,只是一個很簡單的Model設計,若是在開發中使用這樣的Model,會碰到的問題還有以下:
這些問題都是這個簡單的Model會碰到的,單元測試坑比較深,以後有空再單獨寫。 這個model的並不存在公共的實現方法,那麼根本不須要一個單獨的BaseModel接口,BaseModel的意義並不存在。若是這個model須要緩存,若是隻是model內存儲一個數據,那麼這樣的邏輯必然會影響到單元測試。所以這只是個人一個簡單的想法,後續還要完善。
在本身的這些想法以後,我專門去學習了一下Google的Sample的源代碼。放上Google的Sample,這裏是連接:github.com/googlesampl…。 先引用一張來自朋友博客的圖片,博客連接:博客連接
每一個Model是一個Respository都是一個DataSource接口的實例,裏面可能包含一種或者多種數據,每一個數據類型都實現了DataSource接口。在Google的Sample裏,也是根據這種模式去實現的。這是很理想化的Model設計,本地的數據,緩存的數據,測試的數據,單獨區分,每一個負責對應的職責,將代碼解耦開來,是很是好的。 下面上代碼interface TasksDataSource {
interface LoadTasksCallback {
fun onTasksLoaded(tasks: List<Task>)
fun onDataNotAvailable()
}
interface GetTaskCallback {
fun onTaskLoaded(task: Task)
fun onDataNotAvailable()
}
fun getTasks(callback: LoadTasksCallback)
fun getTask(taskId: String, callback: GetTaskCallback)
fun saveTask(task: Task)
fun completeTask(task: Task)
fun completeTask(taskId: String)
fun activateTask(task: Task)
fun activateTask(taskId: String)
fun clearCompletedTasks()
fun refreshTasks()
fun deleteAllTasks()
fun deleteTask(taskId: String)
}
複製代碼
Repository都實現了TasksDataSource接口,而且包含有多個實現了TasksDataSource接口的數據,或是本地數據,或是緩存數據。而且只有getTasks和getTask這兩個函數有回調方法,做爲回調給ViewModel的數據接口。別的函數做爲Model暴露給ViewModel去操做處理數據的函數。提升了通用性,可讓一個Repository去同時完成對緩存數據或者新數據的操做。
使用MVVM後,確實對代碼解耦產生了極好的效果,代碼的可讀性也上升的不少。文章裏不少東西仍是本人的一些想法, 以及碰到的一些問題並無找到好的解決方案,同時在開發中,使用DataBinding以後代碼的debug麻煩程度上升有點多,Model層的設計難度是我以爲最可貴一個點,Google Sample裏我以爲也有一些不太好的地方,以後我會再寫一篇討論一下Google的這個MVVM的Sample。
感謝各位的閱讀,若是有什麼想法,歡迎提出意見、批評。