内容简介:补一补前面偷懒的博客(1/4)只是个人总结的文章不小心被你找到啦~如果感兴趣的话项目地址在文末
补一补前面偷懒的博客(1/4)
只是个人总结的文章不小心被你找到啦~
如果感兴趣的话项目地址在文末
1. What is that?
MVP是一种设计模式(框架),因为其出色的解耦功能广泛地用于Android工程中,它将应用程序分为Model-View-Presenter,各司其职,简称MVP
- Model(模型) 负责对数据的处理和存储,将数据回调给Presenter
- Presenter(主持者) 负责将View层的请求(如点击,更新视图数据)进行转发给对应的Model,接受回调后再通知View层更新视图
- View(视图) 仅负责将显示数据
- Contract(契约类) 仅仅用于定义View和Model的接口,便于管理和查看
一次简单的更新视图的基本流程
顺序就是按照①②③④⑤来进行
:one:在View中,我们向Presenter发送一次更新TextView的请求
:two:Presenter收到请求后再向对应的Model发送获取String的请求(中间可能有耗时操作,所以可能需要回调接口)
:three:成功拿到数据后再通过回调接口给Presenter
:four:Presenter拿到数据后再触发View的回调
:five:最后完成View的视图更新。
自始至终,View做的事情只有处理用户的请求(更新TextView)并发送给Presenter,然后提供一个用来更新视图的回调;Presenter做的事情只有转发,自己本身不处理逻辑;model负责提供信息,同时包括数据的处理。
有的版本的MVP可能选择将数据处理放入Presenter中,然后model只有一个setter/getter的类似JavaBean的作用,但是我觉得这样处理使得Presenter变得很臃肿,所以我选择将逻辑处理放入Model。两种方式都可以√
2. MVP通用框架
2.1 Contract层
Contract并没有什么很通用个框架,因为每个视图和每一个model的工作各不相同,这里给出的是上图中的的范例
class DetailContract{ interface DetailView{ fun onChangeText(String) } interface DetailModel { fun getNowText(callBack: GetTextCallBack) } interface GetTextCallBack{ fun onSuccess(str:String) fun onFail(info:String) } } 复制代码
2.2 Model层
Model也没有什么很通用的框架,这里给出的是上图中的范例
class SampleModel: DetailContract.DetailModel{ override getNowText(callBack: GetTextCallBack){ val str = ... //以上是获取String的操作 if(str!=""){ callBack.onSuccess(str) }else{ callBake.onFail("获取失败") } } } 复制代码
这里的具体Model类实现了Contract契约类中的接口,方便我们Presenter进行调用
2.3 View层
View在Android中一般包括两种,一种是Activity,一种是Fragment,这里只给出Activity的封装,Fragment类似,需要处理一些生命周期的问题。
Activity:
abstract class BaseActivity<V,T:BasePresenter<V>>:Activity(){ val TAG:String = javaClass.simpleName protected lateinit var mPresenter: T lateinit var mContext: Context override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mContext = this //初始化Presenter mPresenter = createPresenter() //将Presenter和View绑定 mPresenter.attachView(this as V) //初始化布局 initView(savedInstanceState) } /** * 应该由子类进行实现的初始化view的方法 */ abstract fun initView(savedInstanceState: Bundle?) /** * 创建对应的Presenter */ abstract fun createPresenter():T //解除绑定 override fun onDestroy() { super.onDestroy() mPresenter.detachView() } } 复制代码
BaseActivity是一个抽象类,所有加入MVP模式的Activity都应该继承这个抽象类。 泛型V代表的是视图(即自己),T则是对应的Presenter。View层持有对应Presenter的引用,用来发送消息。
2.4 Presenter层
abstract class BasePresenter<T> { //View接口类型的弱引用,防止所持有的view已经被销毁,但是该presenter仍然持有,导致内存的泄露 protected lateinit var mViewRef:Reference<T> //绑定View引用 fun attachView(view:T){ mViewRef = SoftReference<T>(view) } //获取当前绑定的View引用 protected fun getView(): T? { return mViewRef.get() } //是否已绑定View fun isViewAttached(): Boolean { return mViewRef != null&&mViewRef.get()!=null } //解除引用 fun detachView(){ if (mViewRef != null){ mViewRef.clear() } } } 复制代码
BasePresenter是一个抽象类,所有加入MVP模式的Presenter都应该继承该抽象类。 Presenter持有View层的一个弱引用,同时包括4个和弱引用有关的方法,分别是绑定View的引用,获取当前View的引用,判定是否已绑定了View,解除View的引用。
在具体的Presenter中还拥有一个对应Model对象。也就是Presenter同时持有View和Model,这样才可以做到信息的转发功能
传入的是Contract中的View接口类型是因为可以使得Presenter只通过接口向view传输传输信息。而不是一个具体的类型。
以上就是一些常用的框架,下面我们用实战来继续加深理解:
3. 实战
该范例选自红岩移动开发部的中期考核,内容为一个音乐App。仅仅分析播放页面(
因为我就做了两个页面:sob:
)
主页 | 播放页 |
---|---|
主要功能就是播放播放音乐以及歌词的滚动 我们先来看看结构:
我这里只展开了一些重要的部分,一些网络请求、自定义view相关的就不涉及。
1. Contract层
我觉得首先应该编写的是这一层,它用来规范我们View和Model的具体行为: DetailMusicContract:
class DetailMusicContract{ interface DetailView{ fun showDetailMusic(name:String,author:String,imageUrl:String) fun showLyric(viewList:ArrayList<View>) fun showToast(message:String) fun changeLyricPosition(position:Int) fun changeNowTimeTextView(time:String) fun changeSeekBarPosition(position:Int) } interface DetailModel { fun getNowMusic(callBack: GetNowMusicCallBack) fun getLyric(context:Context,callBack: GetLyricCallBack) } interface GetNowMusicCallBack{ fun onSuccess(music: MyMusic) fun onFail(info:String) } interface GetLyricCallBack{ fun onSuccess(viewList: ArrayList<View>) fun onFail(info:String) } } 复制代码
在 interface DetailView
中定义了6个方法
showDetailMuisc showLyric changeLyricPosition changNowTimeTextView changeSeekBarPosition
在 interface DetailModel
中定义了两个方法
getNowMusic getLyric
Tips:很多情况下,Model的方法是后面才加的,以为你可能一开始不知道Model需要哪些方法
2. View层
BaseActivity之前已经展示过,实际上的BaseActivity会增加一些关于服务绑定的东西,不在本篇范畴之内
DetailMusicActivity:
class DetailMusicActivity : BaseActivity<DetailMusicContract.DetailView, DetailMusicPresenter>(), DetailMusicContract.DetailView, MyMusicPlayerManager.OnStartPlay, MyMusicPlayerManager.StartNextMusic, View.OnClickListener{ override fun initView(savedInstanceState: Bundle?) { setContentView(R.layout.activity_detail) iv_detail_play.setOnClickListener(this) iv_detail_previous.setOnClickListener(this) iv_detail_next.setOnClickListener(this) iv_detail_back.setOnClickListener(this) //音乐准备完毕的回调 MyMusicPlayerManager.instance.setOnStartPlay(this) MyMusicPlayerManager.instance.setStartNextMusic(this) } //实现绑定成功后的音乐数据 override fun onService(name: ComponentName?, service: IBinder?) { Toast.makeText(this,"绑定成功",Toast.LENGTH_SHORT).show() changeNowMusic() } override fun createPresenter(): DetailMusicPresenter { return DetailMusicPresenter() } override fun showDetailMusic(name: String, author: String, imageUrl: String) { tv_detail_name.text = name tv_detail_author.text = author ImageLoader.with(this) .from(imageUrl) .disposeWith(CutToCircle()) .cacheWith(DoubleCacheUtils.getInstance()) .into(iv_detail_music) } //改变音乐的时候必要操作,注意,这里可以进行一些歌词还没有获取但是已经可以进行的操作 override fun changeNowMusic() { Log.d("刷新音乐","") mPresenter.getNowMusic() mPresenter.getLyric(this) mPresenter.startToChangeTextView() mPresenter.startToChangeSeekBar() sb_detail.max = MyMusicPlayerManager.instance.musicDuration() sb_detail.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ var isTouch = false override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if (isTouch){ val position = seekBar!!.progress MyMusicPlayerManager.instance.musicSeekTo(position) mPresenter.pause() } } override fun onStartTrackingTouch(seekBar: SeekBar?) { isTouch = true } override fun onStopTrackingTouch(seekBar: SeekBar?) { isTouch = false MyMusicPlayerManager.instance.play() } }) } //触发显示歌词的回调,注意,这里应该放只有获取到了歌词之后才可以做出的ui操作 override fun showLyric(viewList:ArrayList<View>) { Log.d("歌词显示回调","成功") runOnUiThread { val adapter = MyViewPagerAdapter(viewList) mb_lyric.init() mb_lyric.setScrollTime(1500) mb_lyric.setAdapter(adapter,this) mb_lyric.setTransformer(CustomTransformer()) mPresenter.startToChangeLyric() } } override fun onNextMusic() { mPresenter.playNext() } override fun showToast(message:String) { Toast.makeText(this,message,Toast.LENGTH_SHORT).show() } override fun changeLyricPosition(position: Int) { runOnUiThread { mb_lyric.changeTo(position) } } override fun changeNowTimeTextView(time: String) { runOnUiThread{ tv_detail_now.text = time } } override fun changeSeekBarPosition(position: Int) { runOnUiThread { sb_detail.progress = position } } //点击事件的集中处理 override fun onClick(v: View?) { when{ v!!.id == iv_detail_play.id -> { if (MyMusicPlayerManager.instance.isPlaying()){ mPresenter.pause() }else{ mPresenter.play() } } v.id == iv_detail_previous.id -> { mPresenter.playPrevious() } v.id == iv_detail_next.id -> { mPresenter.playNext() } v.id == iv_detail_back.id -> { this.finish() } } } /** * 生命周期相关 */ override fun onDestroy() { super.onDestroy() mPresenter.cancelTimer() } } 复制代码
看起来代码可能有点长,原因是这个页面相对来说有点复杂,但是整个View的结构很清晰。
-
override fun initView
这个是继承自BaseActivity里的方法,用于首次启动后的初始化布局,可以看到我们在这里设置了一些控件的监听以及回调接口。需要解释的是MyMusicPlayerManager.instance.setOnStartPlay(this) MyMusicPlayerManager.instance.setStartNextMusic(this)
这两个方法,由于音乐播放器需要从网络上异步加载音乐播放数据,所以需要设置一个 音乐准备播放的回调接口 以及 播放完毕后切换到下一首的回调接口 他们对应的方法为
override fun changeNowMusic()
当前的音乐发生改变时(上一首下一首)的触发的回调override fun onNextMusic()
当播放完毕后切换到下一首的回调接口 -
override fun onService
这个是继承自BaseActivity里的方法(上文中的BaseActivity并没有加入,但是因为这个是一个音乐App,所以需要和当前Activity进行绑定)在这里我们进行的是绑定操作完毕后的操作:执行changeNowMusic()
来进行音乐界面的初始化操作 -
override fun createPresenter
继承自BaseActivity的方法,创建一个对应的Presenter实例 -
override fun showDetailMusic
这个是在Contract接口中定义的方法,用于显示一些控件的值 -
override fun changeNowMusic
在MyMusicPlayerManager.OnStartPlay
接口中定义的方法,具体内容为显示当前正在播放的歌曲的所有信息,里面向Presenter发送了四个消息,getNowMusic
用于请求显示当前的音乐、getLyric
用于请求歌词、startToChangeTextView
用于请求开始不断更新当前播放时间、startToChangeSeekBar
用于请求开始不断更新SeekBar的进度。之后就是设置一些SeekBar的监听来实现对音乐进度的控制 -
override fun showLyric
这个是在Contract接口中定义的方法,用来显示歌词,在最后的时候还想Presenter发送了请求开始滚动歌词界面的消息 -
override fun onNextMusic
这个是定义在MyMusicPlayerManager.StartNextMusic
接口中的方法,上文已经提及,在音乐自动播放完后出发的回调,即播放下一首歌 接下来的一些方法就不用多说了,showToast、changeLyricPosition、changeNowTimeTextView、changeSeekBarPosition、还有集中处理的onClick控件点击监听
View层总结
说了这么多,实际上View层的作用简而言之就是 输入 输出
根据用户的操作向Presenter发送请求、提供各式各样的接口来给Presenter和音乐服务进行回调
3. Presenter层
DetailMusicPresenter:
class DetailMusicPresenter : BasePresenter<DetailMusicContract.DetailView>(){ private var lyricTimer:Timer = Timer() private var textViewTimer:Timer = Timer() private var seekBarTimer:Timer = Timer() private val detailMusicModel = DetailMusicModel() //获取目前播放的音乐的回调 fun getNowMusic(){ detailMusicModel.getNowMusic(object :DetailMusicContract.GetNowMusicCallBack{ override fun onSuccess(music: MyMusic) { mViewRef.get()!!.showDetailMusic(music.name,music.author,music.imageUrl) } override fun onFail(info: String) { mViewRef.get()!!.showToast(info) } }) } fun getLyric(context: Context){ detailMusicModel.getLyric(context,object :DetailMusicContract.GetLyricCallBack{ override fun onSuccess(viewList:ArrayList<View>) { mViewRef.get()!!.showLyric(viewList) } override fun onFail(info: String) { mViewRef.get()!!.showToast(info) } }) } fun startToChangeLyric(){ lyricTimer = Timer() lyricTimer.schedule(object : TimerTask() { override fun run() { mViewRef.get()!!.changeLyricPosition(MyMusicPlayerManager.instance.getNowLyricPosition()) } } ,0,100) } fun startToChangeTextView(){ textViewTimer = Timer() textViewTimer.schedule(object : TimerTask(){ override fun run() { mViewRef.get()!!.changeNowTimeTextView(MyMusicPlayerManager.instance.nowTimeInMin()) } },0,100) } fun startToChangeSeekBar(){ seekBarTimer = Timer() seekBarTimer.schedule(object :TimerTask(){ override fun run() { mViewRef.get()!!.changeSeekBarPosition(MyMusicPlayerManager.instance.musicCurrent()) } },0,100) } //音乐控制 fun play(){ MyMusicPlayerManager.instance.play() } fun pause(){ MyMusicPlayerManager.instance.pause() } fun playPrevious(){ cancelTimer() MyMusicPlayerManager.instance.playPrevious() } fun playNext(){ cancelTimer() MyMusicPlayerManager.instance.playNext() } fun cancelTimer(){ lyricTimer.cancel() textViewTimer.cancel() seekBarTimer.cancel() } } 复制代码
因为Presenter的功能就是转发,所以代码不长,而且结构清晰
- 首先拥有一个DetailModel实例,不需要太多阐述
-
getNowMusic
这个是在View层中调用的,用来获得当前音乐的信息,代码并不难理解,向Model请求获得当前的音乐,成功的话就通过Presenter拥有的View层的引用来调用之前已经在Contract中定义好的showDetailMusic
方法来通知View层更新,如果失败那就调用之前也在Contract中定义好的showToast
方法来显示Toast信息提醒用户 -
fun getLyric
这个是也是在View层中调用的,用来获取当前音乐的歌词,成功则回调View层的接口来显示歌词,否则显示Toast -
fun startToChangeLyric
、fun startToChangeTextView
、fun startToChangeSeekBar
同样是在View层中定义的,实现方式几乎一致,开启一个新的TimerTask,定时获取当前歌词应该在的position、当前已经播放时间、当前SeekBar应该在的进度,然后回调View层对应的方法来更新 - 音乐控制不必多说,不过注意上一首下一首时需要先取消Timer,否则会出现报错
-
fun cancelTimer
用来取消Timer,当换歌、View销毁时,由于Timer在子线程中执行的,会导致Lyric没有初始化,或者nullPoint报错
4. Model层
DetailMusic:
class DetailMusicModel: DetailMusicContract.DetailModel { override fun getNowMusic(callBack: DetailMusicContract.GetNowMusicCallBack){ val music = MyMusicPlayerManager.instance.nowMusic() callBack.onSuccess(music) } override fun getLyric(context:Context,callBack: DetailMusicContract.GetLyricCallBack) { val music = MyMusicPlayerManager.instance.nowMusic() val request = Request.Builder("http://elf.egos.hosigus.com/music/lyric?id=${music.id}") .setMethod("GET").build() NetUtil.getInstance().execute(request,object :Callback{ override fun onResponse(response: String?) { val mainJson = JSONObject(response) val str = mainJson.getJSONObject("lrc").getString("lyric") val lyric = Lyric(str!!) MyMusicPlayerManager.instance.nowMusic().lyric = lyric val viewList = ArrayList<View>() for (i in 0 until lyric.arrayList.size){ val view = LayoutInflater.from(context).inflate(R.layout.item_lyric,null) view.findViewById<TextView>(R.id.tv_item_lyric).text=lyric.arrayList[i] viewList.add(view) } callBack.onSuccess(viewList) } override fun onFailed(t: Throwable?) { } }) } } 复制代码
Model类主要用来收集数据并提供给Presenter
-
override fun getNowMusic
获取当前的音乐并回调到Presenter,而Presenter收到回调后会通知View层进行更新 -
override fun getLyric
获取当前的歌词并打包成ArrayList回调给Presenter,而Presenter收到回调后会通知给View层进行更新
总结
本文只针对MVP的结构进行了分析,一些其他的内容,如音乐播放器,自定义歌词View等并没有涉及,如果感兴趣的话可以访问 GitHub源码地址
Other
抱歉拉低了掘金的文章质量。。。
本人技术有限,仍然在学习当中,如果有什么不对的地方希望大佬们指正!!!
写的初衷也只是来总结罢了,并没有想过会有多少人看hhhhhh
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 观察者模式实战
- Reactive 模式优势与实战
- Go 设计模式实战之并发组件
- 真实项目案例实战—【状态设计模式】使用场景
- 重学 Java 设计模式:实战模版模式「模拟爬虫各类电商商品,生成营销推广海报场景」
- Redis实现分布式锁(设计模式应用实战)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。