Android開發(fā)架構(gòu)
如果開發(fā)過程中大家各自為戰(zhàn)厦幅,沒有統(tǒng)一規(guī)范,久而久之施流,項(xiàng)目代碼會(huì)變得混亂且后續(xù)難以維護(hù)响疚。當(dāng)使用統(tǒng)一的架構(gòu)模式后,有很多的好處瞪醋,如:
- 統(tǒng)一開發(fā)規(guī)范忿晕,使得代碼整潔、規(guī)范银受,后續(xù)易于維護(hù)及擴(kuò)展
- 提高開發(fā)效率(尤其在團(tuán)隊(duì)人員較多時(shí))
- 模塊單一職責(zé)践盼,使得模塊專注自己內(nèi)部(面向?qū)ο?,模塊間解耦
總之宾巍,開發(fā)架構(gòu)是前人總結(jié)出來的一套行之有效的開發(fā)模式咕幻,目的是達(dá)到高內(nèi)聚,低耦合的效果顶霞,使得項(xiàng)目代碼更健壯肄程、易維護(hù)。
Android中常見的架構(gòu)模式有MVC(Model-View-Controller)
选浑、MVP(Model-View-Presenter)
蓝厌、MVVM(Model-View-ViewModel)
,一起來看下各自的特點(diǎn):
MVC
MVC(Model-View-Controller)
是比較早期的架構(gòu)模式古徒,模式整體也比較簡單拓提。
MVC模式將程序分成了三個(gè)部分:
- Model模型層:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
- View視圖層:頁面視圖(通過XML布局編寫視圖層)隧膘,負(fù)責(zé)接收用戶輸入代态、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
- Controller控制器層:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯
MVC特點(diǎn):
-
簡單易用:上圖表述了數(shù)據(jù)整個(gè)流程:
View
接收用戶操作疹吃,通過Controller
去處理業(yè)務(wù)邏輯蹦疑,并通過Model
去獲取/更新數(shù)據(jù),然后Model層
又將最新的數(shù)據(jù)傳回View
層進(jìn)行頁面展示互墓。 - 架構(gòu)簡單的另一面往往是對應(yīng)的副作用:由于XML布局能力弱必尼,我們的View層的很多操作都是寫在Activity/Fragment中,同時(shí),Controller判莉、Model層的代碼也大都寫在Activity/Fragment中豆挽,這就會(huì)導(dǎo)致一個(gè)問題,當(dāng)業(yè)務(wù)邏輯比較復(fù)雜時(shí)券盅,Activity/Fragment中的代碼量會(huì)很大帮哈,其違背了類單一職責(zé),不利于后續(xù)擴(kuò)展及維護(hù)锰镀。尤其是后期你剛接手的項(xiàng)目娘侍,一個(gè)Activity/Fragment類中的代碼動(dòng)輒上千行代碼,那感覺著實(shí)酸爽:當(dāng)然泳炉,如果業(yè)務(wù)很簡單憾筏,使用MVC模式還是一種不錯(cuò)的選擇。
MVP
MVP(Model-View-Presenter)
花鹅,架構(gòu)圖如下:
MVP各模塊職責(zé)如下:
- Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)氧腰、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
- View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入刨肃、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
- Presenter:M與V之間的橋梁古拴,負(fù)責(zé)業(yè)務(wù)邏輯
MVP特點(diǎn): View
層接收用戶操作,并通過持有的Presenter
去處理業(yè)務(wù)邏輯真友,請求數(shù)據(jù)黄痪;接著Presenter
層通過Model
去獲取數(shù)據(jù),然后Model
又將最新的數(shù)據(jù)傳回Presenter
層盔然,Presenter
層又持有View
層的引用桅打,進(jìn)而將數(shù)據(jù)傳給View
層進(jìn)行展示。
MVP相比MVC的幾處變化:
- View層與Model層不再交互愈案,而是通過Presenter去進(jìn)行聯(lián)系
- 本質(zhì)上MVP是面向接口編程油额,Model/View/Presenter每層的職責(zé)分工明確,當(dāng)業(yè)務(wù)復(fù)雜時(shí)刻帚,整個(gè)流程邏輯也是很清晰的
當(dāng)然,MVP
也不是十全十美的涩嚣,MVP
本身也存在以下問題:
-
View
層會(huì)抽象成IView
接口崇众,并在IView
中聲明一些列View
相關(guān)的方法;同樣的航厚,Presenter
會(huì)被抽象成IPresenter
接口及其一些列方法顷歌,每當(dāng)實(shí)現(xiàn)一個(gè)功能時(shí),都需要編寫多個(gè)接口及其對應(yīng)的方法幔睬,實(shí)現(xiàn)起來相對比較繁瑣眯漩,而且每次有改動(dòng)時(shí),對應(yīng)的接口方法也基本都會(huì)再去改動(dòng)。 -
View
層與Presenter
層相互持有赦抖,當(dāng)View
層關(guān)閉時(shí)舱卡,由于Presenter
層不是生命周期感知的,可能會(huì)導(dǎo)致內(nèi)存泄漏甚至是崩潰队萤。
ps:如果你的項(xiàng)目中使用了RxJava
轮锥,可以使用 AutoDispose 自動(dòng)解綁。
MVVM
MVVM(Model-View-ViewModel)
要尔,架構(gòu)圖如下:
MVVM各職責(zé)如下:
- Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)舍杜、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
- View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入赵辕、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
- ViewModel:M與V之間的橋梁既绩,負(fù)責(zé)業(yè)務(wù)邏輯
MVVM特點(diǎn):
- View層接收用戶操作,并通過持有的ViewModel去處理業(yè)務(wù)邏輯还惠,請求數(shù)據(jù)饲握;
- ViewModel層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回ViewModel層吸重,到這里互拾,ViewModel與Presenter所做的事基本是一樣的。但是ViewModel不會(huì)也不能持有View層的引用嚎幸,而是View層會(huì)通過觀察者模式監(jiān)聽ViewModel層的數(shù)據(jù)變化颜矿,當(dāng)有新數(shù)據(jù)時(shí),View層能自動(dòng)收到新數(shù)據(jù)并刷新界面嫉晶。
UI驅(qū)動(dòng) vs 數(shù)據(jù)驅(qū)動(dòng)
MVP
中骑疆,Presenter
中需要持有View
層的引用,當(dāng)數(shù)據(jù)變化時(shí)替废,需要主動(dòng)調(diào)用View
層對應(yīng)的方法將數(shù)據(jù)傳過去并進(jìn)行UI刷新箍铭,這種可以認(rèn)為是UI驅(qū)動(dòng);而MVVM
中椎镣,ViewModel
并不會(huì)持有View
層的引用诈火,View
層會(huì)監(jiān)聽數(shù)據(jù)變化,當(dāng)ViewModel
中有數(shù)據(jù)更新時(shí)状答,View
層能直接拿到新數(shù)據(jù)并完成UI更新冷守,這種可以認(rèn)為是數(shù)據(jù)驅(qū)動(dòng),顯然惊科,MVVM
相比于MVP
來說更加解耦拍摇。
MVVM的具體實(shí)現(xiàn)
上面介紹了MVC/MVP/MVVM
的各自特點(diǎn),其中MVC/MVP
的具體使用方式馆截,本文不再展開實(shí)現(xiàn)充活,接下來主要聊一下MVVM
的使用及封裝蜂莉,MVVM
也是官方推薦的架構(gòu)模式。
Jetpack MVVM
Jetpack
是官方推出的一系列組件庫混卵,使用組件庫開發(fā)有很多好處映穗,如:
- 遵循最佳做法:采用最新的設(shè)計(jì)方法構(gòu)建,具有向后兼容性淮菠,可以減少崩潰和內(nèi)存泄漏
- 消除樣板代碼:開發(fā)者可以更好地專注業(yè)務(wù)邏輯
- 減少不一致:可以在各種Android版本中運(yùn)行男公,兼容性更好。
為了實(shí)現(xiàn)上面的MVVM
架構(gòu)模式合陵,Jetpack
提供了多個(gè)組件來實(shí)現(xiàn)枢赔,具體來說有Lifecycle、LiveData拥知、ViewModel(這里的ViewModel是MVVM中ViewModel層的具體實(shí)現(xiàn))
踏拜,其中Lifecycle
負(fù)責(zé)生命周期相關(guān);LiveData
賦予類可觀察低剔,同時(shí)還是生命周期感知的(內(nèi)部使用了Lifecycle
)速梗;ViewModel
旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù),針對這幾個(gè)庫的詳細(xì)介紹及使用方式就不再展開了襟齿。
通過這幾個(gè)庫姻锁,就可以實(shí)現(xiàn)MVVM
了,官方也發(fā)布了MVVM
的架構(gòu)圖:
其中Activity/Fragment
為View
層猜欺,ViewModel+LiveData
為ViewModel
層位隶,為了統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)及本地?cái)?shù)據(jù)數(shù)據(jù),又引入了Repository
中間管理層开皿,本質(zhì)上是為了更好地管理數(shù)據(jù)涧黄,為了簡單把他們統(tǒng)稱為Model
層吧。
使用舉例
- View層代碼:
//MvvmExampleActivity.kt
class MvvmExampleActivity : BaseActivity() {
private val mTvContent: TextView by id(R.id.tv_content)
private val mBtnQuest: Button by id(R.id.btn_request)
private val mToolBar: Toolbar by id(R.id.toolbar)
override fun getLayoutId(): Int {
return R.layout.activity_wan_android
}
override fun initViews() {
initToolBar(mToolBar, "Jetpack MVVM", true)
}
override fun init() {
//獲取ViewModel實(shí)例赋荆,注意這里不能直接new笋妥,因?yàn)閂iewModel的生命周期比Activity長
mViewModel = ViewModelProvider(this).get(WanViewModel::class.java)
mBtnQuest.setOnClickListener {
//請求數(shù)據(jù)
mViewModel.getWanInfo()
}
//ViewModel中的LiveData注冊觀察者并監(jiān)聽數(shù)據(jù)變化
mViewModel.mWanLiveData.observe(this) { list ->
val builder = StringBuilder()
for (index in list.indices) {
//每條數(shù)據(jù)進(jìn)行折行顯示
if (index != list.size - 1) {
builder.append(list[index])
builder.append("\n\n")
} else {
builder.append(list[index])
}
}
mTvContent.text = builder.toString()
}
}
}
- ViewModel層代碼:
//WanViewModel.kt
class WanViewModel : ViewModel() {
//LiveData
val mWanLiveData = MutableLiveData<List<WanModel>>()
//loading
val loadingLiveData = SingleLiveData<Boolean>()
//異常
val errorLiveData = SingleLiveData<String>()
//Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
private val mWanRepo = WanRepository()
fun getWanInfo(wanId: String = "") {
//展示Loading
loadingLiveData.postValue(true)
viewModelScope.launch(Dispatchers.IO) {
try {
val result = mWanRepo.requestWanData(wanId)
when (result.state) {
State.Success -> mWanLiveData.postValue(result.data)
State.Error -> errorLiveData.postValue(result.msg)
}
} catch (e: Exception) {
error(e.message ?: "")
} finally {
loadingLiveData.postValue(false)
}
}
}
}
- Repository層(Model層)代碼:
class WanRepository {
//請求網(wǎng)絡(luò)數(shù)據(jù)
suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
val service = RetrofitUtil.getService(DrinkService::class.java)
val baseData = service.getBanner()
if (baseData.code == 0) {
//正確
baseData.state = State.Success
} else {
//錯(cuò)誤
baseData.state = State.Error
}
return baseData
}
}
這里只通過Retrofit
請求了網(wǎng)絡(luò)數(shù)據(jù) 玩Android 開放API,如果需要添加本地?cái)?shù)據(jù)窄潭,只需要在方法里添加本地?cái)?shù)據(jù)處理即可春宣,即 Repository是數(shù)據(jù)的管理中間層,對數(shù)據(jù)進(jìn)行統(tǒng)一管理嫉你,ViewModel層中不需要關(guān)心數(shù)據(jù)的來源信认,大家各司其職即可,符合單一職責(zé)均抽,代碼可讀性更好,同時(shí)也更加解耦其掂。在View
層點(diǎn)擊按鈕請求數(shù)據(jù)油挥,執(zhí)行結(jié)果如下:
以上就完成了一次網(wǎng)絡(luò)請求,相比于MVP
,MVVM
既不用聲明多個(gè)接口及方法深寥,同時(shí)ViewModel
也不會(huì)像Presenter
那樣去持有View
層的引用攘乒,而是生命周期感知的,MVVM
方式更加解耦惋鹅。
封裝
上一節(jié)介紹了Jetpack MVVM的使用例子则酝,可以看到有一些代碼邏輯是可以抽離出來封裝到公共部分的,那么本節(jié)就嘗試對其做一次封裝闰集。
首先沽讹,請求數(shù)據(jù)時(shí)可能會(huì)展示Loading
,請求完后可能是空數(shù)據(jù)武鲁、錯(cuò)誤數(shù)據(jù)爽雄,對應(yīng)下面的IStatusView
接口聲明:
interface IStatusView {
fun showEmptyView() //空視圖
fun showErrorView(errMsg: String) //錯(cuò)誤視圖
fun showLoadingView(isShow: Boolean) //展示Loading視圖
}
因?yàn)閂iewModel是在Activity中初始化的,所以可以封裝成一個(gè)Base類:
abstract class BaseMvvmActivity<VM : BaseViewModel> : BaseActivity(), IStatusView {
protected lateinit var mViewModel: VM
protected lateinit var mView: View
private lateinit var mLoadingDialog: LoadingDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mLoadingDialog = LoadingDialog(this, false)
mViewModel = getViewModel()!!
init()
registerEvent()
}
/**
* 獲取ViewModel 子類可以復(fù)寫沐鼠,自行初始化
*/
protected open fun getViewModel(): VM? {
//當(dāng)前對象超類的Type
val type = javaClass.genericSuperclass
//ParameterizedType表示參數(shù)化的類型
if (type != null && type is ParameterizedType) {
//返回此類型實(shí)際類型參數(shù)的Type對象數(shù)組
val actualTypeArguments = type.actualTypeArguments
val tClass = actualTypeArguments[0]
return ViewModelProvider(this).get(tClass as Class<VM>)
}
return null
}
override fun showLoadingView(isShow: Boolean) {
if (isShow) {
mLoadingDialog.showDialog(this, false)
} else {
mLoadingDialog.dismissDialog()
}
}
override fun showEmptyView() {
......
}
//錯(cuò)誤視圖 并且可以重試
override fun showErrorView(errMsg: String) {
.......
}
private fun registerEvent() {
//接收錯(cuò)誤信息
mViewModel.errorLiveData.observe(this) { errMsg ->
showErrorView(errMsg)
}
//接收Loading信息
mViewModel.loadingLiveData.observe(this, { isShow ->
showLoadingView(isShow)
})
}
abstract fun init()
}
Base
類中初始化ViewModel
挚瘟,還可以通過官方activity-ktx
、fragment-ktx
擴(kuò)展庫饲梭,初始化方式:val model: VM by viewModels()
乘盖。
子類中繼承如下:
class MvvmExampleActivity : BaseMvvmActivity<WanViewModel>() {
private val mTvContent: TextView by id(R.id.tv_content)
private val mBtnQuest: Button by id(R.id.btn_request)
private val mToolBar: Toolbar by id(R.id.toolbar)
override fun getLayoutId(): Int {
return R.layout.activity_wan_android
}
override fun initViews() {
initToolBar(mToolBar, "Jetpack MVVM", true)
}
override fun init() {
mBtnQuest.setOnClickListener {
//請求數(shù)據(jù)
mViewModel.getWanInfo()
}
/**
* 這里使用了擴(kuò)展函數(shù),等同于mViewModel.mWanLiveData.observe(this) {}
*/
observe(mViewModel.mWanLiveData) { list ->
val builder = StringBuilder()
for (index in list.indices) {
//每條數(shù)據(jù)進(jìn)行折行顯示
if (index != list.size - 1) {
builder.append(list[index])
builder.append("\n\n")
} else {
builder.append(list[index])
}
}
mTvContent.text = builder.toString()
}
}
}
我們把ViewModel的初始化放到了父類里進(jìn)行憔涉,代碼看上去更簡單了订框。監(jiān)聽數(shù)據(jù)變化mViewModel.mWanLiveData.observe(this) {}
方式改成observe(mViewModel.mWanLiveData) {}
方式,少傳了一個(gè)LifecycleOwner监氢,其實(shí)這是一個(gè)擴(kuò)展函數(shù)布蔗,如下:
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (t: T) -> Unit) {
liveData.observe(this, { observer(it) })
}
ps:我們初始化View控件時(shí),如 private val mBtnQuest: Button by id(R.id.btn_request)
浪腐,依然使用了擴(kuò)展函數(shù)纵揍,如下:
fun <T : View> Activity.id(id: Int) = lazy {
findViewById<T>(id)
}
不用像寫java代碼中那樣時(shí)刻要想著判空,同時(shí)只會(huì)在使用時(shí)才會(huì)進(jìn)行初始化议街,很實(shí)用泽谨!
說回來,接著是ViewModel層的封裝特漩,BaseViewModel.kt
:
abstract class BaseViewModel : ViewModel() {
//loading
val loadingLiveData = SingleLiveData<Boolean>()
//異常
val errorLiveData = SingleLiveData<String>()
/**
* @param request 正常邏輯
* @param error 異常處理
* @param showLoading 請求網(wǎng)絡(luò)時(shí)是否展示Loading
*/
fun launchRequest(
showLoading: Boolean = true,
error: suspend (String) -> Unit = { errMsg ->
//默認(rèn)異常處理吧雹,子類可以進(jìn)行覆寫
errorLiveData.postValue(errMsg)
}, request: suspend () -> Unit
) {
//是否展示Loading
if (showLoading) {
loadStart()
}
//使用viewModelScope.launch開啟協(xié)程
viewModelScope.launch(Dispatchers.IO) {
try {
request()
} catch (e: Exception) {
error(e.message ?: "")
} finally {
if (showLoading) {
loadFinish()
}
}
}
}
private fun loadStart() {
loadingLiveData.postValue(true)
}
private fun loadFinish() {
loadingLiveData.postValue(false)
}
}
擴(kuò)展一下: 1、上面執(zhí)行網(wǎng)絡(luò)請求時(shí)涂身,使用viewModelScope.launch來啟動(dòng)協(xié)程雄卷,引入方式:
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
這樣就可以直接在ViewModel中啟動(dòng)協(xié)程并且當(dāng)ViewModel生命周期結(jié)束時(shí)協(xié)程也會(huì)自動(dòng)關(guān)閉,避免使用GlobalScope.launch { }
或MainScope().launch { }
還需自行關(guān)閉協(xié)程蛤售, 當(dāng)然丁鹉,如果是在Activity/Fragment妒潭、liveData中使用協(xié)程,也可以按需引入:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
具體可以參見官方的 將 Kotlin 協(xié)程與生命周期感知型組件一起使用 這篇文章揣钦。
2雳灾、另外細(xì)心的讀者可能觀察到,上面我們的Loading冯凹、Error信息監(jiān)聽都是用的SingleLiveData谎亩,把這個(gè)類打代碼貼一下:
/**
* 多個(gè)觀察者存在時(shí),只有一個(gè)Observer能夠收到數(shù)據(jù)更新
* https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
*/
class SingleLiveData<T> : MutableLiveData<T>() {
companion object {
private const val TAG = "SingleLiveEvent"
}
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner) { t ->
//如果expect為true宇姚,那么將值update為false匈庭,方法整體返回true,
//即當(dāng)前Observer能夠收到更新空凸,后面如果還有訂閱者嚎花,不能再收到更新通知了
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
override fun setValue(@Nullable value: T?) {
//AtomicBoolean中設(shè)置的值設(shè)置為true
mPending.set(true)
super.setValue(value)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}
可以看到SingleLiveData還是繼承自MutableLiveData,區(qū)別是當(dāng)多個(gè)觀察者存在時(shí)呀洲,只有一個(gè)Observer能夠收到數(shù)據(jù)更新紊选,本質(zhì)上是在observe()時(shí)通過CAS加了限制,注釋已經(jīng)很詳細(xì)了道逗,不再贅述兵罢。
子類中繼承如下:
class WanViewModel : BaseViewModel() {
//LiveData
val mWanLiveData = MutableLiveData<List<WanModel>>()
//Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
private val mWanRepo = WanRepository()
fun getWanInfo(wanId: String = "") {
launchRequest {
val result = mWanRepo.requestWanData(wanId)
when (result.state) {
State.Success -> mWanLiveData.postValue(result.data)
State.Error -> errorLiveData.postValue(result.msg)
}
}
}
}
最后是對Model層的封裝,BaseRepository.kt
:
open class BaseRepository {
suspend fun <T : Any> executeRequest(
block: suspend () -> BaseData<T>
): BaseData<T> {
val baseData = block.invoke()
if (baseData.code == 0) {
//正確
baseData.state = State.Success
} else {
//錯(cuò)誤
baseData.state = State.Error
}
return baseData
}
}
數(shù)據(jù)基類BaseData.kt
:
class BaseData<T> {
@SerializedName("errorCode")
var code = -1
@SerializedName("errorMsg")
var msg: String? = null
var data: T? = null
var state: State = State.Error
}
enum class State {
Success, Error
}
子類中繼承如下:
class WanRepository : BaseRepository() {
suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
val service = RetrofitUtil.getService(DrinkService::class.java)
return executeRequest {
service.getBanner()
}
}
}