版权归作者所有,转发请注明出处:https://www.jianshu.com/p/a945c992a494
前言
开发需要多思多想,万万不可做代码的搬运工,应用程序不仅应该满足UI、UX需求,还需要易于理解,易于维护,方便测试,拥有更多的灵活性,尽量兼顾多种场景,少留隐患
mvvm.png1.MVVM
我们有很多常用的架构,比如MVC,MVP,MVVM等,先简单的介绍Android平台下这几种架构以及区别
MVC
Model: 数据模型以及各种数据类
View: 所展示的视图,布局文件以及View
Controller: 控制器Activity
,Fragmen
t等,在其中控制各种View与数据的绑定逻辑以及事件处理以及与Android系统的交互等
优点: 简单,开发时基本不需要思考,可以快速的开发编写出所要的效果
缺点: Controller代码量庞大,所有业务都在其中,后期维护成本较大,与系统交互的代码在其中,业务逻辑在其中,与视图绑定交互的业务也在其中
MVP
Model: 数据模型以及各种数据类
View: 所展示的视图,布局文件以及View
,以及提供UI接口
Presenter: 负责完成View
与Model
间交互的业务逻辑,主要有两个部分,操作UI以及业务逻辑进行数据操作
优点: Activity
只是处理View
的加载以及实现UI Interface 进行View
的更新,以及与系统进行交互比如事件绑定等,业务逻辑会抽离出来去到Presenter
层,降低了Controller
层的复杂程度
缺点: 对View
的依赖程度相对较大,View
层的变更会影响Activity
,Presenter
以及UI接口,并且当业务程度相对很复杂的时候也会导致Presenter层相对复杂,代码量巨大
MVVM
Model: 数据模型以及各种数据类
View: XML
,View
,Activity
/Fragment
,指责为View的绘制以及与Android系统层进行交互和通信
ViewModel: 拥有视图绑定器,充当Model
和View
的双向绑定,以及业务逻辑
优点: ViewModel
只会持有数据绑定起以及业务逻辑,不会持有UI组件,UI的处理只会在View
层,在MVP中Presenter
不仅要处理业务逻辑还需要持有UI Interface负责UI的控制,ViewModel
其中的业务逻辑部分由于没有UI的耦合很容易进行单元测试和修改
缺点: 前期需要多项准备以搭建完备的MVVM架构,并且由于需要使用到ViewModel
,LiveData
,DataBinding
等技术需要知识储备,并且由于我们是使用DataBinding
,以及ViewModel
,LiveData
自动生成去进行UI和数据的绑定,如果出现异常相对不好排查
2.代码的分层
mike_mvvm.png根据Clean Architecture的设计方式,外圈依赖内圈,我们可以将代码分为以下模块
UI模块: 负责负责View
的绘制,以及与Android
系统的交互,UI层依赖于ViewModel
层持有ViewModel
的对象
ViewModel模块: 负责持有数据绑定以及Usecase
,ViewModel
不会依赖于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需要依赖
ViewModel
,ViewModel
需要依赖Usecase
,Usecase
需要依赖Repository
,Repository
需要依赖WebService
或其他数据源,可以使用Dagger
去管理依赖关系 - RxJava2/RxAndroid: 使用观察者模式的异步框架,由于层级之间在依赖的基础上需要进行通信,上层可以使用观察者的方式拿到并处理下层的数据流,也可以给予观察数据流编写对应的单元测试
- Okhttp3/Retrofit2:
Android
端主流网络框架,使用Retrofit
也可以返回一个可观察对象配合RxJava
使用,使我们的业务链更清晰 - ViewModel : 官方提供的类,以注重生命周期的方式存储和管理界面相关数据,
ViewModel
模块的主要职责就是负责数据绑定以及业务操作,View
层会持有ViewModel
对象从而监听数据的变化或者调用其中的Usecase
,我们也可以将ViewModel
中的数据绑定器直接绑定到xml视图中实现双向绑定
5.MVVM的实现
基于上述使用MVVM实现一个登录案例
添加依赖
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 知识整理
网友评论