深入浅出了解MVP

开始之前,先来个需求。打开App后,通过耗时操作获取到数据,在列表中进行展示。

class MainActivity : AppCompatActivity() {

    val singerList = ArrayList<Singer>()

    lateinit var handler: Handler
   
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        handler = @SuppressLint("HandlerLeak") object : Handler() {
            override fun handleMessage(msg: Message?) {
                when (msg!!.what) {
                    0 -> {
                        rv.layoutManager = GridLayoutManager([email protected], 3)
                        rv.adapter = SingerAdapter(singerList)
                    }
                }
            }
        }

        NetworkThread().start()

    }


    inner class NetworkThread : Thread() {
        /**
         *模拟一次耗时操作获取数据
         */
        override fun run() {

            singerList.let {
                it.add(Singer(R.drawable.singer_1, "薛之谦", "超人气多面体创作才子"))
                it.add(Singer(R.drawable.singer_2, "伍佰", "华语区知名的摇滚歌手"))
                it.add(Singer(R.drawable.singer_3, "霍尊", "国色天香年度总冠军"))
                it.add(Singer(R.drawable.singer_4, "云飞", "星光大道2012年度亚军"))
                it.add(Singer(R.drawable.singer_5, "周杰伦", "华语乐坛流行天王"))
                it.add(Singer(R.drawable.singer_6, "杨坤", "神秘而强大的杨32郎"))
                it.add(Singer(R.drawable.singer_7, "那英", "东方的惠特妮休斯顿"))
                it.add(Singer(R.drawable.singer_8, "李宗盛", "鲍家街43号乐队发起人"))
                it.add(Singer(R.drawable.singer_9, "汪峰", "中国内地摇滚音乐歌手"))
                it.add(Singer(R.drawable.singer_10, "胡夏", "90后内地男歌手"))
                it.add(Singer(R.drawable.singer_11, "周深", "备受赞誉的天籁歌手"))
                it.add(Singer(R.drawable.singer_12, "张国荣", "**的巨星永远的哥哥"))
            }
            val msg = Message.obtain()
            msg.what = 0
            handler.sendMessage(msg)
        }
    }

}

效果图:深入浅出了解MVP
OK,功能实现了,这大概就是前端的魅力吧,依着那一张张精美的图片或者一页页简陋的原型,实现出来自己总能第一眼瞧到程序跑起来的样子!随着这样的代码铺展开来,渐渐地,你会发现程序越来越卡,直至OOM了。瞧瞧下面这张图:

深入浅出了解MVP
按Back键将程序退到后台后,再按了1次GC,Profilter的内存快照中依然存在着MainActivity的实例,这是内存无法回收的警示。在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,即NetworkThread中持有MainActivity的强引用,导致这块内存不容易被回收。

MVC

MVC,即Model-View-Controller。虽是软件的一种经典设计典范,但在Android应用的开发结构中,大量的业务逻辑充斥在众多的Activity中,导致其不只充当了View角色,还充当了Controller角色。虽然Model不依赖于View,但View却依赖着Model,这就导致代码的高耦合度,也不便于项目后期的维护。有没有更好的优化方式呢,有!MVP

MVP

MVP,即Model-View-Presenter,Model提供数据,View负责显示,Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter 来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

interface IListView {
    fun loading()
    /**
     * 显示数据
     */
    fun showList(singerList: List<Singer>)
}
interface IListModel {
    
    fun getData(getDataListener: GetDataListener)

    interface GetDataListener {
        fun onComplete(data: List<Singer>)

        fun onFailed(error: String)
    }
}
class SingerImpl : IListModel {
	/**
     *模拟一次耗时操作获取数据
     */
    override fun getData(getDataListener: IListModel.GetDataListener) {
       
        Thread {

           // Thread.sleep(3000)

            val singerList = ArrayList<Singer>()

            singerList.let {
                it.add(Singer(R.drawable.singer_1, "薛之谦", "超人气多面体创作才子"))
               	...
            }

            getDataListener.onComplete(singerList)

        }.start()
    }
}
class SingerPresenter(iListView: IListView) {

    var weakReference: WeakReference<IListView>? = WeakReference(iListView)

    var iListModel: IListModel = SingerImpl()

    /**
     * 获取数据,交给IListModel子类实例来实现
     */
    fun fetch() {
        if (weakReference != null) {
            weakReference?.get()?.loading()

            iListModel.getData(object : IListModel.GetDataListener {
                override fun onComplete(data: List<Singer>) {
                    weakReference?.get()?.showList(data)
                }

                override fun onFailed(error: String) {
					...
                }

            })
        }
    }
}
class MainActivity : AppCompatActivity(), IListView {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val presenter = SingerPresenter(this)
        presenter.fetch()
    }

    override fun loading() {
        ...
    }
	...
}

简单解释一下上面的代码,首先创建一个 IListView 接口,即 View 层,有加载中以及显示这2个方法,通过让Activity来实现它以充当Presenter层的桥梁。Model 层包含了IListModel及它的子类SingerImpl,分别负责获取数据逻辑的回调以及真实场景中获取数据的实现。那么持有View及Model引用的Presenter仅需要通过fetch方法执行数据获取逻辑并让View层按需执行回调即可。

MVP的进一步封装

open class BaseListPresent<T> {

    var weakReference: WeakReference<T>? = null

    fun attachView(view: T) {
        weakReference = WeakReference(view)
    }

    fun detachView() {
        weakReference?.clear()
    }
}
abstract class BaseActivity<V, T : BaseListPresent<V>> : AppCompatActivity() {

    var baseListPresent: T? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        baseListPresent = createPresenter()
        baseListPresent!!.attachView(this as V)

    }

    override fun onDestroy() {
        super.onDestroy()
        baseListPresent?.detachView()
    }
    abstract fun createPresenter(): T
}
class MainActivity : BaseActivity<IListView, BaseListPresent<IListView>>(), IListView {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        (baseListPresent as SingerPresenter).fetch()
    }
     override fun createPresenter(): SingerPresenter<IListView> = SingerPresenter()
	 ...
}

因为在BasePresenter中无法确定V层及M层对象的类型,所以在这里的对采用了泛型。而只要定义好 View 和 Model 的类型,就能灵活引用它们的对象了,而其中detachView方法,就是用来防止内存泄漏。

MVP较MPC的优点:
1.Activity职责更加趋于明确单一;
2.代码结构更清楚,更容易维护;
3.避免内存泄漏;
4.方便单元测试;