进阶MVP设计模式之二

作者: Chase_stars | 来源:发表于2019-07-06 12:27 被阅读11次

    世上本没有路,走的人多了,便变成了路 — 鲁迅

    写在前面

    在上一篇文章《进阶MVP设计模式之一》中已经详细讲解了这套MVP的实现以及使用,本篇文章是基于上一篇文章的基础上进行扩展。为了能够更好的理解,建议先看上一篇文章加深印象后再来阅读,会更有收获哦。

    在上一篇文章末尾说过:这套MVP设计巧妙,但是也有不如意的地方,就是不能在一个Activity/Fragment中使用多个不同的Presenter。

    在实际项目中,我也遇到过此类情况,比如Activity A需要调用Presenter B中fun()函数,但是一个Activity只允许拥有一个Presenter,Activity A已经拥有Presenter A了,就不能拥有Presenter B,最终解决方案只能是将Presenter B中的fun()函数copy到Presenter A中,这样Activity A就可以调用Presenter A中的fun()函数了。如果Activity A中要大量使用其他不同Presenter中的接口呢,那么Presenter A中就要写一大坨代码,导致整个P层重复的代码太多。换一种角度思考,Activity A想要使用Presenter B,直接把Presenter B拿过来不就好了, 让一个Activity持有多个Presenter,需要谁就把谁拿过来,何乐而不为?

    如何实现

    划重点:接下来会以对比的方式讲解,所以一定要先看上一篇文章!!! 传送门>>>

    1.RequestPresenter

    不同之处是返回值类型,由Class变为Class数组,意味着可以放入多个类了。

    // 注解的生命周期
    @Retention(RetentionPolicy.RUNTIME)
    // 注解的适用范围
    @Target(ElementType.TYPE)
    public @interface RequestPresenter {
        Class<?>[] presenter() default {};
    }
    
    2.BaseView

    这个View接口不变,一个无函数的View接口,需要结合实际需求进行扩展。

    public interface BaseView {}
    
    3.BasePresenter

    这个Presenter抽象类不变,这里的View使用了弱引用,防止内存泄漏。

    public abstract class BasePresenter<V extends BaseView> {
    
        protected Context mContext;
    
        private WeakReference<V> mView;
    
        protected void attachView(Context context, V view) {
            mContext = context;
            mView = new WeakReference<>(view);
        }
    
        protected void detachView() {
            if (mView != null && mView.get() != null) {
                mView.clear();
            }
        }
    
        protected boolean isAttachView() {
            return mView != null && mView.get() != null;
        }
    
        protected V getView() {
            return mView == null ? null : mView.get();
        }
    }
    
    4.IPresenterStore

    新增Presenter存储接口,提供了get(),put()等方法,具体是用来做什么的呢,请带着思考继续看。

    public interface IPresenterStore<V extends BaseView, P extends BasePresenter<V>> {
    
        void put(String key, P presenter);
    
        P get(String key);
    
        Map<String, P> getStore();
    
        void clear();
    }
    
    5.PresenterStore

    新增Presenter存储具体实现类,内部维护了一个Map,用来存储Presenter。

    public class PresenterStore<V extends BaseView, P extends BasePresenter<V>> implements IPresenterStore<V, P> {
    
        private Map<String, P> mStore;
    
        @Override
        public void put(String key, P presenter) {
            if (mStore == null) {
                mStore = new HashMap<>();
            }
            mStore.put(key, presenter);
        }
    
        @Override
        public P get(String key) {
            return mStore.get(key);
        }
    
        @Override
        public Map<String, P> getStore() {
            return mStore;
        }
    
        @Override
        public void clear() {
            if (mStore != null) {
                mStore.clear();
            }
        }
    }
    
    6.IPresenterFactory

    Presenter工厂接口,需要生产Presenter的工厂类来实现,依然只有一个函数。
    不同之处是createPresenter()改为createPresenterStore(),返回值类型也从Presenter改为IPresenterStore,不过仍是创建Presenter的核心。

    public interface IPresenterFactory<V extends BaseView, P extends BasePresenter<V>> {
    
       IPresenterStore<V, P> createPresenterStore();
    }
    
    7.PresenterFactoryImpl

    Presenter工厂的具体实现类,通过一个静态函数createFactory()返回一个该Presenter工厂实现类,并在该函数中通过注解+反射得到全部Presenter类名;当外部调用createPresenterStore()时,会先创建一个新的PresenterStore,然后通过Presenter类名newInstance()一个新的实例添加到PresenterStore中,key为Presenter类的字符串,全部Presenter创建完成后将这个PresenterStore返回,PersenterStore就创建完成了。

    不同之处在于从创建一个Presenter变成了创建多个Presenter,并将多个Presenter使用PresenterStore来维护。

    public class PresenterFactoryImpl<V extends BaseView, P extends BasePresenter<V>>
            implements IPresenterFactory<V, P> {
    
        private Class<P>[] mPresenters;
    
        public static <V extends BaseView, P extends BasePresenter<V>> PresenterFactoryImpl createFactory(Class<?> clazz) {
            if (!clazz.isAnnotationPresent(RequestPresenter.class)) {
                throw new RuntimeException("Do not find RequestPresenter");
            }
    
            RequestPresenter requestPresenter = clazz.getAnnotation(RequestPresenter.class);
            Class<P>[] pClasses = (Class<P>[]) requestPresenter.presenter();
            return pClasses == null ? null : new PresenterFactoryImpl(pClasses);
        }
    
        private PresenterFactoryImpl(Class<P>[] pClasses) {
            mPresenters = pClasses;
        }
    
        @Override
        public IPresenterStore<V, P> createPresenterStore() {
            if (mPresenters == null || mPresenters.length < 1) {
                throw new RuntimeException("Presenters is null");
            }
    
            IPresenterStore<V, P> presenterStore = new PresenterStore<>();
    
            try {
                for (Class<P> pClass : mPresenters) {
                    presenterStore.put(pClass.toString(), pClass.newInstance());
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return presenterStore;
        }
    
    8.IPresenterProxy

    Presenter代理接口,其中有三个函数,可根据实际需求编写。

    public interface IPresenterProxy<V extends BaseView, P extends BasePresenter<V>> {
    
        void setFactory(IPresenterFactory<V, P> factory);
    
        IPresenterFactory<V, P> getFactory();
    
        P getPresenter(String key);
    }
    
    9.PresenterProxyImpl

    因为Presenter工厂只有createFactory()和createPresenterStore(),而实际项目中不仅仅满足于createFactory()和createPresenterStore(),还会有其他的需要;所以使用了一个代理类来满足其他需要,所代理的是Presenter工厂和PresenterStore,可以进行扩展,比如attachView(),detachView(),release()等。

    不同之处在于构造函数或设置工厂时就去创建PresenterStore,而不是在getPresenter()时去创建;由于多个Presenter是通过PresenterStore维护的,这里的getPresenter()需要传入参数key得到对应的Presenter。

    public class PresenterProxyImpl<V extends BaseView, P extends BasePresenter<V>> implements IPresenterProxy<V, P> {
    
        private IPresenterFactory<V, P> mFactory;
    
        private IPresenterStore<V, P> mPresenterStore;
    
        // 通过构造函数传入默认工厂
        public PresenterProxyImpl(IPresenterFactory<V, P> factory) {
            mFactory = factory;
            mPresenterStore = mFactory.createPresenterStore();
        }
    
        // 设置工厂(默认工厂不能满足需求可以设置)
        @Override
        public void setFactory(IPresenterFactory<V, P> factory) {
            mFactory = factory;
            mPresenterStore = mFactory.createPresenterStore();
        }
    
        // 获取当前工厂
        @Override
        public IPresenterFactory<V, P> getFactory() {
            return mFactory;
        }
    
        // 获取Presenter
        @Override
        public P getPresenter(String key) {
            return mPresenterStore.get(key);
        }
    
        public void attachView(Context context, V view) {
            for (Map.Entry<String, P> entry : mPresenterStore.getStore().entrySet()) {
                P presenter = entry.getValue();
                presenter.attachView(context, view);
            }
        }
    
        public void detachView() {
            for (Map.Entry<String, P> entry : mPresenterStore.getStore().entrySet()) {
                P presenter = entry.getValue();
                presenter.detachView();
            }
        }
    
        public void release() {
            mPresenterStore.clear();
        }
    
    10.BaseActivity

    因为有了Presenter代理类,BaseActivity只需要关注Presenter代理类就可以了,无需关注Presenter工厂和PresenterStore等。

    不同之处是getPresneter()需要传入key,在Activity销毁时,要调用Presenter代理类的release()函数释放PresenterStore。

    public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V>> extends AppCompatActivity
            implements IPresenterProxy<V, P> {
    
        private PresenterProxyImpl<V, P> mPresenterProxy;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutId());
            getWindow().getDecorView().post(new Runnable() {
                @Override
                public void run() {
                    initPresenterProxy();
                    initView();
                    initData();
                }
            });
        }
    
        // 初始化Presenter代理类
        private void initPresenterProxy() {
            mPresenterProxy = new PresenterProxyImpl<>(PresenterFactoryImpl.<V, P>createFactory(getClass()));
            mPresenterProxy.attachView(this, (V) this);
        }
    
        @Override
        public void setFactory(IPresenterFactory<V, P> factory) {
            mPresenterProxy.setFactory(factory);
        }
    
        @Override
        public IPresenterFactory<V, P> getFactory() {
            return mPresenterProxy.getFactory();
        }
    
        @Override
        public P getPresenter(String key) {
            return mPresenterProxy.getPresenter(key);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mPresenterProxy.detachView();
            mPresenterProxy.release();
        }
    
        protected abstract int getLayoutId();
    
        protected abstract void initView();
    
        protected abstract void initData();
    }
    

    以上就是经过改良的代码,本质上就是通过一个集合来维护多个Presenter,以此达到一个Activity/Fragment持有多个Presenter的目的。

    如何使用

    接下来还是以一个小Demo介绍使用方式。

    1.编写MVP的Model
    public class LoginModel {
    
        private static final String USERNAME = "12345";
        private static final String PASSWORD = "67890";
    
        public boolean isLoginSuccess(String username, String password) {
            if (USERNAME.equals(username) && PASSWORD.equals(password)) {
                return true;
            }
            return false;
        }
    }
    
    public class RegisterModel {
    
        public boolean isRegisterSuccess(String username, String password) {
            return true;
        }
    }
    
    2. 编写MVP的View
    public interface ILoginView extends BaseView {
    
        void onLoginSuccess();
    
        void onLoginFail();
    }
    
    public interface IRegisterView extends BaseView {
    
        void onRegisterSuccess();
    
        void onRegisterFail();
    }
    
    3.编写MVP的Presenter
    public class LoginPresenter extends BasePresenter<ILoginView> {
    
        private LoginModel mLoginModel;
    
        public LoginPresenter() {
            mLoginModel = new LoginModel();
        }
    
        public void login(String username, String password) {
            if (!isAttachView()) {
                return;
            }
    
            boolean isSuccess = mLoginModel.isLoginSuccess(username, password);
    
            if (isSuccess) {
                getView().onLoginSuccess();
            } else {
                getView().onLoginFail();
            }
        }
    
    public class RegisterPresenter extends BasePresenter<IRegisterView> {
    
        private RegisterModel mRegisterModel;
    
        public RegisterPresenter() {
            mRegisterModel = new RegisterModel();
        }
    
        public void register(String username, String password) {
            if (!isAttachView()) {
                return;
            }
    
            boolean isSuccess = mRegisterModel.isRegisterSuccess(username, password);
    
            if (isSuccess) {
                getView().onRegisterSuccess();
            } else {
                getView().onRegisterFail();
            }
        }
    }
    
    4.编写xml布局文件
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <android.support.v7.widget.AppCompatEditText
            android:id="@+id/edit_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            app:layout_constraintTop_toTopOf="parent" />
    
        <android.support.v7.widget.AppCompatEditText
            android:id="@+id/edit_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toBottomOf="@id/edit_username" />
    
        <android.support.v7.widget.AppCompatButton
            android:id="@+id/btn_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="Login"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/edit_password" />
    
        <android.support.v7.widget.AppCompatButton
            android:id="@+id/btn_register"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="Register"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/edit_password" />
    
    </android.support.constraint.ConstraintLayout>
    
    5. 编写MainActivity
    @RequestPresenter(presenter = {LoginPresenter.class, RegisterPresenter.class})
    public class MainActivity extends BaseActivity implements ILoginView, IRegisterView {
    
        private AppCompatEditText mUsernameEdit;
        private AppCompatEditText mPasswordEdit;
        private AppCompatButton mLoginBtn;
        private AppCompatButton mRegisterBtn;
    
        @Override
        protected int getLayoutId() {
            return R.layout.activity_main;
        }
    
        @Override
        protected void initView() {
            mUsernameEdit = findViewById(R.id.edit_username);
            mPasswordEdit = findViewById(R.id.edit_password);
            mLoginBtn = findViewById(R.id.btn_login);
            mRegisterBtn = findViewById(R.id.btn_register);
    
            // 点击按钮时执行登录
            mLoginBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String username = mUsernameEdit.getText().toString();
                    String password = mPasswordEdit.getText().toString();
                    getLoginPresenter().login(username, password);
                }
            });
    
            // 点击按钮时执行注册
            mRegisterBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String username = mUsernameEdit.getText().toString();
                    String password = mPasswordEdit.getText().toString();
                    getRegisterPresenter().register(username, password);
                }
            });
        }
    
        // 获取LoginPresenter
        private LoginPresenter getLoginPresenter() {
            return (LoginPresenter) getPresenter(LoginPresenter.class.toString());
        }
    
        // 获取RegisterPresenter
        private RegisterPresenter getRegisterPresenter() {
            return (RegisterPresenter) getPresenter(RegisterPresenter.class.toString());
        }
    
        @Override
        protected void initData() {
    
        }
    
        @Override
        public void onLoginSuccess() {
            Toast.makeText(this, "登录成功!!!", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onLoginFail() {
            Toast.makeText(this, "登录失败!!!", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onRegisterSuccess() {
            Toast.makeText(this, "注册成功!!!", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onRegisterFail() {
            Toast.makeText(this, "注册失败!!!", Toast.LENGTH_SHORT).show();
        }
    }
    

    运行效果如下:

    Login.png Register.png

    完结

    项目地址:https://github.com/zhangjunxiang1995/Application/tree/master/mvp2

    相关文章

      网友评论

        本文标题:进阶MVP设计模式之二

        本文链接:https://www.haomeiwen.com/subject/emwuhctx.html