網站首頁 編程語言 正文
Android框架的歷史演變
記得最開始入門Android的時候,還未流行MVP,都是MVC一把梭,后面工作了就是使用了MVP,當時學習的時候好難理解它的回調。
到目前主流的MVVM,其實就是MVP的升級版,再到最新的MVI使用意圖傳輸,隔離各層級的直接調用。我算是經歷了Android框架變遷的全過程。
這里記錄一下各框架的簡單Demo用例。
一. MVC框架
經典MVC分為:
Model 模型層 : 數據和網絡
View 視圖層 : 視圖的展示
Controller 控制層 : 邏輯控制,調用模型驅動視圖
一般我們是把一個xml看作一個View層, Activity看作一個Control層 , Model層則是由相關的數據操作類。
Model層:
class OtherModel : BaseRepository() {
/**
* 使用擴展方法,請求網絡
*/
suspend inline fun getIndustry(): OkResult<List<Industry>> {
return extRequestHttp {
DemoRetrofit.apiService.getIndustry(
Constants.NETWORK_CONTENT_TYPE,
Constants.NETWORK_ACCEPT_V1
)
}
}
}
Controller層:
class MVCActivity : AbsActivity() {
private val mOtherModel: OtherModel by lazy { OtherModel() }
override fun setContentView() {
setContentView(R.layout.activity_demo14_1)
}
override fun init() {
val btnGetData = findViewById<Button>(R.id.btn_get_data)
btnGetData.click {
requestIndustry()
}
}
private fun requestIndustry() {
//MVC中Activity就是Controller,直接調用接口,獲取數據之后直接操作xml控件刷新
lifecycleScope.launch {
//開始Loading
LoadingDialogManager.get().showLoading(this@MVCActivity)
val result = mOtherModel.getIndustry()
result.checkSuccess {
//處理成功的信息
toast("list:$it")
//doSth...
}
LoadingDialogManager.get().dismissLoading()
}
}
}
XML就是View層,獲取到信息展示到XML中。
這樣分工其實也是很明確的,但是一旦邏輯過多,會導致Activity太臃腫。Activcity中又是Model又是View,耦合性太強,記得那時候一個Activity中上千行代碼都是平平常常。
為了解決這個問題,大家開始使用MVP架構。
二. MVP框架
MVP框架的出現,各個模塊權責分明,各干各的活,降低了耦合,減少Activity的臃腫。
Model層:還是MVC那個Model。
View層:接口定義由Activity實,用于操作相應的UI。
Presenter層:用于Model和View的橋梁,負責Model與View的交互
那更復雜的一點的,就是其中加入Contract契約類,把指定頁面的Presenter和View等關聯起來,方便維護。
View接口定義:
interface IDemoView {
fun showLoading()
fun hideLoading()
fun getIndustrySuccess(list: List<Industry>?)
fun getIndustryFailed(msg: String?)
}
Presenter的實現:
class DemoPresenter(private val view: IDemoView) {
private val mOtherModel: OtherModel by lazy { OtherModel() }
//獲取行業數據
fun requestIndustry(lifecycleScope: LifecycleCoroutineScope) {
lifecycleScope.launch {
//開始Loading
view.showLoading()
val result = mOtherModel.getIndustry()
result.checkResult({
//處理成功的信息
toast("list:$it")
view.getIndustrySuccess(it)
}, {
//失敗
view.getIndustryFailed(it)
})
view.hideLoading()
}
}
}
Activity的實現:
class MVPActivity : AbsActivity(), IDemoView {
private lateinit var mPresenter: DemoPresenter
override fun setContentView() {
setContentView(R.layout.activity_demo14_1)
}
override fun init() {
//創建Presenter
mPresenter = DemoPresenter(this)
val btnGetData = findViewById<Button>(R.id.btn_get_data)
btnGetData.click {
//通過Presenter調用接口
mPresenter.requestIndustry(lifecycleScope)
}
}
//回調再次觸發
override fun showLoading() {
LoadingDialogManager.get().showLoading(this)
}
override fun hideLoading() {
LoadingDialogManager.get().dismissLoading()
}
override fun getIndustrySuccess(list: List<Industry>?) {
//popupIndustryData
}
override fun getIndustryFailed(msg: String?) {
//showErrorMessage
}
}
當時MVP框架是火遍一時,當時面試要不會這個,那都不好意思說是做安卓的。
雖然它有一些缺點,比如太復雜,每次都要寫重復的View,修改麻煩,回調地獄,數據交互體驗不佳,無法感知生命周期,重建頁面無法自動恢復數據,耦合還是有很多,等等。但是在當時沒有替代品的選擇下,它是當之無愧的王。
但是當谷歌出了Jetpack,當ViewModel+LiveData+Lifecycles的出現給了我們新的選擇 MVVM框架開始出現并迅猛發展。
三. MVVM框架
這里先說一點有爭議的點。 有些人認為,只要用上ViewModel+LiveData這些就算MVVM框架 Model+View+ViewModel嘛。 有些人認為,MVVM的意思是數據驅動,最大的亮點是數據綁定,使用DataBinding的才算MVVM。 其實這個也沒有官方的定義,世上本無框架,用的人多了才出現框架名字,約定俗成的東西,你想怎么定義就怎么定義,那我姑且稱為前者為半MVVM后者為MVVM吧
3.1 半MVVM框架
其實可以理解為MVP的升級版,去掉了View的接口回調,保存了ViewModel的特性
Model層:還是MVC那個Model。
View層:Activity,用于操作相應的UI。
ViewModel:還是MVP那個Presenter,只是用ViewModel實現。
ViewModel實現: 可以看到代碼確實相比MVP少了很多
class DemoViewModel @ViewModelInject constructor(
private val mRepository: Demo5Repository,
@Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
val liveData = MutableLiveData<List<Industry>?>()
//獲取行業數據
fun requestIndustry() {
viewModelScope.launch {
//開始Loading
loadStartLoading()
val result = mRepository.getIndustry()
result.checkResult({
//處理成功的信息
toast("list:$it")
liveData.value = it
}, {
//失敗
liveData.value = null
})
loadHideProgress()
}
}
}
Activity的實現:
@AndroidEntryPoint
class MVVMActivity : BaseVMActivity<DemoViewModel>() {
override fun getLayoutIdRes(): Int = R.layout.activity_demo14_1
override fun init() {
//自動注入ViewModel,調用接口通過LiveData回調
mViewModel.requestIndustry()
}
override fun startObserve() {
//獲取到網絡數據之后改變xml對應的值
mViewModel.liveData.observe(this) {
it?.let {
// popopIndustryData
}
}
}
}
3.2 帶DataBinding的MVVM框架
特別是現在kotlin那種直接拿id使用的插件已經被官方標記為過時,還不趕緊用DataBinding或ViewBinding?
ViewModel實現:
class DemoViewModel @ViewModelInject constructor(
private val mRepository: Demo5Repository,
@Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
val liveData = MutableLiveData<List<Industry>?>()
//獲取行業數據
fun requestIndustry() {
viewModelScope.launch {
//開始Loading
loadStartLoading()
val result = mRepository.getIndustry()
result.checkResult({
//處理成功的信息
toast("list:$it")
liveData.value = it
}, {
//失敗
liveData.value = null
})
loadHideProgress()
}
}
}
Repository的實現:其實和Model差不多的意思,數據倉庫而已,下面的一些注解是用到了Hilt依賴注入,不用直接new對象也是可以的,不要在意一些細節。
@Singleton
class Demo5Repository @Inject constructor() : BaseRepository() {
suspend inline fun getIndustry(): OkResult<List<Industry>> {
return extRequestHttp {
DemoRetrofit.apiService.getIndustry(
Constants.NETWORK_CONTENT_TYPE,
Constants.NETWORK_ACCEPT_V1
)
}
}
}
Activity的實現: 內部做了一些基類的封裝,事件處理封裝為對象,viewmodel和事件對象在xml中做了引用
@AndroidEntryPoint
class MVVM2Activity : BaseVDBActivity<DemoViewModel, ActivityDemo142Binding>() {
private val clickProxy: ClickProxy by lazy { ClickProxy() }
override fun getDataBindingConfig(): DataBindingConfig {
return DataBindingConfig(R.layout.activity_demo14_2, BR.viewModel, mViewModel)
.addBindingParams(BR.click, clickProxy)
}
override fun init() {
}
override fun startObserve() {
}
/**
* DataBinding事件處理
*/
inner class ClickProxy {
fun getData() {
//MVVM直接調用網絡請求,結果在xml中自動顯示
mViewModel.requestIndustry()
}
}
}
Xml的實現: 注意引用指向的包名要寫對,寫對了可以直接跳轉過去的。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:binding="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.guadou.kt_demo.demo.demo14_mvi.mvvm1.DemoViewModel" /> <variable name="click" type="com.guadou.kt_demo.demo.demo14_mvi.mvvm2.MVVM2Activity.ClickProxy" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/picture_color_blue" android:orientation="vertical"> <com.guadou.lib_baselib.view.titlebar.StatusbarGrayView android:id="@+id/status_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取數據" binding:clicks="@{click.getData}" /> <TextView android:id="@+id/tv_message" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{viewModel.liveData.toString()}" /> </LinearLayout> </layout>
這樣就完成了一個基于數據驅動的DataBinding的MVVM。 如果上面一些代碼如果看不太明白,后面我可能會出DataBinding的封裝并開源。
截止到發稿日期為止,目前市面上最流行的還是MVVM框架,此框架唯一的槽點可能就是Databinding的不好調試吧,一旦出問題,有時候報錯信息莫名其妙的,沒有指向XML中某個數據或語法的錯誤,需要對DataBinding有一定的了解。 不過AS現在貌似越來越智能了,報錯信息都還指向蠻清晰的。MVVM完全可用的。
四. MVI框架
由于是出來沒多久,具體是不是叫MVI框架這個名字還不確定,大家都這么叫,姑且就叫MVI吧。伴隨Compose出現的框架,主流用于Compose應用。
MVI框架是由Model View Intent組成的??梢运闵螹VVM的升級版,在之前我們都是通過在Activity中直接調用ViewModel的方法,現在改為發出操作指令,由ViewModel解析指令,調用對應的方法,回調給Activity。
比如一個DemoActivity需要獲取行業數據,學校數據,等。那么就可以把數據和操作都封裝成指定的對象。
//當前頁面所需的數據與狀態
data class Demo14ViewState(
val industrys: List<Industry> = emptyList(),
val schools: List<SchoolBean> = emptyList(),
var isChanged: Boolean = false
) : BaseViewState()
//當前頁面需要的事件定義
sealed class DemoAction {
object RequestIndustry : DemoAction()
object RequestSchool : DemoAction()
object RequestAllData : DemoAction()
data class UpdateChanged(val isChange: Boolean) : DemoAction()
}
Activity調用相關的接口就不是直接調用ViewModel的方法,而是:
override fun init() {
//發送Intent指令,具體的實現由ViewModel實現
mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
}
那么ViewModel就需要解析指令:
//Action分發入口
fun dispatch(action: DemoAction) {
when (action) {
is DemoAction.RequestIndustry -> requestIndustry()
is DemoAction.RequestSchool -> requestSchool()
is DemoAction.RequestAllData -> getTotalData()
is DemoAction.UpdateChanged -> changeData(action.isChange)
}
}
//獲取行業數據
private fun requestIndustry() {
//xxx
}
完整的代碼如下:
ViewModel的實現:
class Damo14ViewModel @ViewModelInject constructor(
private val mRepository: Demo5Repository,
@Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
//只需要暴露一個LiveData,包括頁面所有狀態
val viewStates: LiveData<Demo14ViewState> = _viewStates
//Action分發入口
fun dispatch(action: DemoAction) {
when (action) {
is DemoAction.RequestIndustry -> requestIndustry()
is DemoAction.RequestSchool -> requestSchool()
is DemoAction.RequestAllData -> getTotalData()
is DemoAction.UpdateChanged -> changeData(action.isChange)
}
}
//獲取行業數據
private fun requestIndustry() {
viewModelScope.launch {
//開始Loading
loadStartLoading()
val result = mRepository.getIndustry()
result.checkSuccess {
_viewStates.setState {
copy(industrys = it ?: emptyList())
}
}
loadHideProgress()
}
}
//獲取學校數據
private fun requestSchool() {
viewModelScope.launch {
//開始Loading
loadStartLoading()
val result = mRepository.getSchool()
result.checkSuccess {
_viewStates.setState {
copy(schools = it ?: emptyList())
}
}
loadHideProgress()
}
}
//獲取全部數據
private fun getTotalData() {
//默認執行在主線程的協程-必須用(可選擇默認執行在IO線程的協程)
launchOnUI {
//開始Loading
loadStartProgress()
val industryResult = async {
mRepository.getIndustry()
}
val schoolResult = async {
mRepository.getSchool()
}
//一起處理數據
val industry = industryResult.await()
val school = schoolResult.await()
//如果都成功了才一起返回
if (industry is OkResult.Success && school is OkResult.Success) {
loadHideProgress()
//設置多種LiveData
_viewStates.setState {
copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList())
}
}
}
}
//改變狀態
private fun changeData(isChanged: Boolean) {
_viewStates.setState {
copy(isChanged = isChanged)
}
}
//當前頁面所需的數據與狀態
data class Demo14ViewState(
val industrys: List<Industry> = emptyList(),
val schools: List<SchoolBean> = emptyList(),
var isChanged: Boolean = false
) : BaseViewState()
//如果想再度封裝,也可以把回調的結果封裝成類似Action的對象,由頁面判斷回調的是哪一種類型,進行相關的操作
//這樣就不需要使用LiveData回調了,LiveData就只是作為保存數據的功能,由DemoEvent回調
// sealed class DemoEvent {
// object PopBack : DemoEvent()
// data class ErrorMessage(val message: String) : DemoEvent()
// }
//當前頁面需要的事件定義
sealed class DemoAction {
object RequestIndustry : DemoAction()
object RequestSchool : DemoAction()
object RequestAllData : DemoAction()
data class UpdateChanged(val isChange: Boolean) : DemoAction()
}
}
Activity的實現:
@AndroidEntryPoint
class Demo14Activity : BaseVDBActivity<Damo14ViewModel, ActivityDemo14Binding>() {
private val clickProxy: ClickProxy by lazy { ClickProxy() }
companion object {
fun startInstance() {
commContext().let {
it.startActivity(Intent(it, Demo14Activity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
}
}
override fun getDataBindingConfig(): DataBindingConfig {
return DataBindingConfig(R.layout.activity_demo14)
.addBindingParams(BR.click, clickProxy)
}
@SuppressLint("SetTextI18n")
override fun startObserve() {
//監聽兩者數據變化
mViewModel.viewStates.observeState(
this,
Damo14ViewModel.Demo14ViewState::industrys,
Damo14ViewModel.Demo14ViewState::schools
) { industry, school ->
YYLogUtils.w("industry: $industry ; school: $school")
}
//只監聽changed的變換
mViewModel.viewStates.observeState(this, Damo14ViewModel.Demo14ViewState::isChanged) {
if (it) {
val industry = mViewModel.viewStates.value?.industrys
val school = mViewModel.viewStates.value?.schools
mBinding.tvMessage.text = "industry: $industry ; school: $school"
}
}
}
override fun init() {
//發送Intent指令,具體的實現由ViewModel實現
mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
}
/**
* DataBinding事件處理
*/
inner class ClickProxy {
fun getData() {
//發送Intent指令,具體的實現由ViewModel實現
// mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestIndustry)
// mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestSchool)
mViewModel.dispatch(Damo14ViewModel.DemoAction.UpdateChanged(true))
}
}
}
注意,有些MVI的寫法是回調給Activity的方式也是用對象封裝如我注釋的代碼:
//如果想再度封裝,也可以把回調的結果封裝成類似Action的對象,由頁面判斷回調的是哪一種類型,進行相關的操作
//這樣就不需要使用LiveData回調了,LiveData就只是作為保存數據的功能,由DemoEvent回調
// sealed class DemoEvent {
// object PopBack : DemoEvent()
// data class ErrorMessage(val message: String) : DemoEvent()
// }
也可以使用LiveData返回,我這里使用擴展方法observeState方法來監聽,這樣可以保證只有你監聽的對象發生了變化才會收到回調。這個擴展方法在MVVM框架也能使用。
擴展方法如下:
import androidx.lifecycle.*
import kotlin.reflect.KProperty1
/**
* @auther Newki
* @date 2022/2/10
* @description LiveData的擴展 支持MVI模式 訂閱單個LiveData實現監聽不同的操作與數據
*/
//監聽一個屬性
fun <T, A> LiveData<T>.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1<T, A>,
action: (A) -> Unit
) {
this.map {
StateTuple1(prop1.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
action.invoke(a)
}
}
//監聽兩個屬性
fun <T, A, B> LiveData<T>.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1<T, A>,
prop2: KProperty1<T, B>,
action: (A, B) -> Unit
) {
this.map {
StateTuple2(prop1.get(it), prop2.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a, b) ->
action.invoke(a, b)
}
}
//監聽三個屬性
fun <T, A, B, C> LiveData<T>.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1<T, A>,
prop2: KProperty1<T, B>,
prop3: KProperty1<T, C>,
action: (A, B, C) -> Unit
) {
this.map {
StateTuple3(prop1.get(it), prop2.get(it), prop3.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) ->
action.invoke(a, b, c)
}
}
internal data class StateTuple1<A>(val a: A)
internal data class StateTuple2<A, B>(val a: A, val b: B)
internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C)
//更新State
fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) {
this.value = this.value?.reducer()
}
太干了,一張圖都沒上,最后總結一下:
世界上本無框架,用的人多了就成了框架,適合自己的才是好的。不是一定說出了最新框架我就要用最新的框架,理解之后再使用才能得心應手。
個人目前平時開發中用的也是MVVM框架。后期會出一些MVVM的封裝和用法開源。
原文鏈接:https://juejin.cn/post/7096762452041285668#heading-6
相關推薦
- 2023-08-01 elementui全局給select option添加title屬性
- 2022-07-14 Python中添加搜索路徑的方法實例_python
- 2022-06-17 flutter監聽app進入前后臺狀態的實現_Android
- 2022-10-29 STDC分割網絡:onnx推理
- 2022-05-12 小程序 onLaunch 與 onLoad 異步問題的解決方案
- 2022-11-26 ASP.NET延遲調用或多次調用第三方Web?API服務_實用技巧
- 2023-03-28 Python利用flask操作Redis的方法詳解_python
- 2022-03-22 C++對象與繼承使用中一些問題介紹_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支