欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android MVP架构

程序员文章站 2022-05-12 17:12:15
...

Android MVP架构

这是一个Kotlin +Okhttp3+Retrofit2+Rxjava2搭建的MVP初始App架构

希望您在看文章之前对MVP有所了解

1. MVP的三大角色

按照国际惯例先放上图片

Android MVP架构

从上图可以知道:View和Presenter可以相互持有,只有Presenter可以与model进行交互。

Model(数据层): model提供了数据的存取功能,数据可以来自网络或者本地。提供操作数据的方法给presenter层使用。

View(UI层):View层不需要写具体的逻辑,只关心UI操作,因为逻辑封装在了presenter中,View负责调用presenter的方法,然后执行相关的回调来操作UI。

Presenter(调度层): 连接model和View,进行相关的调度。 您的程序逻辑写在presenter中

2. MVP的架构优缺点

先吹一波。

优点:易于扩展,易于维护,易于测试,耦合度低,复用性高,程序健壮稳定等等

接受现实。

缺点:程序复杂性提高,代码文件变多,要解决内存泄漏问题(下文说明)

3. 动手前的架初始依赖

//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.1.2'

//Rxjava
implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'com.contrarywind:Android-PickerView:3.2.4'
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

4.从登录案例中来用MVP

项目整体代码
Android MVP架构

解决MVP中的内存泄漏:

由于Presenter经常性的需要执行一些耗时操作,那么当我们在操作未完成时候关闭了Activity,会导致Presenter一直持有Activity的对象,造成内存泄漏。有人可能觉得,我直接在activity中onDestory中解除Presenter和activity的关系不就行了吗?但是并不是所有情况下,onDestory都会被正常调用。

我们需要通过弱引用Activity来解决这个问题

先建立一个BasePresenter

/**
 * 弱引用View 防止内存泄漏
 * View的类型通过泛型类型传递进来,Presenter对这个View持有弱引用
 */
abstract class BasePresenter<T> {

    protected var mViewrRef: Reference<T>? = null

    protected val view: T
        get() = mViewrRef!!.get()!!


    val isAttachView: Boolean
        get() = null != mViewrRef && null != mViewrRef!!.get()

    /**
     * 与View建立关联
     */
    fun attachView(view: T) {
        mViewrRef = WeakReference(view)
    }

    /**
     * 解除与View的关联
     */
    fun detachView() {
        if (null != mViewrRef) {
            mViewrRef!!.clear()
            mViewrRef = null
        }
    }
}

创建一个BaseActivity基类,通过其生命周期来控制它与Presenter的关系

/**
两个泛型参数:第一个是View的接口类型,第二个是Presenter的具体类型
在oncreate()中会构建一个具体的Presenter,构建presenter后调用attachView与activity建立关联
在onDestory中解除对activity的引用。
这样子即使在onDetory()没有被正常调用的情况下。弱引用对象也能及时回收
*/
abstract class BaseActivity<V, T : BasePresenter<V>> : AppCompatActivity(){

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

    override fun onDestroy() {
        super.onDestroy()
        mPresenter?.detachView()
    }

    //创建一个Presenter(
    protected abstract fun createPresenter(): T
    //每个子类的初始化方法
    protected abstract fun init()
    
    /**
     * toast
     */
    protected fun Any.toast(context: Context, duration: Int = Toast.LENGTH_SHORT): Toast {
        val makeText = Toast.makeText(context, this.toString(), duration)
        makeText.setGravity(Gravity.CENTER, 0, 0)
        return makeText.apply { show() }
    }
  }

基类建立完毕后我们就可以用登录案例来模拟实现MVP了。

登录界面如下:
Android MVP架构

View层:MainActivity主要实现按钮点击登录和View接口的回调,进行一个UI操作

class MainActivity : BaseActivity<MainActivity, LoginPresenter>(), ILoginView{

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        init()
    }


    /**
     * 创建你的LoginPresenter
     */
    override fun createPresenter(): LoginPresenter {
       return LoginPresenter(this)
    }

    /**
     * 初始化方法
     */
    override fun init() {
        //按钮点击
        btnLogin.setOnClickListener {
            mPresenter!!.login(edtUserName.text.toString(),edtPassword.text.toString())
        }
    }

    /**
     * View的回调方法
     * 提示相关信息
     */
    override fun showInfoStr(msg: String) {
        msg.toast(this)
    }

    /**
     * View的回调方法
     * 登录成功回调
     */
    override fun loginSuccess(it: User) {
    }
}

Presenter层:具体的业务逻辑和连接View和model。这里主要使用了Rxjava

class LoginPresenter(private val loginView: ILoginView) : BasePresenter<MainActivity>() {

    private val mainManger = LoginModel()

    //LoginInterface
    @SuppressLint("CheckResult")
    fun login(userName: String, password: String) {

        mainManger.login(userName, password)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
//                    if("成功"){
//                        loginView.loginSuccess(it)
//                    }else{
//                        loginView.showInfoStr("错误信息")
//                    }
                }, {
                    loginView.showInfoStr(it.message!!)

                })
    }
}

Model层:进行接口API的调用,返回一个Observable 给Presenter进行进一步的处理

/**
 * 登录模块的model
 */
class LoginModel: ILoginModel{
    override fun login(userName: String, password: String): Observable<User> {
        return RetrofitClient.getApi().login(userName, password)
    }
}

项目结构相对清晰简单,更多的可以参照项目源码:
GitHub传送门