MVC架构
View:Acitivity(View)、Fragment(View)视图,在android里xml布局转成View后,加载到了Activity/Fragment里了。
Controller:Controller对应着Activity/Fragment,绑定UI,处理各种业务。
Model:网络请求数据的获取,数据库存储,更新
1.View 接受用户的请求,然后将请求传递给 Controller
2.Controller 进行业务逻辑处理后,通知 Model去更新(调用Model的相关方法去进行网络请求或者数据库获取数据)
3.Model 数据更新后,通知 View 去更新界面显示(通过接口回调)
举个例子
Model层代码
class UserModel {/*** 进行登录操作*/private val api by lazy {API()}private val random:Random= Random()fun doLogin(callback: OnDoLoginStateChange, account: String, password: String){callback.onLoading()//开始去调用登录的API//api.login//有结果,此操作为耗时操作//0..1val randomValue = random.nextInt(2)if(randomValue==0){callback.onLoginSuccess()}else{callback.onLoginFailed()}}fun checkUserState(account: String,block:(Int)->Unit){//0表示该账号已经注册//1表示该账号没有注册block(random.nextInt(2))}interface OnDoLoginStateChange{fun onLoading()fun onLoginSuccess()fun onLoginFailed()}}
Controller层和View层代码
class MainActivity : AppCompatActivity(), UserModel.OnDoLoginStateChange {private val userModel by lazy{UserModel()}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)//设置点击事件initListener()}private fun initListener() {LoginBtn.setOnClickListener {//去进行登录toLogin()}}/*** 处理登录逻辑*/private fun toLogin() {//做登录的逻辑处理val account:String =accountInputBox.text.toString()val password:String=passwordInputBox.text.toString()//检测账号格式是否正确if(TextUtils.isEmpty(account)){//提示账号有问题return}//检查密码长度是否正确if(TextUtils.isEmpty(password)){//提示密码有问题return}//给密码加密userModel.checkUserState(account){when(it){0->{//可用,已经注册了//进行登录,此操作为异步userModel.doLogin(this,account,password)//禁止按钮可以点击,防止重复提交LoginBtn.isEnabled=false}1->{//表示没有注册,不可用//给出提示}}}}override fun onLoading() {loginTipsText.text="登录中..."}override fun onLoginSuccess() {loginTipsText.text="登录成功..."}override fun onLoginFailed() {loginTipsText.text="登录失败..."}
}
Controller和View都在MainActivity。
在MainActivity中的Controller部分toLogin()方法中,调用了userModel.doLogin方法,Controller是持有userModel的引用,所以Controller指向Model。
在调用userModel.doLogin方法的时候靠的是callback接口回调进行通知登录状态,所以Model层也依赖于Controller层的接口。
userModel.doLogin方法执行完后,我们还对LoginBt按钮状态进行设置,因此Controller持有View的引用,Controller指向View。
我们在给LoginBtn设置点击事件实现方法的时候,View也依赖于Controller中给的接口。
View并没有直接持有Model,是通过Controller来操作Model,所以View不依赖于Model。
Model可以通知我们的View进行更新,比如userModel.checkUserState()方法中,会通过接口回调重写的三个状态onLoading(),onLoginSuccess()和onLoginFailed()更新UI。
优点
view与model隔离,view换了,model不影响。model换其他的数据源,view层也不受影响。一个view可以连接多个model,有些model可以复用。比如说你这个页面需要用户信息,另外一个界面也需要用户信息。
缺点
这里我们主要指Android上的缺点,不适合在Android开发上使用。在Android开发中,View的相关内容和Controller都写到一起了,会让Activity/Fragment越来越臃肿。
在MVC中,Model层处理完数据后,直接通知View层更新,因此MVC耦合性强。
MVP架构
Presenter负责处理业务逻辑
View负责处理界面逻辑
Model负责处理数据操作
在MVP架构中,我们通常涉及到的内容是调用逻辑层的方法和更新UI
问题点就是:怎么调用逻辑层的方法呢?怎么通知UI更新呢?
View层持有Presenter层的引用或者通过管理类管理Presenter,总之View可以直接拿到Presenter,这样子,View就可以调用Presenter里的方法了,比如说Presenter层去给我获取这个页面的分类信息。
Presenter层如何去更新UI呢?
当View层去获取/创建Presenter的时候,把接口给到Presenter(因为View层实现了接口),比如说Presenter层获取到分类接口以后,通知接口回调到View层更新UI。
这样子,我们这个来回就完事了。Presenter层再通过Model层去拿数据。
为什么使用MVP架构,有什么好处?
- 分离了视图逻辑(View)和业务逻辑(Presenter),降低了耦合
- Activity只处理生命周期的任务,使得Activity中的代码变得更加简洁
- 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高了代码的可阅读性
- Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
- 把业务逻辑抽象到Presenter中去,而Presenter通常与Activity与Fragment的生命周期进行绑定。当Activity或Fragment销毁时,Presenter也会被销毁,从而释放资源。这样可以避免Presenter持有Activity或Fragment的引用而导致内存泄露。
MVP架构的整个核心流程:
View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。Presenter中同时持有View层的interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,通知Presenter层加载数据,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层在调用View层的接口将加载后的数据展示给用户。
Model层
/*** Model层进行数据处理并回调返回给View进行数据更新*/
class UserModel {companion object{const val STATE_LOGIN_LOADING=0const val STATE_LOGIN_SUCCESS=1const val STATE_LOGIN_FAILED=2}/*** 进行登录操作*/private val api by lazy {API()}private val random:Random= Random()fun doLogin( account: String, password: String,block: (Int) -> Unit){block(STATE_LOGIN_LOADING)//开始去调用登录的API//api.login//有结果,此操作为耗时操作//0..1val randomValue = random.nextInt(2)if(randomValue==0){block(STATE_LOGIN_SUCCESS)}else{block(STATE_LOGIN_FAILED)}}fun checkUserState(account: String,block:(Int)->Unit){//0表示该账号已经注册//1表示该账号没有注册block(random.nextInt(2))}}
View层
class LoginActivity : AppCompatActivity(), LoginPresenter.OnDoLoginStateChange,LoginPresenter.OnCheckUserNameStateResultCallback {private val loginPresenter by lazy {LoginPresenter()}private var isUserNameCanBeUse=falseoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)//设置点击事件initListener()}private fun initListener() {LoginBtn.setOnClickListener {//去进行登录toLogin()}//监听内容变化accountInputBox.addTextChangedListener(object :TextWatcher{override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}override fun afterTextChanged(p0: Editable?) {//检查当前账号是否有注册loginPresenter.checkUserNameState(p0.toString(),this@LoginActivity)}})}/*** 处理登录逻辑*/private fun toLogin() {//做登录的逻辑处理val account:String =accountInputBox.text.toString()val password:String=passwordInputBox.text.toString()//给密码加密if(!isUserNameCanBeUse){//提示用户说当前用户名已经被注册了return}loginPresenter.doLogin(account,password,this)}override fun onAccountFormatError() {loginTipsText.text="账号不可为空..."}override fun onPasswordEmpty() {loginTipsText.text="密码不可为空..."}override fun onLoading() {loginTipsText.text="登录中..."}override fun onLoginSuccess() {loginTipsText.text="登录成功..."}override fun onLoginFailed() {loginTipsText.text="登录失败..."}override fun onNotExist() {//用户名不可用loginTipsText.text="该用户名已经注册了"isUserNameCanBeUse=false}override fun onExist() {//用户名可用loginTipsText.text="该用户名可以使用"isUserNameCanBeUse=true}
}
Presenter层
/*** Presenter层*/
class LoginPresenter {private val userModel by lazy {UserModel()}fun checkUserNameState(account: String, callback: OnCheckUserNameStateResultCallback) {userModel.checkUserState(account) {when (it) {0 -> {callback.onExist()}1 -> {callback.onNotExist()}}}}interface OnCheckUserNameStateResultCallback {fun onNotExist()fun onExist()}fun doLogin(userName: String, password: String, callback: OnDoLoginStateChange) {//检测账号格式是否正确if (TextUtils.isEmpty(userName)) {//提示账号有问题callback.onAccountFormatError()return}//检查密码长度是否正确if (TextUtils.isEmpty(password)) {//提示密码有问题callback.onPasswordEmpty()return}userModel.doLogin(userName, password) {when (it) {STATE_LOGIN_LOADING -> {callback.onLoading()}STATE_LOGIN_SUCCESS -> {callback.onLoginSuccess()}STATE_LOGIN_FAILED -> {callback.onLoginFailed()}}}}interface OnDoLoginStateChange {fun onAccountFormatError()fun onPasswordEmpty()fun onLoading()fun onLoginSuccess()fun onLoginFailed()}}
View层中LoginActivity 持有Presenter层中的loginPresenter。View层指向Presenter层
Presenter层中doLogin方法中需要callback进行回调更新LoginActivity 中的数据更新,而callback需要View层的引用。所以Presenter层指向View层。
Presenter层持有UserModel的引用,去获取相应的0,1数字代表的账号信息是否注册,Presenter层指向Model层。
Model层当接收到数据更新时,再通过block函数回调以Lambda表达式将数据传递到LoginPresenter层。
优点
- 剥离了View和Controller,解决了复杂的业务Activity过于庞大的问题
缺点
-
更新UI需要注意线程,UI控件是否已经销毁(在用户可视的生命周期范围内更新UI即可)
假如我们去请求一个数据,这个时候请求是耗时的,数据回来了,可是界面已经被用户关掉了,数据回来以后,我们得判断UI控件是否还存在。 -
如果多个地方使用到同一个Presenter,可能会存在一些用不上的接口。比如说我们在做音乐播放器的时候,播放器的逻辑层PlayerPresenter,我们对应需要通知UI的接口有开始播放,暂停播放,缓冲中,播放失败,下一首,上一首,播放模式改变…
如果我们多个地方使用到这个Presenter。比如说首页,详情页面,播放器页面。播放器页面使用这个Presenter实现这个接口没问题,都用得上。可是我的首页,只需要暂停播放和播放呀,其他的状态并不需要。这就会造成接口浪费。
MVVM架构
MVVM(Model-View-ViewModel) 是一种高级项目架构模式。主要分为3种部分:
Model是数据模型部分
View是界面展示部分,一般是Activity/Fragment
ViewModel可以理解成一个连接数据模型和界面展示的桥梁,从而让业务逻辑和界面展示分离,可以将相关逻辑放在其中处理。通过Jetpack的另外一个组件LiveData。LiveData可以使得ViewModel中的数据变化后,通知到View,它起到一个桥梁的作用。本质上这是一个观察者模式。通过DataBinding框架实现View层和Mode 层的双向绑定。
优点:
-
View和Model双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI的数据。不需要findViewByld也不需要butterknife,不需要拿到 具体的View去设置数据绑定监听器等等,这些都可以用DataBinding完成。
-
View和Model的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由lifeCycle完成。Lifecycle 会自动将其关联的回调进行清理,以避免内存泄漏和不必要的资源消耗。
-
不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的View和Presenter接口,项目结构更加低耦合。
缺点:
由于数据和视图的双向绑定,导致出现问题时不好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。
我们将UI控制层包含了我们平时写的Activity、Fragment、布局文件等与界面相关的东西。ViewModel层用于持有和UI元素相关的数据,以保证这些数据在旋转屏幕的时候不会丢失,并且还要提供接口给UI层调用以及和仓库层进行通信。仓库层主要的工作是判断调用方请求的数据应该是从本地数据源获取还是从网络数据源中获取,还是从网络数据源获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。
选择:
1、对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
2、对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。