应用架构思维提升#MVVM

49 阅读5分钟

应用架构思维提升#插件化

应用架构思维提升#组件化

应用架构思维提升#MVVM

我们从MVP开始聊起!

一.解决mvp中的内存泄漏问题

我们知道MVP的架构模式是为了解决mvc的中Activity中臃肿耦合问题,Present主要负责从model层拿数据,去通知view修改ui,解决了耦合问题,但是prensenter也有它的问题,比如ui销毁时的内存泄漏问题,当界面销毁时,present的才从model层拿到数据去通知view修改界面,一般情况下,我们让Activity在销毁时将present中Activity置空,并清除掉其他请求的操作,来解决这样的问题:

伪代码展示:

class PlayActivity : AppCompatActivity(), PlayView {

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_play)
        PlayPresent.instance.attach(this)
        PlayPresent.instance.getMusic()
    }



    override fun onDestroy() {
        super.onDestroy()
        PlayPresent.instance.detachView()
    }

    override fun onPlayOrPause() {
        println("mvp onPlayOrPause->>>>>>>>>>>>")
    }

    override fun onTitleChange(title: String) {
    }

    override fun onProgressChange(current: Int) {
    }

    override fun onPlaying() {
    }

    override fun onCoverChange(cover: String) {
    }
}

interface PlayView {
    
    fun onPlayOrPause()

    fun onTitleChange(title: String)

    fun onProgressChange(current: Int)

    fun onPlaying()

    fun onCoverChange(cover: String)

}
class PlayPresent private constructor(){

    private val playModel = PlayModel()
    private var currentPlayView: WeakReference<PlayView>? = null;
    private var currentMusic: Music? = null;


    companion object {
        val instance by lazy {
            PlayPresent()
        }
    }

    fun attach(playView: PlayView) {
        currentPlayView = WeakReference(playView);
    }


    fun getMusic() {

        println("mvp getMusic->>>>>>>")
        playModel.getMusicById(0, object : PlayModel.PlayModelCallBack {
            override fun onSuccessMusic(music: Music) {
                currentMusic = music
                dispatchViewState()
            }
        })
    }

    private fun dispatchViewState() {
        currentPlayView!!.get()?.let {
             it.onPlayOrPause()
        }
    }

    fun detachView() {
        println("mvp detachView->>>>>>>")
        currentPlayView = null
    }

}

那有没有其他方式来解决呢?

jetPack为我们提供了Lifecycle,它让我们在present中可以轻而易举的感知Activity的生命周期

上面的Presenter就可以改造为:

class PlayPresent private constructor() : LifecycleEventObserver {

    private val playModel = PlayModel()
    private var currentPlayView:PlayView? = null;
    private var currentMusic: Music? = null;


    companion object {
        val instance by lazy {
            PlayPresent()
        }
    }

    fun attach(playView: PlayView) {
        currentPlayView = playView
        if (playView is LifecycleOwner) {
            playView.lifecycle.addObserver(this)
        }
    }


    fun getMusic() {
        println("mvp getMusic->>>>>>>")
        playModel.getMusicById(0, object : PlayModel.PlayModelCallBack {
            override fun onSuccessMusic(music: Music) {
                currentMusic = music
                dispatchViewState()
            }
        })
    }

    private fun dispatchViewState() {
        currentPlayView!!.get()?.let {
            it.onPlayOrPause()
        }
    }

    fun detachView() {
        println("mvp detachView->>>>>>>")
        currentPlayView = null
    }

     /**
     * 感知Activity的生命周期
     */
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        println("mvp onStateChanged->>>>>$event")
        if (event == Lifecycle.Event.ON_DESTROY) {
            detachView()
        }
    }

我这里只是想引出LifeCycle,并没有对mvp这些进行封装,通过lifeCycle,我们可以在present中,添加Activity生命周期的观察者,就可以感知他的生命周期,解除present和Activity的绑定,从而避免了内存泄漏,并且不需要在Activity中主动调用。

二.解决mvp中的接口回调繁杂的问题

我们知道mvp帮助我们做到了充分解耦,它除了可能会导致上面的内存泄漏问题,还有一个问题就是接口回调繁杂的问题。例如上面的例子,我们需要加一个锁屏或者通知栏中的播放状态的Acvitiy,我们同样也使用这个Present,我们对PlayView进行下改造,增加几个方法

interface PlayView {

    fun onTitleChange(title: String)

    fun onProgressChange(current: Int)

    fun onPlaying()

    fun onPausePlay()

    fun onCoverChange(cover: String)

}

class FlowPlayerActivity : AppCompatActivity(), PlayView {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flow_player)
        title = "FlowPlayerActivity"
        PlayPresent.instance.attach(this)
        PlayPresent.instance.getMusic()
        initListener()

    }


    private fun initListener() {

        playOrPauseBtn.setOnClickListener {

            PlayPresent.instance.doPlayOrPause()

        }

    }

    override fun onDestroy() {
        super.onDestroy()
//        PlayPresent.instance.detachView()
    }


    override fun onTitleChange(title: String) {

    }

    override fun onProgressChange(current: Int) {

    }

    override fun onPlaying() {
        runOnUiThread {
            println("mvp FlowPlayerActivity onPlaying->>>>>>>>>>>>")
            playOrPauseBtn.text = "播放状态"
        }

    }

    override fun onPausePlay() {
        runOnUiThread {
            println("mvp FlowPlayerActivity onPausePlay->>>>>>>>>>>>")
            playOrPauseBtn.text = "暂停状态"
        }
    }

    override fun onCoverChange(cover: String) {

    }

}
 

我们在Flow中其实有些接口回调其实是用不上的,但是我们又不得不写上去,这个接口的颗粒度就不好掌握,我们试着从数据驱动UI的角度来思考,我们去写一个数据容器,这个容器可以包装任意类型,并且在present中获得或修改数据的状态,在view层中通过获取观察present中数据的状态,来修改ui,需要注意的是,这个数据容器也要监听view的生命周期,防止内存泄漏,那怎么定义这个数据容器呢?我们来看看下面的代码

class DataListenerContainer<T> {

    private val blocks = arrayListOf<(T) -> Unit>()

    private val viewLifeCycleOwner = hashMapOf<(T) -> Unit, LifecycleOwner>()

    /**
     *
     * 方法执行时 会执行
     * block的回调
     */
    var value: T? = null
        set(value: T?) {

            blocks.forEach {
                /**
                 * 判断一下当前owner的状态,可见状态才更新
                 **/
                val lifecycleOwner = viewLifeCycleOwner[it]
                if (null != lifecycleOwner &&
                    lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
                ) {
                    it.invoke(value!!)
                }

            }
            field = value
        }

    /**
     * 在同一个owner中,有可能有多个view进行了owner(Activity/fragment)的监听(block)
     * 所以要对view的block的进行存储
     */
    fun addListener(lifecycleOwner: LifecycleOwner, valueObserver: (T) -> Unit) {

        viewLifeCycleOwner[valueObserver] = lifecycleOwner
        /**
         * 当owner的state为destroy时要移除
         */
        lifecycleOwner.lifecycle.addObserver(ValueObserver(valueObserver))

        if (!blocks.contains(valueObserver)) {
            blocks.add(valueObserver)
        }
    }

    fun removeListener(block: (T) -> Unit) {
        blocks.remove(block)
    }

    inner class ValueObserver constructor(private val valueObserver: (T) -> Unit) : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                viewLifeCycleOwner.remove(valueObserver)
                removeListener(valueObserver)
            }
        }
    }

}

我们上面说到,这个数据容器内的数据是在present中修改或获取,在view层观察这个对象的状态,这样我们的view层的接口就可以移除了,我们只要在present中定义各种可被观察的数据,在UI层去根据数据的状态去修改UI就可以了,避免的接口浪费,比如我们在FlowPlayerActivity只需要观察播放状态的改变即可,关于view的接口回调直接可以移除,并且由于绑定了lifeCycleOwner,不会造成内存泄漏。

class FlowPlayerActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flow_player)
        title = "FlowPlayerActivity"
        PlayPresent.instance.attach(null)
        PlayPresent.instance.getMusic()
        initListener()

    }


    private fun initListener() {

        playOrPauseBtn.setOnClickListener {

            PlayPresent.instance.doPlayOrPause()

        }

        PlayPresent.instance.dataListenerContainer.addListener(this){


            when (it) {
                PlayPresent.PlayerState.PLAYING -> {
                    runOnUiThread {
                        println("mvp FlowPlayerActivity onPlaying->>>>>>>>>>>>")
                        playOrPauseBtn.text = "播放状态"
                    }
                }
                PlayPresent.PlayerState.PAUSE->{
                    runOnUiThread {
                        println("mvp FlowPlayerActivity onPausePlay->>>>>>>>>>>>")
                        playOrPauseBtn.text = "暂停状态"
                    }
                }
                else -> {}
            }
        }

    }

上面的架构差不多就是mvvm了,所谓的mvvm,它是基于数据驱动UI的,耦合度更低,例如这里并不需要持有FlowPlayerActivity(view层)的present,就相当于于mvvm中的viewmodel,而这里面的数据容器DataListenerContainers就相当于jetPack中的liveData,liveData是具有生命周期的可观察的数据持有类,关键点在于具有生命周期,可观察!!!我们写的DataListenerContainers是不是都具有以上特征。

总结:

本文从mvp的缺点出发,从内存泄漏和接口冗余的角度引出mvvm架构,从代码结构上来说它解耦程度更高,不过对于一些简单的页面,其实也没有必要用,毕竟杀鸡焉用牛刀。