在MVC框架中套在Android上并不适用,会导致V层和C层代码越来越多,如果在处理复杂的逻辑时,在Activity中(V层)中代码成千上万行,会导致V层越来越臃肿,于是开始剥离V层和C层的代码。
开始将大量的逻辑代码抽取到P层,于是就演化出了MVP架构
View:Activity、Fragment
Presenter: 逻辑层
Model: 数据处理层
在MVP架构中,设计到的内容是通过调用逻辑层的方法和更新UI
那么如何调研逻辑层的方法,如何通知UI更新呢?
首先:View层持有Presenter的引用或者管理类管理Presenter,因此,在View层可以直接拿到Presenter,所以在View层可以直接通过Presenter调用里面的方法
其次:在View层获取/创建Presenter的时候,需要在Presenter中需要注册View层的引用,把接口给到Presenter,这样在P层的逻辑处理中,可以通过接口回调通知UI进行更新
最后,Presenter层持有Model对象,通过Model层去拿数据,隔离了V层和M层,实现了解耦
优点:
剥离了V层C层,实现了解耦,解决了Activity(V层)中代码过于臃肿的问题
缺点:
- 更新UI时需要注意线程(P层处理Model层的数据时一般在异步线程,而更新UI需要在主线程)、UI控件的更新有可能在在操作时已经销毁了(需要在用户可视的生命周期范围内进行更新数据) 比如在请求一个数据时,因为是耗时操作,当数据回来时,用户可能将当前的界面关掉了。这个时候更新UI需要判断控件是否存在
- 在多个地方使用到同一个Presenter时,可能会存在一些用不上的接口,但还是要去实现,造成了代码的臃肿
测试代码:
逻辑层接口方法:
package com.example.jetpackbysob.mvptest.palyertest
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 16:29
*/
interface IPlayerPresenter {
fun registerViewCallback(callback: IPlayerViewCallback)
fun unRegisterViewCallback(callback: IPlayerViewCallback)
/**
* 根据状态进行播放或则暂停
*/
fun doPlayOrPause()
/**
* 播放上一首
*/
fun playPre()
/**
* 播放下一首
*/
fun playNext()
}
view层接口方法:
package com.example.jetpackbysob.mvptest.palyertest
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 11:20
*/
interface IPlayerViewCallback {
/**
* 标题改变
*/
fun onTitleChange(title: String)
/**
* 进度改变
*/
fun onProgressChange(current: Int)
/**
* 播放中
*/
fun onPlaying()
/**
* 暂停
*/
fun onPausePlay()
/**
* 封面改变
*/
fun onCoverChange(cover: String)
}
P层:
package com.example.jetpackbysob.mvptest.palyertest
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 11:14
* 通过音乐播放器测试mvp的缺点
*
* 播放音乐
* 暂停音乐
* 上一首
* 下一首
* =================
* 播放的状态:
* 通知UI改变成播放的状态
* 通知UI改变进度的状态
* 上一首,下一首:
* 通知UI歌曲标题的变化
* 通知UI歌曲封面的变化
* 暂停音乐:
* 更新UI状态为暂停
*
*/
class PlayerPresenter private constructor() : IPlayerPresenter {
companion object {
val instance by lazy {
PlayerPresenter()
}
}
// 播放状态的枚举类
enum class PlayState {
NONE, LOADING, PLAYING, PAUSE
}
private val mCallbacks = arrayListOf<IPlayerViewCallback>()
private var mCurrentPlayState = PlayState.NONE
override fun registerViewCallback(callback: IPlayerViewCallback) {
if (!mCallbacks.contains(callback)) {
mCallbacks.add(callback)
}
}
override fun unRegisterViewCallback(callback: IPlayerViewCallback) {
mCallbacks.remove(callback)
}
override fun doPlayOrPause() {
if (mCurrentPlayState != PlayState.PLAYING) {
// 开始播放音乐
handlerPlayingState()
} else {
// 暂停播放
handlerPauseState()
}
}
// 处理通知UI进行暂停音乐
private fun handlerPauseState() {
mCurrentPlayState = PlayState.PAUSE
mCallbacks.forEach {
it.onPausePlay()
}
}
// 处理进行通知界面播放音乐
private fun handlerPlayingState() {
mCurrentPlayState = PlayState.PLAYING
for (mCallback in mCallbacks) {
mCallback.onPlaying()
}
}
/**
* 播放上一首
*/
override fun playPre() {
// 1.拿到上一首歌曲 --> 变更UI,包括通知标题和封面
handlerTitleChange("七里香")
handlerCoverChange("七里香")
// 2.设置给播放器
// 3.等待播放的回调通知
}
/**
* 播放下一首歌曲
*/
override fun playNext() {
// 1.拿到下一首歌曲 --> 变更UI,包括通知标题和封面
handlerTitleChange("玫瑰花的葬礼")
handlerCoverChange("玫瑰花")
// 2.设置给播放器
// 3.等待播放的回调通知
}
private fun handlerCoverChange(cover: String) {
mCallbacks.forEach {
it.onCoverChange(cover)
}
}
private fun handlerTitleChange(title: String) {
mCallbacks.forEach {
it.onTitleChange(title)
}
}
}
V层:
package com.example.jetpackbysob.mvptest.palyertest
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.jetpackbysob.R
import kotlinx.android.synthetic.main.activity_player.*
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 16:22
*/
class PlayerActivity : AppCompatActivity(), IPlayerViewCallback {
private val mPlayerPresenter by lazy {
PlayerPresenter.instance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
initPresenter()
initListener()
}
/**
* 控件设置点击事件
*/
private fun initListener() {
play_or_pause.setOnClickListener {
// 调用presenter层进行播放或者暂停
mPlayerPresenter.doPlayOrPause()
}
// 播放上一首
play_pre.setOnClickListener {
mPlayerPresenter.playPre()
}
// 播放下一首
play_next.setOnClickListener {
mPlayerPresenter.playNext()
}
}
private fun initPresenter() {
mPlayerPresenter.registerViewCallback(this)
}
override fun onDestroy() {
super.onDestroy()
mPlayerPresenter.unRegisterViewCallback(this)
}
override fun onTitleChange(title: String) {
play_title.text = title
}
override fun onProgressChange(current: Int) {
TODO("Not yet implemented")
}
override fun onPlaying() {
// 播放中,图标显示暂停
play_or_pause.text = "暂停"
}
override fun onPausePlay() {
// 暂停中,图标显示播放
play_or_pause.text = "播放"
}
override fun onCoverChange(cover: String) {
Toast.makeText(this, "封面变化了--> $cover", Toast.LENGTH_SHORT).show()
}
}
这里的FlowPlayControllerActivity模拟表示在其他页面播放音乐时的悬浮控制框,PlayerActivity为整个音乐的控制界面,实现的方法不仅有切换上下首歌曲,还有控制音乐的播放状态,而在FlowPlayControllerActivity中只需要控制播放状态,不关心切换上下首歌曲,可是因为其实现了IPlayerViewCallback,它就必须也要去实现相应的方法,这是不科学的