美文网首页安卓Android架构
Android MVP从懵逼到入门:登陆业务实践

Android MVP从懵逼到入门:登陆业务实践

作者: 星际之痕 | 来源:发表于2016-07-13 10:47 被阅读6176次

MVP简介

最近几天在啃MVP,现在的你或许跟几天前的我一样,对MVP还是一脸懵逼,虽然MVP三个字母都认识,但连在一起却不明白到底是个什么东东,没关系,快来干了这碗鸡汤,立马从懵逼到入门,入不了门你来打我,文末统计人数。


首先,MVP是一种设计模式,或者说架构。Google把它列入Android Architecture Blueprints--Android 架构蓝图,并给出了官方的例子来解释如何实现MVP。本文实现的登陆功能就是根据MVP基础架构Demo来实现的。

其次,MVP从何而来?想必都知道是MVC的演化版本,现在比较流行,被广大开发者所认可。被认可的原因我在这里总结一下:

  • 代码清晰,容易理解(掌握MVP的前提下)
  • 简化了万能的Activity的逻辑
  • 解耦了View和Model
  • 方便单元测试

一直以来,在MVC模式中Activity的万能角色就备受诟病,一旦逻辑越来越复杂,Activity就越来越臃肿,承担越来越多的职责,代码阅读起来费劲,维护成本跟着提高,而且,在MVC模式中,View 和 Model直接交互,耦合度高,违背了软件开发“高类聚、低耦合”的设计目标--于是MVP横空出世。

关于MVP的理论知识就哔哔这些,想要详细了解的童鞋回头自己做功课去,这里不是重点,本文的重点是把MVP模式实践起来。

MVP实现登陆

mvp.png

这是google samples TODO-MVP项目中的MVP图解。因为本人学习MVP主要也是根据谷歌官方demo来的,所以,这个图直接拿来。这个图怎么看?

  • 分为左右两部分,左边没颜色的部分包含的内容是Model,这里面包含了数据实体模型、数据访问接口、SQLite数据库操作、网路数据操作、数据内存缓存,这些都是Model要做的事情,跟MVC没什么差别;

  • 右边...那个什么颜色的背景(色盲晚期),注意那个Activity,我们看到VIEW和PRESENTER都被放在了Activity里边,而VIEW的实现类在这里用的是Fragment;

  • 隐藏内容:这里面其实还有一个内容--契约类,也就是项目里的XXXContract.java类,是一个接口类,作用是定义VIEW接口和PRESENTER接口提供的接口方法。这个类原则上不属于MVP模式里的任何角色,所以没在上图出现,可以理解。

    先看看效果 不方便看视频的就看看图吧!
    [图片上传失败...(image-32673-1521790364530)]


[图片上传失败...(image-17e711-1521790364530)]

  • 1.创建View接口和Presenter接口基类
    BaseView作为View接口基类,定义了一个重要的接口:

     void setPresenter(T presenter);
    

[图片上传失败...(image-220445-1521790364530)]
[图片上传失败...(image-edaa6-1521790364530)]

这其实是MVP的一个关键点,通过这个接口,View的实现类(即Fragment)就持有了Presenter的实例,于是,View就可以通过Presenter来操作Model中的数据接口了。如果你要实现MVP模式,记住,不管三七二十一,先写这个基佬,哦,不对,是基类。

Presenter接口基类里同样定义了一个接口:

  void start();

这个方法就是直接操作Model的,比如加载数据。通过这两个基类的定义的接口就能看出,View和Model不直接交互,而是通过Presenter来操作,这是与MVC的不同之处。

  • 2.登陆契约类LoginContract
    public interface LoginContract {

    interface Presenter extends BasePresenter {
          void login();
          void reset();
    }
    
    interface View extends BaseView<Presenter> {
          String getUserEmail();
          String getPassword();
    
          boolean isEmailValid(String email);
          boolean isPasswordValid(String password);
    
          boolean setEmailError(String error);
          boolean setPasswordError(String error);
    
          void showLoginProgress(boolean show);
          void resetEditView();
          void toMainAct();
          void showFailedError();
      }
    }
    

    这个类是首次出现于google的mvp示例中,以前的MVP模式并未见到,这个类定义了View接口和Presenter接口为对方的实例提供的方法。

    比如,我在View中可以获取用户输入的邮箱和密码,判断邮箱密码是否有效,设置邮箱密码输入框错误提示信息,显示登陆ProgressBar等,同样,在Presenter接口中,提供了登陆和重置两个功能,用户通过View上的两个按钮,响应Presenter对应的接口,执行相关的业务逻辑。

    这个契约类的好处是方便接口统一管理、修改,同时,内容清晰,一目了然。

  • 3.View的实现类LoginFragment implements LoginContract.View
    实现接口定义的各个方法,必须持有Presenter,并通过接口

    void setPresenter(T presenter)
    

为其赋值。

注意官方的demo说明里有这段内容:

Note: in a MVP context, the term "view" is overloaded:

The class android.view.View will be referred to as "Android View"
The view that receives commands from a presenter in MVP, will be simply called "view".

我来献个丑,翻译一下:

注意:在MVP的上下文里,“view”一词有多重含义:

  • android.view.View被称为“Android View”
  • 在MVP中,从presenter接收命令的view将被简单地称为“view”。

什么意思?

我的理解是这样的:MVP中的VIEW由两部分组成,一个是view接口,比如上面的LoginContract.View接口;一个是该接口的实现类,比如上面的LoginFragment。view接口负责与presenter交互,presenter调用view接口定义方法来操作view的实现类;具体实现都是在android.view.View里实现的,即LoginFragment。

  • 4.Presenter的实现类LoginPresenter implements LoginContract.Presenter
    实现接口定义的各个方法,必须持有Model对象和View对象

    private final UserRepository mUserRepository;
    private final LoginContract.View mLoginView;
    

然后,你想让View干嘛,调用View相对应的接口就行了,想要什么数据,想对数据做什么操作,调用Model对象的对应方法就行了;或许你已经发现了:

Presenter对View的操作都是通过接口来完成的。

  • 5.Activity的角色,看Google官方Sample里怎么介绍的:

It uses fragments for two reasons:

The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters.
Tablet layout or screens with multiple views take advantage of the Fragments framework.

献丑二进宫:

(MVP中的View实现)使用Fragment有两个原因:

Activity与Fragment之间的分离很好的符合了MVP的实现:Activity作为整体控制器来创建和连接views与presenters。
Tablet布局或者屏幕上有多个views的布局可以很好的利用Fragments框架。

在MVP模式里,Activity的功能变得简单了很多,一是创建View布局(Fragment),二是实例化Presenter(LoginPresenter),并将View(Fragment)作为参数,传入到Presenter(LoginPresenter)中,在Presenter(LoginPresenter)构造函数中传递给Presenter(LoginPresenter)持有的View对象,然后View对象调用setPresenter方法,将自身this传递给View实例(Fragment)。也就是上面说的“创建和连接views与presenters。

来看看Activity代码多简单:
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);

        LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);        
        // Create the view        
        if (loginFragment == null) {            
            loginFragment = LoginFragment.newInstance("LOGIN_FRAGMENT");        
        }

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 
        transaction.add(R.id.contentFrame, loginFragment);        
        transaction.commit(); 
   
        // Create the presenter        
        new LoginPresenter(
                getApplicationContext(),                
                UserRepository.getInstance(
                    UserLocalDataSource.getInstance(getApplicationContext()),
                    UserRemoteDataSource.getInstance()),                
                loginFragment);    
    }
  }

至此,MVP模式里的VP就可以运行起来了,连通起来了。下面来说说Model。如果对MVC的Model非常熟悉可以跳过。

  • 6.Model的创建
    在目录结构图中,整个data package里的都是Model的内容,包括实体模型(User类)、本地数据库操作(local package)、远程数据访问(remote package)三部分,跟在MVC里并无差别,这里不展开介绍。

    业务逻辑需要什么样的数据实体、数据操作,在对应的包里面构建就行了,这里要提到的是,Presenter对Model的持有,这里也是通过接口实现的,间接通过UserDataSource接口类,直接通过UserDataSource的实现类UserRepository。而UserRepository同时持有对本地数据和远程数据操作的对象:
    private final UserDataSource mLocalDataSource;
    private final UserDataSource mRemoteDataSource;

MVP实践总结

使用MVP模式也有一些不尽如人意的地方,比如,类和接口变多了,代码也多了,项目大了可能会不好管理,但这都不是事,用多点代码、多点类文件换取低耦合度、结构清晰、容易理解、易扩展的架构,这买卖值了。

第一遍看的时候懵逼没关系,再看一遍,认真的解读MVP的设计思路,参考代码,发现其实真的是很清晰的思路,并不难,难的是啃下来的决心。这碗鸡汤我干了,你们随意。

相信看完这篇文章会对你理解MVP有所帮助,如果你还是一脸懵逼的话,请举起手来:
[图片上传失败...(image-e459b1-1521790364530)]

最后,奉上本文的源码GitHub:Login-MVP-Architecture,如果觉得有用,点个Star表示支持。

相关文章

网友评论

  • 执着的小虫子:MVVM可以写一个吗?
  • Zomll:这种逻辑判断为啥要写在view 层啊 ,,,老铁,,我没看明白!~~~


    @Override
    public boolean isEmailValid(String email) {
    return email.contains("@");
    }

    @Override
    public boolean isPasswordValid(String password) {
    return password.length() > 4;
    }
    920611c8bbbb:你说mvp中解耦了View和Model,想问下mvc中的View和Model是怎么耦合的呢?
    星际之痕:@Zomll 这是判断view上控件内容的逻辑,放在view层合理啊!
  • panw3i:BaseView 有什么啊?直接在协议类里面定义完了了不就行了吗?
  • bc1ff59c5b30:手机刷文,开头的图片有点大啊
    星际之痕:@thevim 大也是值得的:smile:
  • 梨落520:麻痹,看了本篇文章终于明白mvp是咋回事了,楼主思路真是厉害,一步紧扣一步,他妹这是看小说啊,必须来一波666! 啤酒、饮料、八宝粥已买好 请继续你的表演。
    星际之痕:@梨落520 表酱
  • OvenChou:不得不说,感觉好一点了,看了很多都蒙,就你的舒服👍👍👍
  • Draper:Presenter接口基类里同样定义了一个接口:
    void start();

    请问这个 start(); 的作用是什么
    Draper:@星际之痕 哦哦,谢谢 😜
    星际之痕:@Draper 一个示例的接口方法
  • ef3b5ff7019d:程序界说相声的 :+1:
    星际之痕:@Beanu 我有有这么牛逼?:smile:
  • 抚一曲天涯:蒙蔽了,能不能搞个一加一的例子
    星际之痕:@Tiger_Hu 叫网红
    Tiger_Hu:@星际之痕 上面那女的叫什么名字
  • Android之路:LZ你好,我仿照你的写了个简单的登录Demo 结果一直报空指针~想了半天不知道为啥 求解~
    Android之路:@星际之痕 Activity中 我也new LoginPresenter了
    Android之路:@星际之痕 在fragment中 我调用Presenter.login()方法 结果说空指针 问题是我在setPresenter中获取LoginContract.Presenter不为null. 我现在一直很烦恼到底是那块出现问题了.博主你的QQ多少 我可以加你吗
    .
    星际之痕:那就看看空指针在哪报的啊!
  • Wang_Guan:还是一脸懵逼!。。。
    星际之痕:@Kenny_Orz :joy:相信你,你可以的!:joy:
  • 捡淑:马克
    捡淑:@星际之痕 。。。
    星际之痕:@捡淑 头像也是醉了……
  • 83b7dcaa63cf:感谢分享
  • 木子v大可:我也蒙逼中
  • 筱南独舞:最近打算转MVP,学习了
    星际之痕:@筱南独舞 一起学习
  • 王元_Trump:我是冲着图片来的
    星际之痕:@王元_Trump 拿去吧:smile:
  • 023e483cd8cd:正在懵逼…
    星际之痕:@i_fuqiang :joy:
  • 星际之痕:"foo@example.com:hello", "bar@example.com:world"这两个账号可以登录成功,其他登录失败。(分号左右分别是邮箱、密码’)

本文标题:Android MVP从懵逼到入门:登陆业务实践

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