Android MVP 框架搭建

2023-11-11

前言

本文主要根据自身项目的使用和对MVP的理解,搭建符合自身项目情况的MVP架构。

关于MVP

  • M(Model)负责数据的请求,解析,过滤等数据操作。
  • V(View)负责处理UI,通常以Activity Fragment的形式出现。
  • P(Presenter)View Model中间件,交互的桥梁。

MVP的好处

  • 分离了UI逻辑和业务逻辑,降低了耦合。
  • Activity只处理UI相关操作,代码变得更加简洁。
  • UI逻辑和业务逻辑抽象到接口中,方便阅读及维护。
  • 把业务逻辑抽到Presenter中去,避免复杂业务逻辑造成的内存泄漏。

具体实现

1.view

IView:一般情况下,做数据请求都有显示加载框、请求成功、请求失败等操作,我们把这些共有的功能封装到IView中。

public interface IView {
    /**
     * 显示加载框
     */
    void showLoading();

    /**
     * 隐藏加载框
     */
    void dismissLoading();

    /**
     * 网络请求失败
     *
     * @param ex   异常信息
     * @param code 错误码
     * @param msg  错误信息
     */
    void onFail(Throwable ex, String code, String msg);

    /**
     * 网络错误
     */
    void onNetError();

}

2.Presenter

IPresenter:为了避免持有View的Presenter做耗时操作而引起的内存泄漏,我们的Presenter应该和宿主Activity/Fragment生命周期绑定。

public interface IPresenter<T extends IView> {
    /**
     * 绑定view
     * @param view view
     */
    void attachView(T view);

    /**
     * 分离view
     */
    void detachView();

    /**
     * 判断view是否已经销毁
     * @return true 未销毁
     */
    boolean isViewAttach();

}

BasePresenter:抽象的persenter业务处理层。关联抽象层view和抽象model。

public abstract class BasePresenter<T extends IView, K extends IModel> implements IPresenter<T> {
    protected K mModel;
    private WeakReference<T> weakReference;

    @Override
    public void attachView(T view) {
    	// 使用弱引用持有view对象,防止内存泄漏
        weakReference = new WeakReference<>(view);
        if (this.mModel == null) {
            this.mModel = createModule();
        }
    }

    @Override
    public void detachView() {
        if (isViewAttach()) {
            weakReference.clear();
            weakReference = null;
        }
        if (mModel != null) {
            mModel.unSubscribe();
            mModel = null;
        }
    }

    @Override
    public boolean isViewAttach() {
        return weakReference != null && weakReference.get() != null;
    }

    protected T getView() {
        return weakReference.get();
    }

    protected void showLoading() {
        if (isViewAttach()) {
            getView().showLoading();
        }
    }

    protected void onFail(Throwable ex, String code, String msg) {
        if (isViewAttach()) {
            getView().onFail(ex, code, msg);
        }
    }

    protected void onNetError() {
        if (isViewAttach()) {
            getView().onNetError();
        }
    }

    protected void dismissLoading() {
        if (isViewAttach()) {
            getView().dismissLoading();
        }
    }

    /**
     * 由外部创建 module
     *
     * @return module
     */
    protected abstract K createModule();

}

3.model

IModel:由于项目使用Rxjava+Retrofit2.0+Okhttp,所以我在model层对Rxjava进行绑定和解绑,防止内存泄漏。

public interface IModel {
    void unSubscribe();

    void addSubscribe(Subscription subscription);
}

BaseModel:实现对Rxjava绑定和解绑,初始化ApiService。

public class BaseModel implements IModel {
    protected ApiService mApi;
    private CompositeSubscription mCompositeSubscription;

    public BaseModel() {
        this.mApi = RetrofitHelper.getInstance().createApiService(AppConstants.BASE_SERVER_IP);
    }

    @Override
    public void unSubscribe() {
        if (mCompositeSubscription != null && !mCompositeSubscription.isUnsubscribed()) {
            mCompositeSubscription.clear();
            mCompositeSubscription.unsubscribe();
        }
    }

    @Override
    public void addSubscribe(Subscription subscription) {
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(subscription);
    }
}

4.BaseMvpActivity基类
通过泛型规定Presenter,并且暴露抽象方法createPresenter()给子类来创建Presenter,基类实现IView中的公共方法,减少子类代码的冗余。至于BaseMvpActivity功能根据项目业务需求进行封装。

public abstract class BaseMvpActivity<P extends BasePresenter> extends AppCompatActivity implements IView {
    protected P mPresenter;
    private Unbinder unbinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        unbinder = ButterKnife.bind(this);
    	// 初始化Presenter
        initPresenter();
    }

    private void initPresenter() {
        mPresenter = createPresenter();
        // 完成Presenter和view的绑定
        if (mPresenter != null) {
            mPresenter.attachView(this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 将Presenter和view解绑
        if (mPresenter != null) {
            mPresenter.detachView();
            mPresenter = null;
        }
        // 解绑ButterKnife
        if (unbinder != null) {
            unbinder.unbind();
        }      
    }

    @Override
    public void showLoading() {
       // 这里实现自己的加载弹框
    }

    @Override
    public void dismissLoading() {
       // 取消弹框
    }

    @Override
    public void onFail(Throwable ex, String code, String msg) {
        // 基础的网络请求失败处理
    }

    @Override
    public void onNetError() {
        // 网络错误处理
    }

    /**
     * 页面初始化数据
     */
    protected void initData() {

    }

    /**
     * 创建Presenter
     *
     * @return Presenter
     */
    protected abstract P createPresenter();

    /**
     * 获取当前activity的id
     *
     * @return 当前xml的布局res ID
     */
    protected abstract int getLayoutId();

    /**
     * 初始化view控件
     */
    protected abstract void initView();

}

使用MVP

1.契约类Contract

通过契约类来管理Model、View、Presenter的所有接口,这样使得Presenter和View有哪些功能一目了然,维护起来也方便,同时使得View与Presenter一一对应,并有效地减少类的数目。

public interface LoginContract {

    interface View extends IView {
    	 /**
         * 登录成功
         */
        void onLoginSuccess(UserInfo data);
         /**
         * 登录失败
         */
        void onLoginFail(Throwable ex, String code, String msg);
    }

    interface Presenter {
        void login(String phone, String password);
    }

    interface Model extends IModel {
    	/**
         * 登录
         *
         * @param map 用户登陆信息
         * @param subscriber 回调
         */
        void login(Map map, Subscriber subscriber);

    }

}

2.LoginPresenter

public class LoginPresenter extends BasePresenter<LoginContract.View, LoginContract.Model> implements LoginContract.Presenter {

    @Override
    protected LoginContract.Model createModule() {
        return new LoginModel();
    }

    @Override
    public void login(String phone, String password) {
        showLoading();
        Map<String, String> params = new HashMap<>();
        params.put("phone", phone);
        params.put("password", password);
        
        mModel.login(params, new BaseSubscriber<UserInfo>(new CallBackListener<UserInfo>() {
            @Override
            public void onSuccess(String code, UserInfo data) {
                dismissLoading();
                if (isViewAttach()) {
                    getView().onLoginSuccess(data);
                }
            }

            @Override
            public void onFailed(Throwable ex, String code, String msg) {
            	dismissLoading();
                if (isViewAttach()) {
                    getView().onLoginFail(ex, code, msg);
                }
            }

            @Override
            public void onError() {
                onNetError();
            }
        }));
    }

}

3.LoginModel

public class LoginModel extends BaseModel implements LoginContract.Model{
    @Override
    public void login(Map map, Subscriber subscriber) {
        Subscription subscription = mApi.login(map).compose(RetrofitHelper.applySchedulers()).subscribe(subscriber);
        addSubscribe(subscription);
    }

}

4.LoginActivity

public class LoginActivity extends BaseMvpActivity<LoginPresenter> implements LoginContract.View {

    @BindView(R.id.tv_service)
    TextView tvService;
    
    private UserInfo userInfo;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_login;
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @Override
    protected void initView() {
      addHeadTitle("登陆");
    }

	@OnClick({R.id.login, R.id.register})
    public void onViewClicked(View view) {
    	switch (view.getId()) {
            case R.id.login:
               	mPresenter.login(phone,password);
                break;
            case R.id.register:
              	// 注册 
                break;
        }
        
    }

    @Override
    public void onLoginSuccess(UserInfo data) {
        // 登陆成功回调
    }

	@Override
    public void onLoginFail(Throwable ex, String code, String msg) {
        // 登陆失败回调
    }
   
}

总结

至此,MVP搭建完成。其实还有很多可以优化的地方。每个人对MVP的理解不一样,而MVP架构也并不是一成不变,适合自己项目的才是最好的。

GitHub地址:https://github.com/mingyang22/MVP_demo

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android MVP 框架搭建 的相关文章

  • easypoi基本使用-Excel数据的导入导出

    1 为什么要用easypoi 实现excel表格的导入导出 基于模板的导出 easypoi简化poi的操作 让人更加快速上手使用 2 easypoi使用 引入依赖
  • Git-gitignore规则之“感叹号“的用法坑点

    本文只讲一个知识点 开头的模式标识否定 该文件将会再次被包含 如果排除了该文件的父级目录 则使用 也不会再次被包含 以下面的例子进行说明 目标是要保留文件夹1中所有各层文件夹中的c文件 其他都不要 文件夹1 test c一个文件 文件夹1
  • 文件——统计成绩

    从键盘输入以下10 个学生的学号 姓名 以及数学 语文和英语成绩 写到文本文件f3 txt 中 再从文件中取出数据 计算每个学生的总成绩和平均分 并将结果显示在屏幕上 程序 include
  • vscode开启鼠标滚轮缩放字体大小设置

    打开首选项 进入设置 搜索zoom 给滚轮缩放字体选项打上钩 完成
  • Flutter实战篇(1):使用第三方极光认证一键登录

    Flutter实战篇 1 使用第三方极光认证一键登录 需求场景 在项目中有时会碰到需要手机号一键登录的需求 那么我们可以利用第三方极光平台的认证模块进行集成 实操Let s Go 首先导入第三方官方插件 jverify jverify 2
  • LaTex排版技巧:[15]公式太长如何换行?

    https jingyan baidu com article d7130635045b2013fcf47543 html LaTeX作为一个功能强大国际通用的排版软件 它特别适合于数理科学中科技论文 专业书籍排版 LaTeX尤其适合于各种
  • Python案例分析:使用LightGBM算法、随机森林、五折交叉验证进行分类预测

    1 数据导入 import pandas as pd import numpy as np import warnings from imblearn import under sampling over sampling from imb
  • 【计算机毕业设计】redis的电商秒杀系统

    redis的电商秒杀系统 开发语言 Java 框架 springcloud JDK版本 JDK1 8 服务器 tomcat7 数据库 mysql 5 7 一定要5 7版本 数据库工具 Navicat11 开发软件 eclipse myecl
  • MySQL中limit对于order by的优化

    一 无limit的场景对于order by如何优化 考虑无limit的order by语句 下面假设age是一个普通索引 1 1 查询无覆盖索引 select from emp order by age 此句中 查询的字段和order by
  • slf4j如何进行logback配置呢?

    转自 slf4j如何进行logback配置呢 slf4j简介 slf4j simple logging facade for java的缩写 翻译为java的简单日志外观 slf4j是一个开源项目 它提供我们一个一致的API来使用不同的日志
  • js逆向工具-nodejs服务使用

    目录 一 运行js文件 二 引入或开放接口模块 1 提供模块公开接口module exports 2 引入模块require 三 GET POST请求 1 获取GET请求内容 2 获取Post请求内容 四 创建Web服务器 客户端 1 No
  • Select的OnChange()事件

    我们用Select的onchange事件时 常会遇到这样一个问题 那就是连续选相同一项时 不触发onchange事件 select的onchange事件就是这样子的 你得有Change 改变 才能触发该事件 掌握了它的特性后 相应的解决办法
  • 操作系统-进程API

    概述 进程的基本操作接口 进程创建 fork spawn vfork clone 进程执行 exec 进程间同步 wait 进程退出 exit abort 进程创建 fork fork 语义 为调用进程创建一个一模一样的新进程 fork后的
  • Java 多线程 --- 创建线程, 线程状态

    Java 多线程 创建线程 线程状态 如何创建线程 使用Thread类 使用Runnable接口 使用Runnable接口和继承Thread类的区别 continue 线程状态 New Threads Runnable Threads Bl
  • 华为OD机试真题- 字符串解密【2023Q1】【JAVA、Python、C++】

    题目描述 给定两个字符串string1和string2 string1是一个被加扰的字符串 string1由小写英文字母 a z 和数字字符 0 9 组成 而加扰字符串由 0 9 a f 组成 string1里面可能包含0个或多个加扰子串
  • IntelliJ插件开发教程之调试插件

    JetBrains公司系列产品IDEA WebStrom PyCharm CLion GoLand等都是基于IntelliJ Platform开发而成 掌握IntelliJ插件开发技能便能拥有提升开发效率的终极武器 本教程Demo源码请关注
  • JS 解析 key-value 最佳实践

    一般请求接口返回的数据大部分是 json 格式 JS 解析某个字段数据 相信大家都会遇到取某个字段可能会是 undefined 或者 null 类型 如果代码忘记处理 bad case 就会挂了 下面代码是封装了对字典的解析 感觉挺好用的
  • Js对象数组,根据对象关联键合并一个新数组

    Js对象数组 根据对象关联键合并一个新数组 需求 合并数组a b为一个对象数组 数组中每一个对象由 id name age 组成 const a id 1 name ty1 id 2 name ty2 id 3 name ty3 id 4

随机推荐

  • 【软件测试】如何用python连接Linux服务器

    1 安装paramiko库 pip install paramiko 2 使用paramiko库连接linux 导入库 import paramiko 创建一个sshclient对象 ssh paramiko SSHClient 允许连接不
  • ubuntu16.04下2080Ti显卡配置 cuda10.0 + cudnn7.4.2 + tensorflow 1.13.1

    ubuntu16 04下2080Ti显卡配置cuda10 0 cudnn7 4 2 tensorflow 1 13 1 pytorch 1 0 0 pytorch 1 0 0 友情提醒 没有一个环境配置只看一个文档就能解决的 所以还是需要多
  • java 相关知识点梳理(包含项目中实际应用) 一 ~持续更新

    最全java知识点梳理 1
  • 【自用】云服务器 docker 环境下 HomeAssistant 安装 HACS 教程

    一 进入 docker 中的 HomeAssistant 1 查找 HomeAssistant 的 CONTAINER ID 连接上云服务器 宿主机 后 终端内进入 root 输入 docker ps 找到了 docker 的 contai
  • source /build/envsetup.sh和lunch)

    提醒 想要研究安卓编译系统 必须对bash shell和GUN make非常熟悉 不然会看的云里雾里 没有这个背景的可以先补充知识 1 source build envsetup sh 主要是加载device vendor目录下面的vend
  • sqlsum多个字段求和_条件求和函数Sumif、Sumifs超级实用技巧解读!

    提起求和 有点儿老生常谈的感觉 不就是用Sum函数而已吗 如果附加条件 你还认为是用Sum函数吗 这是我们是否应该考虑用Sumif或Sumifs函数呢 详情请阅读下文 一 Sumif函数 单条件求和 1 功能及语法结构 功能 对符合条件的单
  • 华为传输服务器系统类型,服务器操作系统平台类型

    服务器操作系统平台类型 内容精选 换一换 云硬盘挂载至云服务器时 无法挂载 以下排查思路根据原因的出现概率进行排序 建议您从高频率原因往低频率原因排查 从而帮助您快速找到问题的原因 如果解决完某个可能原因仍未解决问题 请继续排查其他可能原因
  • 2022 年全国硕士研究生入学统一考试英语(二)试题

    2022年全国硕士研究生入学统一考试英语 二 试题 Section Use of English Directions Read the following text Choose the best word s for each numb
  • MySQL自学笔记详细版(从安装到入门)

    MySQL学习目录 前言 一 MySQL是什么 二 MySQL的好处 三 数据库的概念 1 DB database 2 DBMS Database Management System DBMS分为两类 3 SQL Structure Que
  • TypeError: unsupported operand type(s) for /: ‘NoneType‘ and ‘float‘

    可能有的小伙伴在使用opencv的时候会出现以下错误 这个错误的原因可能是图片读取失败 导致 img 变量为 NoneType 类型 在变量上执行除法运算时出现了 TypeError 所以就加了一个 if 条件判断 判断 img 是否为 N
  • VC++ UI布局管理器

    程序从codeproject上下载的 功能类似QT的QLayout 有了它界面缩放再也不会乱套了 具体使用方法可参考源码示例 资源下载链接地址 https download csdn net download u012156872 1926
  • PyTorch学习日志_20201031_数据并行处理

    日期 2020 10 31 主题 PyTorch入门 内容 根据PyTorch官方教程文档 学习如何使用数据并行 DataParallel 来使用多GPU 根据自己的理解和试验 为代码添加少量注解 具体代码如下 数据并行处理 导入和参数 i
  • c++——复制构造函数

    一 概述 复制构造函数作用 使用一个已经存在的对象去初始化同类的一个新对象 其形参是本类对象的引用 如果没有定义复制构造函数 编译器会在必要时自动生成一个隐含的复制构造函数 声明和实现复制构造函数 class 类名 public 类名 形参
  • Netty实战(八)引导

    引导 一 引导 1 1 什么是引导 1 2 Bootstrap 类 1 3 引导客户端和无连接协议 1 4 引导客户端 1 5 Channel 和 EventLoopGroup 的兼容性 二 引导服务器 2 1 ServerBootstra
  • Python:小数、百分比相互转化

    简介 实现小数和百分比相互转换 相关攻略 Python内置库 数据计算相关 math random 类型 主要分为以下5种情况处理 1 百分比转小数 2 小数转百分比 直接转可能存在尾数不足的情况 3 小数转百分比 注意是两个 4 小数转百
  • MySQL 中 MyISAM 与 InnoDB 引擎的区别

    分析 回答 区别很多 大家说出下面几点 面试就应该 OK 了 1 事务支持 MyISAM不支持事务 而InnoDB支持 InnoDB的AUTOCOMMIT默认是打开的 即每条SQL语句会默认被封装成一个事务 自动提交 这样会影响速度 所以最
  • KiCad使用笔记(03)-原理图绘制

    绘图过程 放置元件 绘制导线 编号元件 检查原理图 关联封装 生成网表 标题栏设置 相关视频教程 绘图过程 放置元件 放置元件可以点击右侧工具栏中放置元件 然后在工作区鼠标左键单击 在弹出的选项框中通过关键词搜索或是直接在下方列表中选择 可
  • ctf misc之MP3隐写

    2022 4 9 第一次写mp3隐写 走了好多坑记录一下 题目得到一个MP3文件和一个压缩包 然后听一遍这个歌发现最后面有一段非常违和的 用Audacity打开这个mp3 查找了一下MP3的隐写方法有以下几种 MP3编码隐写将数据隐藏在MP
  • 【Seata】00 - Seata Server 部署(Windows、Docker 基于 Jpom)

    文章目录 前言 参考目录 版本说明 Windows 部署 seata server 1 下载压缩包 2 文件存储模式 3 db 存储模式 3 1 建表 3 2 修改配置文件 3 3 启动脚本 4 源码部署 Docker 部署 seata s
  • Android MVP 框架搭建

    前言 本文主要根据自身项目的使用和对MVP的理解 搭建符合自身项目情况的MVP架构 关于MVP M Model 负责数据的请求 解析 过滤等数据操作 V View 负责处理UI 通常以Activity Fragment的形式出现 P Pre