Android实战 | 详解MVC、MVP模式并分别实现登录界面案例

简介: Android实战 | 详解MVC、MVP模式并分别实现登录界面案例

参考资料(《(菜鸟窝)安卓进阶必学》)

本文参考技术资料做一个笔记,主要内容是总结MVC、MVP两个设计模式的思想,以及分别运用这两个模式的实现,实现两个project(MVCSmallTest还有MVPTest),内容都是登录界面。

文章主要内容摘要:

  • MVC模式的分析和实战
  • MVP模式的分析和实战
  • MVP模式下多个Activity情况下的接口抽取

实战案例效果如下:输入正确的密码并点击登录按钮时,Toast“登录成功”,若密码或账号错误,则Toast“登录失败”,若全部输入,则Toast“用户名和密码不能为空”:

MVC模式

View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入(比如EditText.getText().toString())
Model层就是JavaBean实体类,用于保存实例数据
Controller控制器用于更新UI界面和数据实例

特点:
  • 使用多
  • 软件开发最早使用的设计模式
  • xml做View层,Activity做C层
弊端:Activity既是V又是C,UI逻辑和业务逻辑都写在一块(Activity.java 中),没有实现V和C的分离;



下面用MVC模式编写本例子

MainActivity.java:

package com.lwp.mvcsmalltest;

import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userName = et_username.getText().toString();
                String pwd = et_pwd.getText().toString();
                if(!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(pwd)){
                    login(userName,pwd);
                }else{
                    Toast.makeText(MainActivity.this,"用户名和密码不能为空",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private void login(final String userName, final String pwd) {
        //开一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);//沉睡两秒模拟登录
                //runOnUiThread切回主线程更新UI
                if(userName.equals("jiangxue") && pwd.equals("666666")){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
                        }
                    });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录失败",Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"/>

    <EditText
        android:id="@+id/et_pwd" 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"/>

</LinearLayout>

**刚刚说过View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入;
Controller控制器用于更新UI界面和数据实例,
由此我们可以分析MainActivity.java得到以下的结果:MainActivity既是V又是C,没有实现V和C的分离;业务逻辑(本例中即login()跟UI逻辑都写在一个Activity里面),**这样写毫无疑问很冗杂,对于简单的项目也许没什么影响和明显的弊端,甚至显得方便,但是一旦项目大了,这样写会使可读性非常低,不利于项目后期的诸多工作;

到此,我们便用MVC模式完成了登录界面小案例;
下面分析MVP模式



MVP模式


presenter   n.节目主持人,演播员; 推荐者; 提出者; 赠送者

MVP模式的核心思想:

对比与MVC,把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model类。

作用:
  1. 分离视图逻辑和业务逻辑,降低耦合;
  2. Activity只处理生命周期的任务,代码简洁;
  3. 视图逻辑和业务逻辑抽象到了View和Presenter中,提高阅读性;
  4. Presenter被抽象成接口,可以有多种具体的实现
  5. 业务逻辑在Presenter中,避免后台线程引用Activity导致内存泄漏

下面从零到一开始实战:
首先新建一个项目,在主包下创建三个包(即model、presenter、view),待会儿用于存放MVP三层各自的代码。然后把MainActivity.java拉进view包:

接下来我们把方才的几个UI逻辑都抽象成View接口,方才哪几个UI逻辑呢?就登陆成功、登录失败、弹出toast等这些个UI逻辑了:

把UI逻辑抽象成BaseView接口:
package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
把业务逻辑抽象成Presenter接口:

presenter/BasePresenter.java:

package com.lwp.mvptest.presenter;

import android.provider.UserDictionary;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}

model/User.java:

package com.lwp.mvptest.model;

/**
 * Created by 700 on 2019/1/12.
 */

public class User {

    private String name;
    private String pwd;

    public User(String name, String pwd){
        this.name = name;
        this.pwd = pwd;
    }

    public String getName(){return name;}

    public void setName(String name) {this.name = name;}

    public String getPwd() {return pwd;}

    public void setPwd(String pwd) {this.pwd = pwd;}

}

此时目录:

编写MainActivity.java:
实例化各组件,实例化model类对象,实现UI逻辑接口:

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }
}

presenter包下再创建一个 MainPresenter(Implement) 类,书写业务逻辑,实现业务逻辑接口:

这里,
我们在 presenter包下写好 抽象的业务逻辑接口BasePresenter
  在接口里面定义好 抽象的业务逻辑方法
然后在 presenter包下又写下一个对应 抽象的业务逻辑接口BasePresenter业务逻辑实现类MainPresenter
  用于实现对应的接口;
这样子,我们便把 业务逻辑抽象出来,实现在 业务逻辑实现类中,
到时候 Activity.java中要使用对应的 业务逻辑的时候,
只需要简简单单 实例化一个对应的 业务逻辑实现类对象
用它调用一个 自定义方法(如下面的attachView())
Activity的this指针(也即activity本身)赋给 业务逻辑实现类对象中的全局变量
之后即可用这个 业务逻辑类对象去调用 实现类中对应的业务逻辑方法
接收对应的 数据,实现 对应的业务逻辑

也就是,
现在activity要使用业务逻辑的话就不用再在写具体的业务逻辑了,
抽象地说,可以说只要三行代码;
**第一行实例化业务逻辑实现类的对象,
第二行绑定this和业务逻辑实现类的对象,
第三行使用对象并以相关数据为参数调用相关的业务逻辑方法实现即可;**

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements BasePresenter {

    private BaseView baseView;

    @Override
    public void attachView(BaseView v) {
        this.baseView = v;//绑定的时候把view置进来
    }

    @Override
    public void detachView() {
        baseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                baseView.loginSuccess("登陆成功");
            }else {
                baseView.loginFailed("登录失败");
            }
        }else{
            baseView.showToast("用户名或密码不能为空");
        }
    }
}

接下来修改MainActivity,实例化业务逻辑类对象,绑定View(以及解绑逻辑),传入数据供给并调用业务逻辑方法:

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.lwp.mvptest.R;
import com.lwp.mvptest.model.User;
import com.lwp.mvptest.presenter.MainPresenter;

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

    private MainPresenter mainPresenter;//声明业务逻辑类

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        mainPresenter = new MainPresenter();//实例化业务逻辑类对象
        mainPresenter.attachView(this);//绑定view(把this付给业务逻辑类中的全局变量,
                                        //业务逻辑类中的逻辑方法会使用到这个全局变量(传进去的this),
                                        // 从而具体实现业务逻辑类中的业务逻辑)

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
                mainPresenter.login(user);//传入数据,调用业务逻辑方法
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }

    //销毁状态周期时解除绑定
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mainPresenter.detachView();
    }
}

成功运行:

此时目录如下:
**小结:
User 用于存储数据;
BasePresenter是业务逻辑接口抽象;
MainPresenter实现业务逻辑接口;
BaseView是抽象的UI逻辑接口,在MainActivity中实现;
MainActivity统筹所有;**

至此我们其实便用MVP模式完成了登录界面小案例;


MVP模式下多个Activity情况下的接口抽取

假如我们项目中再来一个OtherActivity,实现功能如此如此(见下方代码注释):

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);
        
        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }
}

我们按照上面的思路给它抽象出对应的逻辑接口和逻辑实现类:

UI逻辑接口OtherBaseView:

package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

业务逻辑接口OtherPresenter:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter {
    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
    
    void uploadImage(String path);
}

业务逻辑接口实现类OtherPresenterImpl:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter {
    @Override
    public void attachView(OtherBaseView v) {
        
    }

    @Override
    public void detachView() {

    }

    @Override
    public void uploadImage(String path) {

    }
}

OtherActivity,实现OtherBaseView:

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity implements OtherBaseView{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }

    @Override
    public void showToast(String msg) {
        
    }

    @Override
    public void showProgress(int progress) {

    }
}

此时项目结构:

UI逻辑接口抽取

我们观察一下,OtherBaseView和BaseView有什么不同,

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
--------------------------------------
public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

可以发现统一地都有一个showToast()方法(通过这个案例,我们可以体会开发过程中的一些相应的场景——两个抽象接口具有相同的方法时候),

这样的话,我们可以对这两个接口进行抽取
(抽取像我们数学表达式中的提公因式,是普适而重要的一环),

下面在View包下新建一个MainBaseView,代替原来BaseView的位置(MainActivity的UI逻辑接口),
抽取相同的部分放在BaseView中,MainBaseView和OtherBaseView继承BaseView:

public interface BaseView {
    void showToast(String msg) ;
}
------------------
package com.lwp.mvptest.view;

/**
 * 只负责MainActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBaseView extends BaseView{
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}

------------------
package com.lwp.mvptest.view;

/**
 * 只负责OtherActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView extends BaseView{
    void showProgress(int progress);
}

最后记得public class MainActivity extends AppCompatActivity implements MainBaseView


业务逻辑接口的抽取

我们观察一下BasePresenter和OtherPresenter的区别:

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}
------------------
public interface OtherPresenter {

    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void uploadImage(String path);
}

我们可以将attachView()以及detachView()抽取出来,放在BasePresenter中,新建一个MainBasePresenter,让其和OtherPresenter 继承自BasePresenter:

public interface BasePresenter<T> {

    void attachView(T v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
}
--------------------------------------
/**
 * 只为MainActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBasePresenter extends BasePresenter<MainBaseView>{
    void login(User user);
}
--------------------------------------
/**
 * 只为OtherActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter extends BasePresenter<OtherBaseView>{
    void uploadImage(String path);
}

注意这里的BasePresenter要用泛型进行定义,因为下面的子业务逻辑模块(如MainBasePresenter )都需要用attachView()绑定对应的UI逻辑接口(如MainBaseView),所以这里使用泛型,子逻辑模块在继承时可以动态匹配。

接下来进入MainPresenter,进行代码的修改,
删掉下面这一段,然后更改implement到MainBasePresenter,然后Alt+Enter自动生成对应方法,最后修改类中对应的内容:

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;
import com.lwp.mvptest.view.MainBaseView;

/**
 *MainActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements MainBasePresenter {

    private MainBaseView mainBaseView;

    @Override
    public void attachView(MainBaseView v) {
        this.mainBaseView = v;
    }

    @Override
    public void detachView() {
        mainBaseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                mainBaseView.loginSuccess("登陆成功");
            }else {
                mainBaseView.loginFailed("登录失败");
            }
        }else{
            mainBaseView.showToast("用户名或密码不能为空");
        }
    }
}

最后为各个文件添加注释...

/**
 * OtherActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter...

到此,逻辑编写便完毕了,这里的Other系列都是作为演绎作用的空模板,暂时没有实现具体的功能,

最后成功运行:


相关文章
|
13天前
|
存储 前端开发 测试技术
MVC、MVP、MVVM 模式
MVC、MVP 和 MVVM 是三种常见的软件架构模式,用于分离用户界面和业务逻辑。MVC(Model-View-Controller)通过模型、视图和控制器分离数据、界面和控制逻辑;MVP(Model-View-Presenter)将控制逻辑移到 Presenter 中,减少视图的负担;MVVM(Model-View-ViewModel)通过数据绑定机制进一步解耦视图和模型,提高代码的可维护性和测试性。
|
16天前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
15 3
|
2月前
|
设计模式 开发框架 前端开发
MVC 模式在 C# 中的应用
MVC(Model-View-Controller)模式是广泛应用于Web应用程序开发的设计模式,将应用分为模型(存储数据及逻辑)、视图(展示数据给用户)和控制器(处理用户输入并控制模型与视图交互)三部分,有助于管理复杂应用并提高代码可读性和维护性。在C#中,ASP.NET MVC框架常用于构建基于MVC模式的Web应用,通过定义模型、控制器和视图,实现结构清晰且易维护的应用程序。
46 2
|
2月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
135 9
|
25天前
|
Android开发
Android实战之如何快速实现自动轮播图
本文介绍了在 Android 中使用 `ViewPager2` 和自定义适配器实现轮播图的方法,包括添加依赖、布局配置、创建适配器及实现自动轮播等步骤。
20 0
|
2月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
31 3
|
27天前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
18 0
|
2月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
109 1
|
1月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
33 0
|
XML 前端开发 数据处理
Android——MVC、MVP、MVVM框架实现登录示例
MVC 描述 缺点 优点 MVP 效果图 描述 缺点 优点 代码解析 视图效果图 建立实体类 建立实体类接口 实现实体类接口 设置P层 建立交互接口 数据绑定 MVVM 效果图 描述 代码解析 导入dataBinding 实体类 建立viewmodel xml绑定数据 视图与数据绑定
434 0
Android——MVC、MVP、MVVM框架实现登录示例