版權(quán)歸作者所有,轉(zhuǎn)發(fā)請(qǐng)注明出處:http://www.itdecent.cn/p/a945c992a494
前言
開發(fā)需要多思多想,萬萬不可做代碼的搬運(yùn)工,應(yīng)用程序不僅應(yīng)該滿足UI、UX需求,還需要易于理解,易于維護(hù),方便測(cè)試,擁有更多的靈活性,盡量兼顧多種場(chǎng)景,少留隱患

1.MVVM
我們有很多常用的架構(gòu),比如MVC,MVP,MVVM等,先簡(jiǎn)單的介紹Android平臺(tái)下這幾種架構(gòu)以及區(qū)別
MVC
Model: 數(shù)據(jù)模型以及各種數(shù)據(jù)類
View: 所展示的視圖,布局文件以及View
Controller: 控制器Activity,Fragment等,在其中控制各種View與數(shù)據(jù)的綁定邏輯以及事件處理以及與Android系統(tǒng)的交互等
優(yōu)點(diǎn): 簡(jiǎn)單,開發(fā)時(shí)基本不需要思考,可以快速的開發(fā)編寫出所要的效果
缺點(diǎn): Controller代碼量龐大,所有業(yè)務(wù)都在其中,后期維護(hù)成本較大,與系統(tǒng)交互的代碼在其中,業(yè)務(wù)邏輯在其中,與視圖綁定交互的業(yè)務(wù)也在其中
MVP
Model: 數(shù)據(jù)模型以及各種數(shù)據(jù)類
View: 所展示的視圖,布局文件以及View,以及提供UI接口
Presenter: 負(fù)責(zé)完成View與Model間交互的業(yè)務(wù)邏輯,主要有兩個(gè)部分,操作UI以及業(yè)務(wù)邏輯進(jìn)行數(shù)據(jù)操作
優(yōu)點(diǎn): Activity只是處理View的加載以及實(shí)現(xiàn)UI Interface 進(jìn)行View的更新,以及與系統(tǒng)進(jìn)行交互比如事件綁定等,業(yè)務(wù)邏輯會(huì)抽離出來去到Presenter層,降低了Controller層的復(fù)雜程度
缺點(diǎn): 對(duì)View的依賴程度相對(duì)較大,View層的變更會(huì)影響Activity,Presenter以及UI接口,并且當(dāng)業(yè)務(wù)程度相對(duì)很復(fù)雜的時(shí)候也會(huì)導(dǎo)致Presenter層相對(duì)復(fù)雜,代碼量巨大
MVVM
Model: 數(shù)據(jù)模型以及各種數(shù)據(jù)類
View: XML,View,Activity/Fragment,指責(zé)為View的繪制以及與Android系統(tǒng)層進(jìn)行交互和通信
ViewModel: 擁有視圖綁定器,充當(dāng)Model和View的雙向綁定,以及業(yè)務(wù)邏輯
優(yōu)點(diǎn): ViewModel只會(huì)持有數(shù)據(jù)綁定起以及業(yè)務(wù)邏輯,不會(huì)持有UI組件,UI的處理只會(huì)在View層,在MVP中Presenter不僅要處理業(yè)務(wù)邏輯還需要持有UI Interface負(fù)責(zé)UI的控制,ViewModel其中的業(yè)務(wù)邏輯部分由于沒有UI的耦合很容易進(jìn)行單元測(cè)試和修改
缺點(diǎn): 前期需要多項(xiàng)準(zhǔn)備以搭建完備的MVVM架構(gòu),并且由于需要使用到ViewModel,LiveData,DataBinding等技術(shù)需要知識(shí)儲(chǔ)備,并且由于我們是使用DataBinding,以及ViewModel,LiveData自動(dòng)生成去進(jìn)行UI和數(shù)據(jù)的綁定,如果出現(xiàn)異常相對(duì)不好排查
2.代碼的分層

根據(jù)Clean Architecture的設(shè)計(jì)方式,外圈依賴內(nèi)圈,我們可以將代碼分為以下模塊
UI模塊: 負(fù)責(zé)負(fù)責(zé)View的繪制,以及與Android系統(tǒng)的交互,UI層依賴于ViewModel層持有ViewModel的對(duì)象
ViewModel模塊: 負(fù)責(zé)持有數(shù)據(jù)綁定以及Usecase,ViewModel不會(huì)依賴于UI模塊,這意味著它不應(yīng)該持有View對(duì)象或者上下文對(duì)象
Usecase模塊: 只負(fù)責(zé)核心業(yè)務(wù)模塊,它依賴于Repository模塊
Repository: 存儲(chǔ)區(qū)模塊會(huì)處理數(shù)據(jù)操作。它們會(huì)提供一個(gè)干凈的 API,以便應(yīng)用的其余部分可以輕松檢索該數(shù)據(jù)。數(shù)據(jù)更新時(shí),它們知道從何處獲取數(shù)據(jù)以及進(jìn)行哪些 API 調(diào)用。您可以將存儲(chǔ)區(qū)視為不同數(shù)據(jù)源(如持久性模型、網(wǎng)絡(luò)服務(wù)和緩存)之間的媒介,它只負(fù)責(zé)數(shù)據(jù)的獲取以及刷新,不關(guān)心數(shù)據(jù)如何使用
3.MVVM的數(shù)據(jù)流

當(dāng)我們點(diǎn)擊按鈕事件會(huì)在UI層觸發(fā),然后UI層會(huì)調(diào)用ViewModel去進(jìn)行通信,ViewModel會(huì)調(diào)用Usecase以獲取對(duì)應(yīng)的結(jié)果然后更新ViewModel中的LiveData,在Usecase中會(huì)與Repository進(jìn)行交互獲取數(shù)據(jù),Repository會(huì)與對(duì)應(yīng)的WebService交互以獲取對(duì)應(yīng)的數(shù)據(jù)
4.MVVM的實(shí)現(xiàn)所需要的組件
- Dagger2: 依賴注入框架,基于上述的結(jié)構(gòu),UI需要依賴
ViewModel,ViewModel需要依賴Usecase,Usecase需要依賴Repository,Repository需要依賴WebService或其他數(shù)據(jù)源,可以使用Dagger去管理依賴關(guān)系 - RxJava2/RxAndroid: 使用觀察者模式的異步框架,由于層級(jí)之間在依賴的基礎(chǔ)上需要進(jìn)行通信,上層可以使用觀察者的方式拿到并處理下層的數(shù)據(jù)流,也可以給予觀察數(shù)據(jù)流編寫對(duì)應(yīng)的單元測(cè)試
- Okhttp3/Retrofit2:
Android端主流網(wǎng)絡(luò)框架,使用Retrofit也可以返回一個(gè)可觀察對(duì)象配合RxJava使用,使我們的業(yè)務(wù)鏈更清晰 - ViewModel : 官方提供的類,以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)數(shù)據(jù),
ViewModel模塊的主要職責(zé)就是負(fù)責(zé)數(shù)據(jù)綁定以及業(yè)務(wù)操作,View層會(huì)持有ViewModel對(duì)象從而監(jiān)聽數(shù)據(jù)的變化或者調(diào)用其中的Usecase,我們也可以將ViewModel中的數(shù)據(jù)綁定器直接綁定到xml視圖中實(shí)現(xiàn)雙向綁定
5.MVVM的實(shí)現(xiàn)
基于上述使用MVVM實(shí)現(xiàn)一個(gè)登錄案例

添加依賴
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//Dagger2
implementation 'com.google.dagger:dagger-android:2.35.1'
implementation 'com.google.dagger:dagger-android-support:2.35.1'
kapt 'com.google.dagger:dagger-android-processor:2.35.1'
kapt "com.google.dagger:dagger-compiler:2.35.1"
//OkHttp3
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
//RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
//lifecycle
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
在Module的Gradle文件中聲明
apply plugin: 'kotlin-kapt'
Dagger2配置
創(chuàng)建全局的NetWorkModule
@Module
class NetWorkModule {
@Provides
@Singleton
fun provideGson(): Gson {
return GsonBuilder().setLenient().create()
}
@Provides
fun provideOkHttpClient(): OkHttpClient {
val okHttpBuilder = OkHttpClient.Builder()
okHttpBuilder.addInterceptor(HttpLoggingInterceptor())
return okHttpBuilder.build()
}
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://www.test.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
}
}
創(chuàng)建全局的Component,完成之后使用Android Studio中的Rebuild Project,生成對(duì)應(yīng)的DaggerAppComponent
@Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
NetWorkModule::class]
)
interface AppComponent : AndroidInjector<DaggerApplication> {
@Component.Builder
interface Builder {
fun build(): AppComponent
@BindsInstance
fun application(application: Application): Builder
}
}
創(chuàng)建Application,繼承自DaggerApplication,并將其配置到AndroidManifest.xml
class LoginMVVMApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
創(chuàng)建LoginActivity
class LoginActivity : DaggerAppCompatActivity() {
}
創(chuàng)建Login package,配置LoginActivity的依賴,將LoginActivityBuilder在AppComponent中聲明
@Module
abstract class LoginActivityBuilder {
@ContributesAndroidInjector(modules = [LoginActivityModule::class, LoginViewModelModule::class])
@ActivityScope
abstract fun loginActivity(): LoginActivity
}
@Module
class LoginActivityModule {
}
創(chuàng)建Entity
data class UseInfo(val name: String)
創(chuàng)建Repository相關(guān)
創(chuàng)建ApiService
interface ApiService {
@POST("/api/login")
fun login(): Single<UseInfo>
}
創(chuàng)建接口LoginRepo,以及實(shí)現(xiàn)類LoginRepoImpl,將ApiService依賴注入其中
interface LoginRepo {
fun login(): Single<UseInfo>
}
class LoginRepoImpl @Inject constructor(private val apiService: ApiService) : LoginRepo {
override fun login(): Single<UseInfo> {
// return Single.create {
// Thread.sleep(3000)
// it.onSuccess(UseInfo("Mike"))
// }
return apiService.login()
}
}
將LoginRepo 添加到LoginActivityModule,后續(xù)在UseCase中將會(huì)注入此依賴
@Module
class LoginActivityModule {
@Provides
@ActivityScope
fun provideLoginRepo(loginRepoImpl: LoginRepoImpl): LoginRepo {
return loginRepoImpl
}
}
創(chuàng)建UseCase相關(guān)
創(chuàng)建接口LoginUseCase,實(shí)現(xiàn)類LoginUseCaseImpl注入依賴LoginRepo,并將其添加到LoginActivityModule
interface LoginUseCase {
fun login(): Single<UseInfo>
}
class LoginUseCaseImpl @Inject constructor(private val loginRepo: LoginRepo) : LoginUseCase {
override fun login(): Single<UseInfo> {
return loginRepo.login()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
}
}
@Module
class LoginActivityModule {
@Provides
@ActivityScope
fun provideLoginRepo(loginRepoImpl: LoginRepoImpl): LoginRepo {
return loginRepoImpl
}
@Provides
@ActivityScope
fun provideLoginUseCase(loginUseCaseImpl: LoginUseCaseImpl): LoginUseCase {
return loginUseCaseImpl
}
}
創(chuàng)建ViewModel相關(guān)
創(chuàng)建ViewModelFactory,用于生成ViewModel示例,提供在Activity中生成ViewModel的函數(shù)ViewModelProvider.Factory.obtainViewModel,將ViewModelFactory添加到LoginActivityModule
class ViewModelFactory @Inject constructor(var viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return viewModels[modelClass]?.get() as T
}
}
inline fun <reified VM : ViewModel> ViewModelProvider.Factory.obtainViewModel(activity: FragmentActivity): VM =
ViewModelProvider(activity, this)[VM::class.java]
@Provides
@ActivityScope
fun provideFactory(viewModels:MutableMap<Class<out ViewModel>, Provider<ViewModel>>): ViewModelProvider.Factory {
return ViewModelFactory(viewModels)
}
創(chuàng)建ViewModelKey
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
創(chuàng)建LoginViewModel以及LoginViewModelImpl
abstract class LoginViewModel : ViewModel() {
abstract val useCase: LoginUseCase
abstract val showProgress: MutableLiveData<Boolean>
abstract val userInfo: MutableLiveData<UseInfo>
abstract fun login()
}
class LoginViewModelImpl @Inject constructor(override val useCase: LoginUseCase) :
LoginViewModel() {
override val showProgress: MutableLiveData<Boolean> = MutableLiveData(false)
override val userInfo: MutableLiveData<UseInfo> = MutableLiveData()
private val compositeDisposable = CompositeDisposable()
override fun login() {
showProgress.postValue(true)
useCase.login()
.doAfterTerminate {
showProgress.postValue(false)
}
.subscribe({
userInfo.value = it
}, {
//TODO Error
}).also {
compositeDisposable.add(it)
}
}
override fun onCleared() {
compositeDisposable.clear()
}
}
創(chuàng)建LoginViewModelModule提供ViewModel依賴,并添加到LoginActivityBuilder中
@Module
abstract class LoginViewModelModule {
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
internal abstract fun bindLoginViewModel(viewModel: LoginViewModelImpl): ViewModel
}
完善LoginActivity代碼以及布局文件
暫時(shí)先不使用DataBinding,后續(xù)添加
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Login" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</RelativeLayout>
完善LoginActivity代碼,調(diào)用ViewModel業(yè)務(wù),監(jiān)聽LiveData刷新UI
class LoginActivity : DaggerAppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.atctivity_login)
loginViewModel = viewModelFactory.obtainViewModel(this)
loginViewModel.userInfo.observe(this, Observer {
Toast.makeText(this, "Login complete!", Toast.LENGTH_LONG).show()
})
val progress = findViewById<ProgressBar>(R.id.loading)
loginViewModel.showProgress.observe(this, Observer {
progress.visibility = if (it) View.VISIBLE else View.INVISIBLE
})
findViewById<Button>(R.id.btn_login).setOnClickListener {
loginViewModel.login()
}
}
}
代碼結(jié)構(gòu):
代碼連接
https://github.com/huangyiCode/android_mvvm
待續(xù)
使用DataBinding優(yōu)化視圖的綁定
單元測(cè)試的執(zhí)行
歡迎關(guān)注Mike的簡(jiǎn)書
Android 知識(shí)整理