經(jīng)過(guò)前段時(shí)間的 Android Dev Summit ,相信你已經(jīng)大概了解了 Jetpack Compose 。如果你還沒有聽說(shuō)過(guò),可以閱讀這篇文章 Jetpack Compose 最新進(jìn)展 ??偠灾?,Compose 是一個(gè) 顛覆性 的 聲明式 UI 框架 ,它的口號(hào)就是 消滅 xml 文件 ! 盡管 Jetpack Compose 還只是預(yù)覽版,API 可能發(fā)生變化,缺乏足夠的控件支持,甚至不是那么穩(wěn)定,但這阻止不了我這顆好奇的心。我在第一時(shí)間就上手?jǐn)]了一款 Compose 版本 Wanandroid 應(yīng)用,功能也比較簡(jiǎn)單,僅僅包括首頁(yè),廣告和最新項(xiàng)目,類似于 Android 原生頁(yè)面的 可以看出來(lái)頁(yè)面并不是那么流暢,View 的復(fù)用應(yīng)該是個(gè)問(wèn)題,甚至我也沒發(fā)現(xiàn)應(yīng)該怎么做下拉刷新。那么,Compose 給我們帶來(lái)了什么呢?在解答這個(gè)問(wèn)題之前,我想先來(lái)說(shuō)說(shuō) Android 應(yīng)用架構(gòu)問(wèn)題。 荒蕪年代 —— MVC在我剛?cè)胄械臅r(shí)候,可以說(shuō)是 Android 開發(fā)的黃金時(shí)代,也可以說(shuō)是開發(fā)者的荒蕪時(shí)代。一方面,毫不夸張的說(shuō),基本會(huì)寫 xml 都能謀得一份工作。另一方面,對(duì)于開發(fā)者來(lái)說(shuō),遠(yuǎn)遠(yuǎn)沒有現(xiàn)在的規(guī)范的開發(fā)架構(gòu)。沒記錯(cuò)的話,我當(dāng)年的主力開發(fā)框架是 在架構(gòu)方面,很多人都是一個(gè) Activity 擼到死,我真的見過(guò)上千行zouguolai的 MainActivity 。并且覺得這就是 MVC 架構(gòu),實(shí)體類 Entity 就是 但這當(dāng)真是 正確的 MVC 模式中,Model 層不僅包含實(shí)體類 Entity,更重要的作用是處理業(yè)務(wù)邏輯。View 層負(fù)責(zé)處理視圖邏輯。而 Controller 就是 Model 和 View 之間的橋梁。橋怎么建,其實(shí)并沒有標(biāo)準(zhǔn),根據(jù)你自己的需求就可以了。 引用一張阮一峰老師的圖,大致是這么個(gè)意思,但是也不一定就完全都是單向依賴。 從荒蕪時(shí)代走過(guò)來(lái),MVC 總算有點(diǎn)分層的味道在里面了,分離了視圖層和業(yè)務(wù)層。但是 View 層和 Model 層的依賴關(guān)系,造成代碼耦合,終將導(dǎo)致 Activity 日益臃腫。那么有沒有辦法將 View 層和 Model 層徹底分離,做到視圖層和模型層完全分離呢? MVP 就應(yīng)運(yùn)而生了。 青銅年代 —— MVP依舊是阮一峰老師的圖片: 相較于 MVC ,MVP 用 想象一個(gè)獲取用戶信息的場(chǎng)景。 總結(jié)一下,MVP 中 View 和 Model 完全解耦,通過(guò) Presenter 通信。View 和 Presenter 共同處理視圖層邏輯,Model 層負(fù)責(zé)業(yè)務(wù)邏輯。 在 Github 上 Android 官方的架構(gòu)示例 architecture-samples 中 MVP 作為主分支堅(jiān)挺了很久。我最初也是根據(jù)這個(gè)官方示例改造了自己的 MVP 架構(gòu),并且使用了很長(zhǎng)時(shí)間。但是 MVP 作為一款面向接口編程的架構(gòu),隨著業(yè)務(wù)的復(fù)雜程度不斷加大,有種遍地都是接口的既視感,實(shí)在顯得有點(diǎn)繁瑣。 另外一點(diǎn),Presenter 的職責(zé)邊界不夠清晰,它除了承擔(dān)調(diào)用 Model 層獲取業(yè)務(wù)邏輯之外,還要控制 View 層處理 UI。用下面一段代碼表示一下: class LoginPresenter(private val mView: LoginContract.View) : LoginContract.Presenter { ...... override fun login(userName: String, passWord: String) { CoroutineScope(Dispatchers.Main).launch { val result = WanRetrofitClient.service.login(userName, passWord).await() with(result) { if (errorCode == -1) mView.loginError(errorMsg) else mView.login(data) } } } } 一旦 View 層發(fā)生任何變化,Presenter 層也要做出相應(yīng)改動(dòng)。雖然 View 和 Model 之間解耦了,但是 View 和 Presenter 卻耦合了。理想情況下,Presenter 層應(yīng)該僅負(fù)責(zé)數(shù)據(jù)的獲取,View 層自動(dòng)觀察數(shù)據(jù)的變化。于是,MVVM 來(lái)了。 黃金時(shí)代 —— MVVMGoogle 官圖鎮(zhèn)樓 。 MVP 風(fēng)光早已不在, Android 官方的架構(gòu)示例 architecture-samples 的主分支已經(jīng)切換到 MVVM 。在 Android 的 MVVM 架構(gòu)中,ViewModel 是重中之重,它一方面通過(guò)數(shù)據(jù)倉(cāng)庫(kù) Repository 獲取數(shù)據(jù),另一方面根據(jù)獲取的數(shù)據(jù)更新 View 層的 Activity/Fragment。等等,這句話怎么聽著這么耳熟,Presenter 不也是干了這些事嗎?的確,它們干的事情都差不多,但是實(shí)現(xiàn)上完全不一樣。 以我的開源項(xiàng)目 Wanandroid 中的 class LoginViewModel(val repository: LoginRepository) : BaseViewModel() { private val _uiState = MutableLiveData<LoginUiModel>() val uiState: LiveData<LoginUiModel> get() = _uiState fun loginDataChanged(userName: String, passWord: String) { emitUiState(enableLoginButton = isInputValid(userName, passWord)) } // ViewModel 只處理視圖邏輯,數(shù)據(jù)倉(cāng)庫(kù) Repository 負(fù)責(zé)業(yè)務(wù)邏輯 fun login(userName: String, passWord: String) { viewModelScope.launch(Dispatchers.Default) { if (userName.isBlank() || passWord.isBlank()) return@launch withContext(Dispatchers.Main) { showLoading() } val result = repository.login(userName, passWord) withContext(Dispatchers.Main) { if (result is Result.Success) { emitUiState(showSuccess = result.data,enableLoginButton = true) } else if (result is Result.Error) { emitUiState(showError = result.exception.message,enableLoginButton = true) } } } } private fun showLoading() { emitUiState(true) } private fun emitUiState( showProgress: Boolean = false, showError: String? = null, showSuccess: User? = null, enableLoginButton: Boolean = false, needLogin: Boolean = false ) { val uiModel = LoginUiModel(showProgress, showError, showSuccess, enableLoginButton,needLogin) _uiState.value = uiModel } data class LoginUiModel( val showProgress: Boolean, val showError: String?, val showSuccess: User?, val enableLoginButton: Boolean, val needLogin:Boolean ) } 可以看到,ViewModel 中是沒有 View 的引用的,View 通過(guò)可觀察的 LIveData 來(lái)觀察數(shù)據(jù)變化,基于觀察者模式做到和 ViewModel 完全解耦。 數(shù)據(jù)驅(qū)動(dòng)視圖 ,這是 Jetpack MVVM 推崇的一個(gè)重要原則。其基本數(shù)據(jù)流如下所示 :
曾經(jīng)和一些開發(fā)者討論過(guò)這樣一個(gè)問(wèn)題,** 不使用 DataBinding 還算是 MVVM 嗎 ?** 我認(rèn)為 MVVM 的核心從來(lái)不在于 DataBinding 。DataBinding 只是可以幫助我們將 數(shù)據(jù)驅(qū)動(dòng)視圖 做到極致,順便還可以雙向綁定。 要說(shuō)到對(duì) Jetpack MVVM 中最不滿意的一塊,那非 DataBinding 莫屬了。在我狹隘的認(rèn)為 DataBinding 就是一個(gè)在 xml 里面寫邏輯代碼的反人類的庫(kù)時(shí),我是堅(jiān)決反對(duì)在任何項(xiàng)目中引入它的。固執(zhí)己見的時(shí)候就容易走進(jìn)誤區(qū),在閱讀 KunminX 的 重學(xué)安卓:從 被反對(duì) 到 真香 的 Jetpack DataBinding! 之后,正如這篇文章名字一樣,真香。 香的確是香,一切能讓我早下班的都是好東西。在我的某次提交日志上,我寫下了 消滅 Adapter 幾個(gè)字,那時(shí)我剛用 DataBinding 消滅了大部分 RecyclerView 的 Adapter ??墒窃谔峤恢?,我的良心惴惴不安,我追究還是在 xml 文件里寫邏輯代碼了,難道這真的不反人類嗎? 未來(lái)可期 —— Jetpack Compose現(xiàn)在你應(yīng)該可以理解我對(duì) Jetpack Compose 的執(zhí)念了。拋去其他特性,在我看來(lái),它完美的解決了 數(shù)據(jù)驅(qū)動(dòng)視圖 的問(wèn)題,我再也不需要使用 DataBinding 了。 簡(jiǎn)單代碼展示一下 Compose 的用法。下面的代碼描繪的是首頁(yè) Tab 下的文章列表。 @Composable fun MainTab(articleUiModel: ArticleViewModel.ArticleUiModel?) { VerticalScroller { FlexColumn { inflexible { HeightSpacer(height = 16.dp) } flexible(1f) { articleUiModel?.showSuccess?.datas?.forEach { ArticleItem(article = it) } articleUiModel?.showError?.let { toast(App.CONTEXT, it) } wenjian articleUiModel?.showLoading?.let { Progress() } } } } } 這種寫法叫做 聲明式編程 ,會(huì)用 Flutter 的同學(xué)應(yīng)該很熟悉。方法參數(shù) 那么,數(shù)據(jù)如何更新呢?最簡(jiǎn)單的方式是使用 @Model data class ArticleUiModel(){ ...... } 對(duì),就是這么簡(jiǎn)單。 但是我在實(shí)際開發(fā)中結(jié)合 LiveData 使用時(shí),好像表現(xiàn)的不是那么正常。后來(lái)在 Medium 上無(wú)意中看到了解決方案,針對(duì) LiveData 做了特殊處理 : // general purpose observe effect. this will likely be provided by LiveData. effect API for // compose will also simplify soon. fun <T> observe(data: LiveData<T>) = effectOf<T?> { val result = +state<T?> { data.value } val observer = +memo { Observer<T> { result.value = it } } +onCommit(data) { data.observeForever(observer) onDispose { data.removeObserver(observer) } } result.value }wenjian 在 Activity/Fragment 中觀測(cè) LiveData 即可: class MainActivity : BaseVMActivity<ArticleViewModel>() { override fun initView() { setContent { +observe(mViewModel.uiState) WanandroidApp(mViewModel) } } override fun initData() { mViewModel.getHomeArticleList() } } 這樣 View 層就可以自動(dòng)觀察 LiveData 所包含的值了。 沒有 xml,沒有 DataBinding,一切看起來(lái)稱心如意多了。但就是 UI 體驗(yàn)有那么一點(diǎn)糟心,你可以在公眾號(hào)后臺(tái)回復(fù) Compose 安裝體驗(yàn)一下。由于還是早期的預(yù)覽版,這也是可以理解的。我相信,等到發(fā)布 Release 版本的時(shí)候,一定足以完全代替原聲的 View 體系。 本文并沒有詳細(xì)介紹 Jetpack Compose 的詳細(xì)使用過(guò)程和其他特性,更多信息我推薦下面兩篇文章: 最后正如 Android 官網(wǎng) Jetpack 介紹頁(yè)所說(shuō),Jetpack 可以幫助開發(fā)者更輕松的編寫優(yōu)質(zhì)應(yīng)用。的確,隨著應(yīng)用架構(gòu)的規(guī)范,我們只需要把精力放在需要的代碼上,加速開發(fā),消除樣板代碼,減少崩潰和內(nèi)存泄露,構(gòu)建高質(zhì)量的強(qiáng)大應(yīng)用。我想不出來(lái)有任何理由不使用 Jetpack 來(lái)構(gòu)建你的應(yīng)用。而 Compose 必將稱為 Jetpack 中極其重要的一塊拼圖。 Jetpack Compse ,未來(lái)可期 ! 添加我的微信,加入技術(shù)交流群。 公眾號(hào)后臺(tái)回復(fù) “compose”, 獲取最新安裝包。 |
|