栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > 架构设计

Android MVP★★★★★

架构设计 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android MVP★★★★★

1.为什么需要一个清晰的软件架构
避免创建神类。避免创建无所不知、无所不能的上帝类。如果一个类需要花费时间从其他类中通过Get()和Set()检索数据(也就是说,需要深入业务并且告诉它们如何去做),那么是否应该把这些功能函数更好的组织到其它类而不是上帝类中。

上帝类的维护成本很高,你很难理解正在进行的操作,并且难以测试和扩展,这就是为什么要避免创建上帝类的黄金法则。
然而,在Android开发中,如果不考虑架构的话,Activity类往往会越来越大。这是因为,在Android中允许View和其它线程共存于Activity内。其实最大的问题莫过于在Activity中同时存在业务逻辑和UI逻辑,这会增加测试和维护的成本。

这是为什么需要清晰架构的原因之一。不仅会造成Activity的臃肿,还会引起其他问题,如使Activity和Fragment的生命周期变复杂以及数据绑定等。

2.MVP

MVP代表Model、View和Presenter。

View层负责处理用户事件和视图部分的展示。在Android中,它可能是Activity或者Fragment类。

Model层负责访问数据。数据可以是远端的Server API、本地数据库或者SharedPreferences等。

Presenter层是连接(或适配)View和Model的桥梁。

View从不直接与Model通信。

MVP模式可以分离显示层和逻辑层,所以功能接口如何工作与功能的展示可以实现分离,MVP模式理想化地可以实现同一份逻辑代码搭配不同的显示界面。

下图是基于MVP架构的模式之一。
View是UI线程。Presenter是View与Model之间的适配器。UseCase或者Domain在Model层中,负责从实体获取或载入数据。
依赖规则如下:

关键是,高层接口不知道底层接口的细节,或者更准确地说,高层接口不能,不应该,并且必须不了解底层接口的细节,是(面向)抽象的,并且是细节隐藏的。

同心圆将软件划分为不同的区域,一般的,随着层级的深入,软件的等级也就越高。外圆是实现机制,内圆是核心策略。
①Entities:
可以是一个持有方法函数的对象
可以是一组数据结构或方法函数
它并不重要,能在项目中被不同应用程序使用即可
②Use Cases
包含特定于应用程序的业务规则
精心编排流入Entity或从Entity流出的数据
指挥Entity直接使用项目范围内的业务规则,从而实现Use Case的目标
③Presenters
将Use Case和Entity中的数据转换成格式最方便的数据,使外部系统,如数据库或网页能够方便地使用这些数据
④External Interfaces, UI, DB
所有的细节所在,如数据库细节,Web框架细节等等。

view只是负责处理事件监听或者展示每个视图组件,所有的业务逻辑必须委托给Presenter类。在MVP中,View和Presenter是一 一对应的(在MVVM中是一对多的)

MVP的优缺点:
①优点
耦合降低,Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity、Fragment等关联, 便于写单元测试。
MVP很好的体现了单一职责的原则,并且严格分为三层,即使后期业务变多,结构仍然清晰,非常利于项目后期的维护。当有新的需求时,只需将相关的需求写在接口,然后实现这个接口,无需顾及之前的结构,免去几方面去调整的头痛。另外,还能更好的对接口代码进行单元测试。
②缺点
使用MVP模式构建项目,会造成类文件和接口文件过多,进而增大包的体积。解决办法的话,可以采用官方的做法,写一个Contract接口,然后把与MVP三层的相关接口全部列入到里面去,类似下面这种:
public interface Contract {
public interface IModel {
xxx;
xxx;
}
public interface IPresenter {
xxx;
xxx;
}
public interface IView {
xxx;
xxx;
}
}
除此之外,还可能内存泄漏的问题。用户关闭了View层,但这时Model层如果仍然在进行耗时操作,因为Presenter层也持有View层的引用,所以造成垃圾回收器无法对View层进行回收,这样一来,就造成了内存泄漏。这里可以重写onDestroy()方法,在View销毁时强制回收掉Presenter。还有一个解决办法就是采用弱引用的方式,如下:
WeakReference< xxx> refrence = new WeakRefrence<>(this);
// 使用时直接就能获得对象的引用
reference.get();
然后在引用进行引用之前,都需要判断引用不为空,以防止空指针异常。

3.MVP与MVC比较
MVP其实就是从MVC模式演化产生的,先看一下著名的MVC模式:
View:对应于布局文件
Model:业务逻辑和实体模型
Controller:控制器,Android中对应于Activity
对应的MVC交互图如下:

虽然Android系统应用开发本身是遵循MVC开发模式的,但是我们仔细看一下View层和Activity,具体view布局文件中的数据绑定和事件处理的方法代码都是冗余在Activity中的,所以我们经常可以看到Activity类动不动就是少则九百行,多则上千甚至几千行。
现在来看一下MVP模式,MVP模式会引入 Presenter层,完成View层和Model层的交互,那么具体MVP对应如下:
View:View通常来说是由Activity实现的,它会包含一个Presenter的引用,View要做的就只是在每次有接口调用的时候(比如按钮点击后)调用Presenter的方法。
Model:业务逻辑和实体模型
Presenter:主要作为沟通View和Model的桥梁,它从Model层检索数据后,返回给View层,但是不像MVC结构,因为它也可以决定与View层的交互操作。
MVP的数据交互图如下:
我们来具体看一下下面两张对比,就可以看来具体区别了:

MVP与MVC唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成, Presenter主要作为View和model交互的一个纽带,扮演 “主持交互”的角色,处理交互逻辑。比如model只负责网络的请求、pesenter负责处理请求网络后的数据处理:加载中 成功 or 失败 取消加载;最后View进行界面的展示。还有一点就是Presenter与View之间的交互是通过接口的。
MVC一个致命缺点,就是在android中由于activity(god object)的存在,Controller和View很难做到完全解耦。但在MVP中就可以很好的解决这个问题

4.MVP举例

以登录为例使用MVP。

使用MVP的好处之一就是模块职责划分明显,层次清晰。 该例的结构图即可展现此优点:

(1)Model层

Model层负责对从登录页面获取帐号、密码进行验证(一般需要请求服务器进行验证,本例直接模拟这一过程)。 从上图的包结构图中可以看出,Model层包含内容:

①实体类bean。

②接口类,表示Model层所要执行的业务逻辑。

③接口实现类,具体实现业务逻辑,包含的一些主要方法。

下面以代码的形式一一展开。

①实体类bean

public class User {

    private String password;

    private String username;

    public String getPassword() {

        return password;

    }

    public void setPassword(String password) {

        this.password = password;

    }

    public String getUsername() {

        return username;

    }

    public void setUsername(String username) {

        this.username = username;

    }

}

封装了用户名、密码,方便数据传递。

②接口

public interface LoginModel {

    void login(User user, OnLoginFinishedListener listener);

}

其中OnLoginFinishedListener是presenter层的接口,方便在Model中实现回调presenter,通知presenter业务逻辑的返回结果。

③接口实现类

public class LoginModelImpl implements LoginModel {

    @Override

    public void login(User user, final OnLoginFinishedListener listener) {

        final String username = user.getUsername();

        final String password = user.getPassword();

        new Handler().postDelayed(new Runnable(){

            @Override

            public void run() {

                boolean error = false;

                if (TextUtils.isEmpty(username)){

                    listener.onUsernameError();//在M层里回调P层的接口

                    error = true;

                }

                if (TextUtils.isEmpty(password)){

                    listener.onPasswordError();//在M层里回调P层的接口

                    error = true;

                }

                if (!error){

                    listener.onSuccess();//在M层里回调P层的接口

                }

            }

        }, 2000);

    }

}

实现Model层逻辑:延时模拟登陆(2s),如果用户名或者密码为空则登陆失败,否则登陆成功。无论登录失败或成功,都会回调P层的接口,告诉P结果。

(2)View层

一般视图都只是包含用户界面(UI),而不包含界面逻辑,界面逻辑由Presenter来实现。

从上图的包结构图中可以看出,View包含内容:

①接口类,Presenter与View交互是通过接口。其中接口中方法的定义是根据Activity用户交互需要展示的控件确定的。

②接口实现类,将上述定义的接口中的方法在Activity中对应实现具体操作。

下面以代码的形式一一展开。

 ①接口

public interface LoginView {

    //login是个耗时操作,需要给用户一个友好的提示,一般就是操作ProgressBar

    void showProgress();

    void hideProgress();

   //login存在登录成功与失败的处理,失败给出提示

    void setUsernameError();

    void setPasswordError();

   //login成功,也给个提示

    void showSuccess();

}

这5个方法都是presenter根据model层返回结果需要view执行的对应的操作。

②接口实现类

即对应的登录Activity,需要实现LoginView接口。

public class LoginActivity extends AppCompatActivity implements LoginView, View.OnClickListener {

    private ProgressBar progressBar;

    private EditText username;

    private EditText password;

    private LoginPresenter presenter;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_login);

        progressBar = (ProgressBar) findViewById( R.id.progress);

        username = (EditText) findViewById( R.id.username);

        password = (EditText) findViewById( R.id.password);

        findViewById(R.id.button).setOnClickListen er(this);

       //创建一个presenter对象,当点击登录按钮时,让presenter去调用model层的login()方法,验证帐号密码

        presenter = new LoginPresenterImpl(this);

    }

    @Override

    protected void onDestroy() {

        presenter.onDestroy();

        super.onDestroy();

    }

    @Override

    public void showProgress() {

        progressBar.setVisibility(View.VISIBLE);

    }

    @Override

    public void hideProgress() {

        progressBar.setVisibility(View.GONE);

    }

    @Override

    public void setUsernameError() {

        username.setError("username_error");

    }

    @Override

    public void setPasswordError() {

        password.setError("password_error");

    }

    @Override

    public void showSuccess() {

         progressBar.setVisibility(View.GONE);

        Toast.makeText(this,"login success",Toast.LENGTH_SHORT).show();

    }

    @Override

    public void onClick(View v) {

        User user = new User();

        user.setPassword( password.getText().toString());

        user.setUsername( username.getText().toString());

        presenter.login(user);

    }

}

View层实现Presenter层需要调用的控件操作,方便Presenter层根据Model层返回的结果进行操作View层进行对应的显示。

(3)Presenter层

Presenter是Model和View之间交互的桥梁。 从上图的包结构图中可以看出,Presenter包含内容:

①接口,包含Presenter需要进行Model和View之间交互逻辑的接口,以及上面提到的Model层数据请求完成后回调的接口。

②接口实现类,即实现具体的Presenter类逻辑。

下面以代码的形式一一展开。

①接口

public interface OnLoginFinishedListener {

    void onUsernameError();

    void onPasswordError();

    void onSuccess();

}

当Model层得到请求的结果,需要回调Presenter层,让Presenter层调用View层的接口方法。

public interface LoginPresenter {

    void login(User user);

    void onDestroy();

}

登陆的Presenter 的接口,实现类为LoginPresenterImpl,完成登陆的验证,以及销毁当前view。

②接口实现类

public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {

    private LoginView loginView;

    private LoginModel loginModel;

    public LoginPresenterImpl(LoginView loginView) {

        this.loginView = loginView;

        this.loginModel = new LoginModelImpl();

    }

    @Override

    public void login(User user) {

        if (loginView != null) {

            loginView.showProgress();

        }

        loginModel.login(user, this);//P中调用M的接口方法

    }

    @Override

    public void onDestroy() {

        loginView = null;

    }

    @Override

    public void onUsernameError() {

        if (loginView != null) {

            loginView.setUsernameError();//P中调用V层的接口方法

            loginView.hideProgress();

        }

    }

    @Override

    public void onPasswordError() {

        if (loginView != null) {

            loginView.setPasswordError();

            loginView.hideProgress();

        }

    }

    @Override

    public void onSuccess() {

        if (loginView != null) {

            loginView.showSuccess();

        }

    }

}

由于presenter完成二者的交互,那么肯定需要二者的实现类(通过传入参数,或者new)。

presenter里面有个OnLoginFinishedListener, 其在Presenter层实现,给Model层回调,更改View层的状态, 确保 Model层不直接操作View层。

通过这个例子可以看出MVP模式的整个核心流程:

View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有View层的Interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层再调用View层的接口将加载后的数据展示给用户。

 

5.MVP中使用Contract

由于MVP在实现代码简洁的同时,额外增加了大量的接口、类,不方便进行管理,于是Contract就登场了。

Contract是一个契约,将Model、View和Presenter进行约束管理,方便后期类的查找、维护。

还是以上个例子为例:

①首先,创建一个登陆的Contract:

public interface LoginContract {

    interface ILoginModel { }

    interface ILoginView { }

    interface ILoginPresenter { }

    interface OnLoginFinishedListener { }

}

②其次创建Presenter、Model、View对应Contract中的接口;

public class LoginPresenterImpl implements LoginContract.ILoginPresenter, LoginContract.OnLoginFinishedListener{

}

public class MainModel implements MainContract.Model{

}

public class MainActivity implements MainContract.View {

}

架构搭起来了,然后写具体内容。完整的Contract:

public interface LoginContract {

    interface ILoginModel {
        void login(User user, OnLoginFinishedListener listener);

    }

    interface ILoginView {

        void showProgress();

        void hideProgress();

        void setUsernameError();

        void setPasswordError();

        void showSuccess();

    }

    interface ILoginPresenter {

        void login(User user);

        void onDestroy();

    }

    interface OnLoginFinishedListener {

        void onUsernameError();

        void onPasswordError();

        void onSuccess();

    }

}

LoginContract就是将所有的接口都定义在这一个类里,从而减少了类的个数,而model,view和presenter对应的实现类不变。

在LoginContract中:

Model接口:创建登录请求的具体实现逻辑,将Presenter提交的字段放到联网请求中,发送给服务器;

View接口:创建在界面上显示加载中、取消加载以及登陆成功、失败的方法;

Presenter接口:创建登陆的方法,以及需要提交的字段 (username、password);

MainPresenter实现MainContract.Presenter 接口中的 login(String username, String password) 方法;实例化Model,在MainPresenter login(String username, String password)方法中,调用model的网络请求,将username、password放在model的login()方法中,进行请求服务器。请求服务器前 使用MainContract.View中的 mView.showLoading()方法,进行显示加载中;在成功失败的回调中,使用对应的方法,以及取消加载。
其中BasePresenter、BaseView 是对Presenter以及View进行的封装。

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/951404.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号