Android MVVM實(shí)現(xiàn)

版權(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)景,少留隱患

mvvm.png

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é)完成ViewModel間交互的業(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,ViewActivity/Fragment,指責(zé)為View的繪制以及與Android系統(tǒng)層進(jìn)行交互和通信
ViewModel: 擁有視圖綁定器,充當(dāng)ModelView的雙向綁定,以及業(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,LiveDataDataBinding等技術(shù)需要知識(shí)儲(chǔ)備,并且由于我們是使用DataBinding,以及ViewModel,LiveData自動(dòng)生成去進(jìn)行UI和數(shù)據(jù)的綁定,如果出現(xiàn)異常相對(duì)不好排查

2.代碼的分層

mike_mvvm.png

根據(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ù)流

mike_mvvm_data_flow.png

當(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需要依賴ViewModelViewModel需要依賴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è)登錄案例

mike_mvvm_login.png

添加依賴

    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):


結(jié)構(gòu).PNG

代碼連接
https://github.com/huangyiCode/android_mvvm

待續(xù)
使用DataBinding優(yōu)化視圖的綁定
單元測(cè)試的執(zhí)行

歡迎關(guān)注Mike的簡(jiǎn)書

Android 知識(shí)整理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文發(fā)表于: Rocko's blog - MVVM_Android-CleanArchitecture 前言 "...
    Rocko閱讀 6,371評(píng)論 2 28
  • 前言 本文屬于《Android構(gòu)建MVVM》系列開篇,共六個(gè)篇章,詳見目錄樹該系列文章旨在為Android的開發(fā)者...
    xykjlcx閱讀 7,717評(píng)論 2 62
  • java基礎(chǔ)volidate、線程生命周期、反射、NIO 內(nèi)存分區(qū)GC、類加載 強(qiáng)弱等引用 基本數(shù)據(jù)結(jié)構(gòu) 線程池 ...
    RichardLee123閱讀 343評(píng)論 0 3
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,889評(píng)論 28 54
  • 信任包括信任自己和信任他人 很多時(shí)候,很多事情,失敗、遺憾、錯(cuò)過,源于不自信,不信任他人 覺得自己做不成,別人做不...
    吳氵晃閱讀 6,391評(píng)論 4 8

友情鏈接更多精彩內(nèi)容