Android MVVM实现

作者: 盛世光阴 | 来源:发表于2021-05-24 19:06 被阅读0次

    版权归作者所有,转发请注明出处:https://www.jianshu.com/p/a945c992a494

    前言

    开发需要多思多想,万万不可做代码的搬运工,应用程序不仅应该满足UI、UX需求,还需要易于理解,易于维护,方便测试,拥有更多的灵活性,尽量兼顾多种场景,少留隐患

    mvvm.png

    1.MVVM

    我们有很多常用的架构,比如MVC,MVP,MVVM等,先简单的介绍Android平台下这几种架构以及区别

    MVC

    Model: 数据模型以及各种数据类
    View: 所展示的视图,布局文件以及View
    Controller: 控制器ActivityFragment等,在其中控制各种View与数据的绑定逻辑以及事件处理以及与Android系统的交互等
    优点: 简单,开发时基本不需要思考,可以快速的开发编写出所要的效果
    缺点: Controller代码量庞大,所有业务都在其中,后期维护成本较大,与系统交互的代码在其中,业务逻辑在其中,与视图绑定交互的业务也在其中

    MVP

    Model: 数据模型以及各种数据类
    View: 所展示的视图,布局文件以及View,以及提供UI接口
    Presenter: 负责完成ViewModel间交互的业务逻辑,主要有两个部分,操作UI以及业务逻辑进行数据操作
    优点: Activity只是处理View的加载以及实现UI Interface 进行View的更新,以及与系统进行交互比如事件绑定等,业务逻辑会抽离出来去到Presenter层,降低了Controller层的复杂程度
    缺点: 对View的依赖程度相对较大,View层的变更会影响ActivityPresenter以及UI接口,并且当业务程度相对很复杂的时候也会导致Presenter层相对复杂,代码量巨大

    MVVM

    Model: 数据模型以及各种数据类
    View: XMLViewActivity/Fragment,指责为View的绘制以及与Android系统层进行交互和通信
    ViewModel: 拥有视图绑定器,充当ModelView的双向绑定,以及业务逻辑
    优点: ViewModel只会持有数据绑定起以及业务逻辑,不会持有UI组件,UI的处理只会在View层,在MVP中Presenter不仅要处理业务逻辑还需要持有UI Interface负责UI的控制,ViewModel其中的业务逻辑部分由于没有UI的耦合很容易进行单元测试和修改
    缺点: 前期需要多项准备以搭建完备的MVVM架构,并且由于需要使用到ViewModelLiveDataDataBinding等技术需要知识储备,并且由于我们是使用DataBinding,以及ViewModelLiveData自动生成去进行UI和数据的绑定,如果出现异常相对不好排查

    2.代码的分层

    mike_mvvm.png

    根据Clean Architecture的设计方式,外圈依赖内圈,我们可以将代码分为以下模块

    UI模块: 负责负责View的绘制,以及与Android系统的交互,UI层依赖于ViewModel层持有ViewModel的对象

    ViewModel模块: 负责持有数据绑定以及UsecaseViewModel不会依赖于UI模块,这意味着它不应该持有View对象或者上下文对象

    Usecase模块: 只负责核心业务模块,它依赖于Repository模块

    Repository: 存储区模块会处理数据操作。它们会提供一个干净的 API,以便应用的其余部分可以轻松检索该数据。数据更新时,它们知道从何处获取数据以及进行哪些 API 调用。您可以将存储区视为不同数据源(如持久性模型、网络服务和缓存)之间的媒介,它只负责数据的获取以及刷新,不关心数据如何使用

    3.MVVM的数据流

    mike_mvvm_data_flow.png

    当我们点击按钮事件会在UI层触发,然后UI层会调用ViewModel去进行通信,ViewModel会调用Usecase以获取对应的结果然后更新ViewModel中的LiveData,在Usecase中会与Repository进行交互获取数据,Repository会与对应的WebService交互以获取对应的数据

    4.MVVM的实现所需要的组件

    • Dagger2: 依赖注入框架,基于上述的结构,UI需要依赖ViewModelViewModel需要依赖UsecaseUsecase需要依赖RepositoryRepository需要依赖WebService或其他数据源,可以使用Dagger去管理依赖关系
    • RxJava2/RxAndroid: 使用观察者模式的异步框架,由于层级之间在依赖的基础上需要进行通信,上层可以使用观察者的方式拿到并处理下层的数据流,也可以给予观察数据流编写对应的单元测试
    • Okhttp3/Retrofit2: Android端主流网络框架,使用Retrofit也可以返回一个可观察对象配合RxJava使用,使我们的业务链更清晰
    • ViewModel : 官方提供的类,以注重生命周期的方式存储和管理界面相关数据,ViewModel模块的主要职责就是负责数据绑定以及业务操作,View层会持有ViewModel对象从而监听数据的变化或者调用其中的Usecase,我们也可以将ViewModel中的数据绑定器直接绑定到xml视图中实现双向绑定

    5.MVVM的实现

    基于上述使用MVVM实现一个登录案例

    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配置

    创建全局的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()
        }
    
    }
    

    创建全局的Component,完成之后使用Android Studio中的Rebuild Project,生成对应的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
        }
    
    }
    

    创建Application,继承自DaggerApplication,并将其配置到AndroidManifest.xml

    class LoginMVVMApplication : DaggerApplication() {
        override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
            return DaggerAppComponent.builder().application(this).build()
        }
    }
    

    创建LoginActivity

    class LoginActivity : DaggerAppCompatActivity() {
    }
    

    创建Login package,配置LoginActivity的依赖,将LoginActivityBuilder在AppComponent中声明

    @Module
    abstract class LoginActivityBuilder {
    
        @ContributesAndroidInjector(modules = [LoginActivityModule::class, LoginViewModelModule::class])
        @ActivityScope
        abstract fun loginActivity(): LoginActivity
    }
    
    @Module
    class LoginActivityModule {
    
    }
    

    创建Entity

    data class UseInfo(val name: String)
    

    创建Repository相关

    创建ApiService

    interface ApiService {
        @POST("/api/login")
        fun login(): Single<UseInfo>
    }
    

    创建接口LoginRepo,以及实现类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,后续在UseCase中将会注入此依赖

    @Module
    class LoginActivityModule {
    
        @Provides
        @ActivityScope
        fun provideLoginRepo(loginRepoImpl: LoginRepoImpl): LoginRepo {
            return loginRepoImpl
        }
    }
    

    创建UseCase相关
    创建接口LoginUseCase,实现类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
        }
    }
    

    创建ViewModel相关
    创建ViewModelFactory,用于生成ViewModel示例,提供在Activity中生成ViewModel的函数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)
    }
    

    创建ViewModelKey

    @Target(
        AnnotationTarget.FUNCTION,
        AnnotationTarget.PROPERTY_GETTER,
        AnnotationTarget.PROPERTY_SETTER
    )
    @Retention(AnnotationRetention.RUNTIME)
    @MapKey
    internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
    

    创建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()
        }
    }
    

    创建LoginViewModelModule提供ViewModel依赖,并添加到LoginActivityBuilder中

    @Module
    abstract class LoginViewModelModule {
    
        @Binds
        @IntoMap
        @ViewModelKey(LoginViewModel::class)
        internal abstract fun bindLoginViewModel(viewModel: LoginViewModelImpl): ViewModel
    }
    

    完善LoginActivity代码以及布局文件
    暂时先不使用DataBinding,后续添加
    布局文件

    <?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代码,调用ViewModel业务,监听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()
            }
        }
    }
    

    代码结构:


    结构.PNG

    代码连接
    https://github.com/huangyiCode/android_mvvm

    待续
    使用DataBinding优化视图的绑定
    单元测试的执行

    欢迎关注Mike的简书

    Android 知识整理

    相关文章

      网友评论

        本文标题:Android MVVM实现

        本文链接:https://www.haomeiwen.com/subject/dkmwjltx.html