一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Jetpack Compse 實(shí)戰(zhàn) —— 全新的開發(fā)體驗(yàn)

 小世界的野孩子 2022-01-03

項(xiàng)目地址: Wanandroid-Compose

經(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è)面的 Viewpager + TabLayout 。下面的 gif 展示了應(yīng)用的基本頁(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ā)框架是 xUtils 2 ,一個(gè)類庫(kù)大包干,從 布局和 ID 綁定,網(wǎng)絡(luò)請(qǐng)求,到圖片展示,ORM 操作,一應(yīng)俱全。當(dāng)時(shí)的 布局和 ID 綁定還是運(yùn)行時(shí)反射,而不是編譯期注解。很長(zhǎng)一段時(shí)間以來(lái),Android 連一個(gè)官方的網(wǎng)絡(luò)庫(kù)都沒有。

在架構(gòu)方面,很多人都是一個(gè) Activity 擼到死,我真的見過(guò)上千行zouguolai的 MainActivity 。并且覺得這就是 MVC 架構(gòu),實(shí)體類 Entity 就是 Model 層,Activity/Fragment 就是 Controller 層,布局文件就是 View 層。

但這當(dāng)真是 MVC 嗎?其實(shí)并不是。不管是 MVCMVP,還是 MVVM,都應(yīng)該遵循一個(gè)最起碼的原則,表現(xiàn)層和業(yè)務(wù)層分離 ,也就是 Android 官網(wǎng)給出的 應(yīng)用架構(gòu)指南 中強(qiáng)調(diào)的 分離關(guān)注點(diǎn) 。Activity/Fragment 既要承擔(dān)視圖層的任務(wù),展示和更新 UI,又要處理業(yè)務(wù)層邏輯,獲取數(shù)據(jù)等。這并不符合架構(gòu)設(shè)計(jì)的基本原則。

正確的 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 用 Presenter 層 代替了 Controller 層 ,且 View 層Model 層 完全分離,依靠 Presenter 進(jìn)行通信 。

想象一個(gè)獲取用戶信息的場(chǎng)景。IView 接口中定義了一系列視圖層接口 ,View 層(Activity)實(shí)現(xiàn) IView 接口中相應(yīng)視圖邏輯。 View 層通過(guò)持有的 Presenter 處理業(yè)務(wù)邏輯,即請(qǐng)求用戶信息。一般情況下,Presenter 也不直接處理業(yè)務(wù)邏輯,而是通過(guò) Model 層,例如數(shù)據(jù)倉(cāng)庫(kù) Repository, 來(lái)獲取數(shù)據(jù),避免 Presenter 重蹈覆轍,日漸臃腫。同時(shí),Presenter 層也是持有 VIew 的,獲取用戶信息之后再轉(zhuǎn)發(fā)給 View 。

總結(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í)代 —— MVVM

Google 官圖鎮(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 中的 LoginViewModel 為例:

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ù)流如下所示 :

  • 數(shù)據(jù)層 Repository 負(fù)責(zé)從不同數(shù)據(jù)源獲取和整合數(shù)據(jù),基本負(fù)責(zé)所有的業(yè)務(wù)邏輯

  • ViewModel 持有 Repository,獲取數(shù)據(jù)并驅(qū)動(dòng) View 層更新

  • View 持有 ViewModel,觀察 LiveData 攜帶的數(shù)據(jù),數(shù)據(jù)驅(qū)動(dòng) UI

曾經(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ù) ArticleUiModel 就是數(shù)據(jù)實(shí)體類,直接根據(jù)數(shù)據(jù) ArticleUiModel 構(gòu)建 UI 。說(shuō)的大白話一點(diǎn),就是給你長(zhǎng)方形的長(zhǎng)和寬了,讓你畫個(gè)長(zhǎng)方形出來(lái)。最后加上 @Compose 注解,就是一個(gè)可用的 UI 組件了。仔細(xì)看代碼,里面還用了兩個(gè) UI 組件 ,ArticleItemProgress ,代碼就不貼出來(lái)了。分別是文章列表的 item 項(xiàng)目 和加載進(jìn)度條。

那么,數(shù)據(jù)如何更新呢?最簡(jiǎn)單的方式是使用 @Model 注解。

@Model
data class ArticleUiModel(){
  ......
}

對(duì),就是這么簡(jiǎn)單。@Model 注解會(huì)自動(dòng)把你的數(shù)據(jù)類變成可觀察對(duì)象,只要 ArticleUIModel 發(fā)生變化,UI 就會(huì)自動(dòng)更新。

但是我在實(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”, 獲取最新安裝包。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    色婷婷人妻av毛片一区二区三区| 欧美精品日韩精品一区| 翘臀少妇成人一区二区| 99久久成人精品国产免费| 欧美国产在线观看精品| 深夜福利亚洲高清性感| 日韩欧美中文字幕av| 不卡中文字幕在线免费看| 国产又大又猛又粗又长又爽| 大香蕉网国产在线观看av| 国产精品熟女在线视频| 国产不卡免费高清视频| 日韩人妻少妇一区二区| 色婷婷中文字幕在线视频| 成人精品国产亚洲av久久| 日本高清中文精品在线不卡| 亚洲中文字幕在线综合视频| 91播色在线免费播放| 久久香蕉综合网精品视频| 99久热只有精品视频免费看| 激情三级在线观看视频| 亚洲日本中文字幕视频在线观看| 日本成人三级在线播放| 国产永久免费高清在线精品| 五月激情综合在线视频| 欧美字幕一区二区三区| 日本午夜福利视频免费观看| 亚洲精品欧美精品一区三区| 国产av大片一区二区三区| 亚洲中文字幕人妻av| 精品欧美国产一二三区| 国产一区二区三区口爆在线| 午夜激情视频一区二区| 国产精品午夜性色视频| 黄片三级免费在线观看| 加勒比日本欧美在线观看| 自拍偷女厕所拍偷区亚洲综合| 五月天丁香亚洲综合网| 极品少妇一区二区三区精品视频| 初尝人妻少妇中文字幕在线| 国产精品一区二区视频|