首頁 > 軟體

Android架構發展進化詳解

2022-08-01 22:05:29

一.MVC架構

1.概述

MVC架構是第一個應用於Android開發的成熟架構,由Model、View、Controller三部分組成:

  • Model:負責資料的儲存及相關邏輯。
  • View:負責介面展示。
  • Controller:負責業務邏輯。

MVC架構將程式碼邏輯分成了資料邏輯、渲染邏輯、業務邏輯三部分,三部分邏輯分別封裝在Model層、View層、Controller層。理想條件下,三者呈單向呼叫,如下圖所示:

但實際上,由於Android中負責頁面展示的元件(Activity或Fragment)同時承擔了View層和Controller層兩部分的職責,導致MVC架構在實際使用中退化。退化後的MVC架構如下圖所示:

在一些業務場景中,由於View層與Controller層的邏輯耦合在一起,使得二者可以相互呼叫,進一步衍生出呼叫關係更加複雜的MVC架構,如下圖所示:

此時的MVC架構僅保持了Model層的獨立,而View層和Controller層沒有得到有效的隔離。

2.例子

點選按鈕從網路獲取使用者資訊,經過解析後展示到介面上。

1)Model層

data class UserInfo(
    var name: String,
    var age: Int,
    var height: Int,
    var weight: Int
)
interface NetResponse<T> {
    fun onSuccess(data: T)
    fun onError(e: Throwable)
}
class NetModel {
    fun getUserInfo(callback: NetResponse<UserInfo>) {
        // 模擬網路獲取
        val dataStr = getStringFromNet()
        val info = dataStr.parseJson<UserInfo>()
        return callback.onSuccess(info)
    }
}

2)Controller層

class MainActivity : AppCompatActivity() {
    private val netModel = NetModel()
    // Controller呼叫Model
    private fun getUserInfo(callback: (UserInfo) -> Unit) {
        netModel.getUserInfo(object : NetResponse<UserInfo> {
            override fun onSuccess(data: UserInfo) {
                callback.invoke(data)
            }
            override fun onError(e: Throwable) {
                callback.invoke(getNetErrorData())
            }
        })
    }
    private fun getNetErrorData(): UserInfo {
        return UserInfo(name = "", age = 0, height = 0, weight = 0)
    }
}

3)View層

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnNet?.setOnClickListener {
            // View呼叫Controller
            getUserInfo {
                tvName?.text = it.name
                tvAge?.text = it.age.toString()
                tvHeight?.text = it.height.toString()
                tvWeight?.text = it.weight.toString()
            }
        }
    }
}

在MVC架構中,Controller實際上就是對Model程式碼的封裝,如果在btnNet的點選回撥中呼叫getUserInfo方法,則是View呼叫Controller。如果在點選回撥中直接呼叫netModel的getUserInfo方法,那就變成了View呼叫Model。

二.MVP架構

1.概述

MVP架構是MVC架構的升級版,由Model、View、Presenter三部分組成:

  • Model:負責資料的儲存及相關邏輯。
  • View:負責介面展示。
  • Presenter:負責業務邏輯。

MVP架構也將程式碼邏輯分成了資料邏輯、渲染邏輯、業務邏輯三部分。與MVC架構不同的是,在MVP架構中,負責業務邏輯的Controller層升級為Presenter層。Presenter層不再像Controller層一樣耦合在View層中,而是作為獨立的類與View層和Model層進行雙向的通訊,三者呼叫關係如下圖所示:

MVP架構有效的將View層與Model層隔離,同時,由於Presenter層僅持有View層和Model層的介面,因此對於Presenter層邏輯的測試變得更加的方便。而由於所有的邏輯程式碼都集中到Presenter層中,因此Presenter層變得比原本的Controller層更臃腫。

2.例子

點選按鈕從網路獲取使用者資訊,經過解析後展示到介面上。

1)Model層

data class UserInfo(
    var name: String,
    var age: Int,
    var height: Int,
    var weight: Int
)
interface NetResponse<T> {
    fun onSuccess(data: T)
    fun onError(e: Throwable)
}
class NetModel {
    fun getUserInfo(callback: NetResponse<UserInfo>) {
        // 模擬網路獲取
        val dataStr = getStringFromNet()
        val info = dataStr.parseJson<UserInfo>()
        return callback.onSuccess(info)
    }
}

2)Presenter層

class Presenter(private val view: IView) {
    private val model = NetModel()
    fun getUserInfo() {
        model.getUserInfo(object : NetResponse<UserInfo> {
            override fun onSuccess(data: UserInfo) {
                view.setName(data.name)
                view.setAge(data.age)
                view.setHeight(data.height)
                view.setWeight(data.weight)
            }
            override fun onError(e: Throwable) {
                val data = getNetErrorData()
                view.setName(data.name)
                view.setAge(data.age)
                view.setHeight(data.height)
                view.setWeight(data.weight)
            }
        })
    }
    private fun getNetErrorData(): UserInfo {
        return UserInfo(name = "", age = 0, height = 0, weight = 0)
    }
}

3)View層

interface IView {
    fun setName(name: String)
    fun setAge(age: Int)
    fun setHeight(height: Int)
    fun setWeight(weight: Int)
}
class MainActivity : AppCompatActivity(), IView {
    private val presenter = Presenter(this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnNet?.setOnClickListener {
            presenter.getUserInfo()
        }
    }
    override fun setName(name: String) {
        tvName?.text = name
    }
    override fun setAge(age: Int) {
        tvAge?.text = age.toString()
    }
    override fun setHeight(height: Int) {
        tvHeight?.text = height.toString()
    }
    override fun setWeight(weight: Int) {
        tvWeight?.text = weight.toString()
    }
}

三.MVVM架構

1.概述

MVVM架構最早由微軟提出,並在微軟的桌面使用者端UI框架WPF中實現,由Model、View、ViewModel三部分組成:

  • Model:負責資料的儲存及相關邏輯。
  • View:負責介面展示。
  • ViewModel:負責業務邏輯。

MVVM架構是MVP架構的簡化版,它同樣的解決了View層與Model層的隔離問題。不同於MVP架構中的View層與Presenter層之間的雙向通訊,MVVM架構採用了一種View層與ViewModel層繫結的方式,當ViewModel層中的UI資料或屬性發生變化時,View層的UI會跟隨一起變化,這樣原本Presenter層中操作View層介面的邏輯通過繫結的方式被簡化,層級變得更加輕量,簡化後的Presenter層與View層之間只存在資料驅動的關係,Presenter層被簡化成了ViewModel層。三個層級的呼叫關係如下圖所示:

在MVVM架構的早期發展中, 開發者們使用谷歌官方的DataBinding框架實現資料與檢視的繫結,但DataBinding框架使用複雜,且每當有一處程式碼需要修改,就會聯動修改多處程式碼。後來,谷歌官方推薦使用Jetpack元件集中的LiveData+ViewModel。通過使用LiveData+ViewModel,大大減少了在使用MVVM架構中模版程式碼的編寫。再後來,為了更好的在協程中使用MVVM架構,以及更方便的編寫流式程式碼,谷歌官方推薦使用StateFlow。

MVVM架構既做到了View層與Model層的隔離,又保證了ViewModel層的簡潔不臃腫。View層與ViewModel層的互動完全依賴資料驅動,方便層級的邏輯複用與測試以及多人的共同作業開發。但同時,由於MVVM架構比MVC架構和MVP架構更抽象,因此在使用時需要編寫更多的模版程式碼。當UI介面複雜時,由於每個UI需要有不同的資料,因此會造成資料過於分散不易維護。

2.例子

點選按鈕從網路獲取使用者資訊,經過解析後展示到介面上。

1)Model層

data class UserInfo(
    var name: String,
    var age: Int,
    var height: Int,
    var weight: Int
)
interface NetResponse<T> {
    fun onSuccess(data: T)
    fun onError(e: Throwable)
}
class NetModel {
    fun getUserInfo(callback: NetResponse<UserInfo>) {
        // 模擬網路獲取
        val dataStr = getStringFromNet()
        val info = dataStr.parseJson<UserInfo>()
        return callback.onSuccess(info)
    }
}

2)ViewModel層

class NetViewModel : ViewModel() {
    private val model = NetModel()
    private val name = MutableLiveData<String>("")
    private val age = MutableLiveData<Int>(0)
    private val height = MutableLiveData<Int>(0)
    private val weight = MutableLiveData<Int>(0)
    fun getUserInfo() {
        model.getUserInfo(object : NetResponse<UserInfo> {
            override fun onSuccess(data: UserInfo) {
                name.postValue(data.name)
                age.postValue(data.age)
                height.postValue(data.height)
                weight.postValue(data.weight)
            }
            override fun onError(e: Throwable) {
                val data = getNetErrorData()
                name.postValue(data.name)
                age.postValue(data.age)
                height.postValue(data.height)
                weight.postValue(data.weight)
            }
        })
    }
    private fun getNetErrorData(): UserInfo {
        return UserInfo(name = "", age = 0, height = 0, weight = 0)
    }
    fun name(): LiveData<String> = name
    fun age(): LiveData<Int> = age
    fun height(): LiveData<Int> = height
    fun weight(): LiveData<Int> = weight
}

3)View層

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: NetViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(NetViewModel::class.java)
        viewModel.name().observe(this, Observer<String> {
            tvName?.text = it
        })
        viewModel.age().observe(this, Observer<Int> {
            tvAge?.text = it.toString()
        })
        viewModel.height().observe(this, Observer<Int> {
            tvHeight?.text = it.toString()
        })
        viewModel.weight().observe(this, Observer<Int> {
            tvWeight?.text = it.toString()
        })
        btnNet?.setOnClickListener {
            viewModel.getUserInfo()
        }
    }
}

四.Clean架構

1.概述

Clean架構由Robert.C.Martin(Bob大叔)提出,最基礎的Clean架構由Entities(實體層)、Use Cases(用例層)、Interface Adapters(介面適配層)、Frameworks & Drivers(框架驅動層)四部分組成:

  • Entities:用於封裝企業業務規則,指業務的核心資料模型和介面,一般情況下不會發生變化,只有當業務規則改變才會改變實體層。
  • Use Cases:用於封裝軟體應用的業務規則,包含實現整個應用所需全部功能的用例,通過呼叫各種實體的規則實現用例的功能。用例層的改變不會影響實體層,同時外部UI與資料庫的改變也不會影響到用例層。
  • Interface Adapters:用於將外部資料轉換為實體層與用例層需要的資料結構或將實體層與用例層返回的資料轉換為外部需要的資料結構。Clean-MVP中Presenter就是在這一層。
  • Frameworks & Drivers:由一些框架與工具組成,如資料庫、UI框架等。這層更多的是用於編寫技術細節,而不需要考慮業務邏輯。

Clean架構通過規範層級間的單向依賴關係,從而形成一層包裹一層的層級結構。如下圖所示:

Clean架構的層級並不是固定的。根據不同的業務特點,Clean架構也可有更多的層級(最少四層)。由此可見,Clean架構並非傳統意義上的架構,它更像是一種理念。Clean架構可以與MVC架構、MVP架構、MVVM架構結合形成Clean-MVC架構、Clean-MVP架構、Clean-MVVM架構。谷歌官方借鑑Clean的理念推出Clean-MVVM架構。

Clean架構具有單向依賴、層級分明、資料驅動的優點。因此在Clean架構中,各個層級的程式碼邏輯更容易被測試,缺陷與問題更容易被定位,程式碼的可讀性得到顯著提升。Clean架構的結構十分複雜。它被稱為洋蔥架構,不僅是因為它層層包裹的結構,也是因為當你意識到需要寫多層模版程式碼時,Clean架構會讓你哭泣。同時,Clean架構還存在用例層程式碼複用率低、急劇增加的用例導致類膨脹的問題。

2.例子

點選按鈕從網路獲取使用者資訊,經過解析後展示到介面上。

1)Entities層

data class UserInfo(
    var name: String,
    var age: Int,
    var height: Int,
    var weight: Int
)

2)Use Cases層

interface UseCase<T, R> {
    fun execute(param: T, callback: R)
}
interface UseCaseCallback<T> {
    fun success(data: T)
    fun fail(e: Throwable)
}
class GetUserInfoUseCase : UseCase<Unit, UseCaseCallback<UserInfo>> {
    private val repository = NetRepository()
    override fun execute(param: Unit, callback: UseCaseCallback<UserInfo>) {
        repository.getUserInfo(object : NetResponse<UserInfo> {
            override fun onSuccess(data: UserInfo) {
                callback.success(data)
            }
            override fun onError(e: Throwable) {
                callback.fail(e)
            }
        })
    }
}

3)Interface Adapters層

interface NetResponse<T> {
    fun onSuccess(data: T)
    fun onError(e: Throwable)
}
class NetRepository {
    fun getUserInfo(callback: NetResponse<UserInfo>) {
        // 模擬網路獲取
        val dataStr = getStringFromNet()
        val info = dataStr.parseJson<UserInfo>()
        return callback.onSuccess(info)
    }
}
interface IView {
    fun setName(name: String)
    fun setAge(age: String)
    fun setHeight(height: String)
    fun setWeight(weight: String)
}
class Presenter(private val view: IView) : UseCaseCallback<UserInfo> {
    private val getUserInfoUseCase = GetUserInfoUseCase()
    fun getUserInfo() =
        getUserInfoUseCase.execute(Unit, this)
    override fun success(data: UserInfo) {
        view.setName(data.name)
        view.setAge(data.age.toString())
        view.setHeight(data.height.toString())
        view.setWeight(data.weight.toString())
    }
    override fun fail(e: Throwable) {
        val data = getNetErrorData()
        view.setName(data.name)
        view.setAge(data.age.toString())
        view.setHeight(data.height.toString())
        view.setWeight(data.weight.toString())
    }
    private fun getNetErrorData(): UserInfo {
        return UserInfo(name = "", age = 0, height = 0, weight = 0)
    }
}

4)Frameworks & Drivers層

class MainActivity : AppCompatActivity(), IView {
    private val presenter = Presenter(this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnNet?.setOnClickListener {
            presenter.getUserInfo()
        }
    }
    override fun setName(name: String) {
        tvName?.text = name
    }
    override fun setAge(age: String) {
        tvAge?.text = age
    }
    override fun setHeight(height: String) {
        tvHeight?.text = height
    }
    override fun setWeight(weight: String) {
        tvWeight?.text = weight
    }
}

五.MVI架構

1.概述

MVI架構是MVVM架構的升級版,由Model、View、Intent三部分組成:

  • Model:負責儲存檢視的資料和狀態。
  • View:負責介面的展示。
  • Intent:負責封裝與傳送使用者的操作。

MVI架構在MVVM架構的基礎上強調資料的單向流動與狀態的集中管理,保證資料的唯一性。在MVVM架構中三者的呼叫關係如下圖所示:

在MVVM架構中,View會呼叫ViewModel執行具體的邏輯操作。而在MVI架構中,使用者對View層的互動會被抽象成Intent。當互動發生時,View會傳送對應的Intent到ViewModel中處理,通過Intent隔離View對ViewModel的呼叫。如下圖所示:

在MVVM架構中,ViewModel內部對View的資料與屬性的維護是分散的,具體表現為每個View都會有一個Data。而在MVI架構中,這些Data將會被整合在一起,形成統一的State,如下圖所示:

在MVI架構中,Model層指的就是State。通過引入State,可以更加方便ViewModel的管理。同時隔離ViewModel對View的呼叫,如下圖所示:

這樣,原本在MVVM架構中由於雙向繫結引起的程式碼耦合問題,在MVI架構中通過引入Intent與State得以解決。同時,架構整體的資料流向更加清晰,如下圖所示:

在MVI架構中,通過狀態集中管理減少了模版程式碼。通過資料單向流動保證了資料流向的一致性,開發者可以更方便的對狀態進行跟蹤與偵錯。MVI也存在著一些問題,由於狀態集中管理,當頁面功能複雜時,State易膨脹。每次更新狀態時,都需要更新整體的狀態,對記憶體有一定開銷,而且不支援區域性重新整理。

2.例子

點選按鈕從網路獲取使用者資訊,經過解析後展示到介面上。

1)Model層

data class PageState(
    val response: PageResponse = PageResponse.Default,
    val userInfo: UserInfo = UserInfo()
)
sealed class PageResponse {
    class Success<T>(data: T) : PageResponse()
    class Fail(e: Throwable) : PageResponse()
    object Loading : PageResponse()
    object Default : PageResponse()
}
data class UserInfo(
    val name: String = "",
    val age: Int = 0,
    val height: Int = 0,
    val weight: Int = 0
)

2)Intent層

sealed class UserIntent {
    object GetUserInfo : UserIntent()
    object Default : UserIntent()
}

3)ViewModel層

class NetViewModel : ViewModel() {
    private val model = NetRepository()
    private val state = MutableStateFlow(PageState())
    private val intent = MutableStateFlow<UserIntent>(UserIntent.Default)
    init {
        handleUserIntent()
    }
    fun state() =
        state.asStateFlow()
    fun sendIntent(userIntent: UserIntent) =
        viewModelScope.launch { intent.emit(userIntent) }
    private fun handleUserIntent() =
        intent.onEach {
            when (it) {
                is UserIntent.GetUserInfo -> getUserInfo()
                else -> {}
            }
        }.launchIn(viewModelScope)
    private fun getUserInfo() {
        state.value = state.value.copy(response = PageResponse.Loading)
        model.getUserInfo(object : NetResponse<UserInfo> {
            override fun onSuccess(data: UserInfo) {
                state.value =
                    state.value.copy(response = PageResponse.Success(data), userInfo = data)
            }
            override fun onError(e: Throwable) {
                state.value =
                    state.value.copy(response = PageResponse.Fail(e), userInfo = getNetErrorData())
            }
        })
    }
    private fun getNetErrorData(): UserInfo {
        return UserInfo(name = "", age = 0, height = 0, weight = 0)
    }
}
interface NetResponse<T> {
    fun onSuccess(data: T)
    fun onError(e: Throwable)
}
class NetRepository {
    fun getUserInfo(callback: NetResponse<UserInfo>) {
        // 模擬網路獲取
        val dataStr = getStringFromNet()
        val info = dataStr.parseJson<UserInfo>()
        return callback.onSuccess(info)
    }
}

4)View層

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: NetViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(NetViewModel::class.java)
        viewModel.state().onEach {
            when (it.response) {
                is PageResponse.Success<*> -> {
                    tvName?.text = it.response
                    tvAge?.text = it.userInfo.age.toString()
                    tvHeight?.text = it.userInfo.height.toString()
                    tvWeight?.text = it.userInfo.weight.toString()
                }
                else -> {}
            }
        }.launchIn(lifecycleScope)
        btnNet?.setOnClickListener {
            viewModel.sendIntent(UserIntent.GetUserInfo)
        }
    }
}

六.總結

1.從MVC架構到MVI架構

開發者們在早期就意識到資料邏輯、業務邏輯、渲染邏輯要分離,因此引入了MVC架構。但由於Android頁面元件的特殊性,導致在實際使用中MVC架構的資料邏輯、業務邏輯與渲染邏輯並沒有完美分離,因此產生了MVP架構。MVP架構雖然解決了渲染邏輯與資料邏輯的隔離問題,但由於Presenter層需要與View層和Model層雙向通訊,導致承載業務邏輯的Presenter層過於臃腫,因此引入了MVVM架構。MVVM架構通過繫結的通訊方式,解決了業務邏輯層由於層級間通訊而過於臃腫的問題。隨著時間推移,繫結框架也在不斷髮展:從DataBinding到LiveData,再從LiveData到StateFlow。由於當介面UI複雜時,MVVM架構的資料過於分散不易維護,因此產生了MVI架構。MVI架構引入了虛擬的“使用者層”,框架的資料流動從“使用者層”開始,到“使用者層”結束,單方向流動。

2.從clean code到clean coder

縱觀Android架構的發展歷程,資料邏輯、業務邏輯與渲染邏輯的分離關注點正在從程式碼逐漸轉移到使用者,這是一種由“物”向“人”的轉變。

這種轉變在Clean架構也中有所體現。Clean架構的提出者Bob大叔早年間一直致力於保持程式碼的整潔與條理清晰,於是出版了《Clean Code》。後來,經過探索研究發現,想要保持程式碼的“乾淨”,最重要的還是要提升開發者的自身素質,於是誕生了《 The Clean Coder》。Clean架構通過複雜細緻的分層設計,規範每個層級的功能,通過依賴關係保證了程式碼的整潔。但每層內程式碼的整潔,仍然需要開發者通過程式碼素養來保證。只有保持每一層程式碼的整潔,整體程式碼才能整潔。

3.MVI架構之後

谷歌官方最早推薦開發者使用MVVM架構,在MVI架構推出後,轉而推薦使用MVI架構。那麼,在MVI架構之後的下一個主流框架又將是什麼呢?這一點恐怕無人知曉。但通過分析可以發現,谷歌官方推薦使用MVI架構,本質上是在推薦狀態集中管理和單向資料流動的開發思想,而這一開發思想正好是響應式程式設計的基礎。因此可以合理預測下一個主流架構可能就是某個響應式框架。谷歌官方這幾年也一直在開發響應式開發框架Compose,但Compose框架在未來能否取代MVI框架仍然是個未知數。

到此這篇關於Android架構發展進化詳解的文章就介紹到這了,更多相關Android架構內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com