跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

Android 项目开发整体架构设计与演进

Android 项目开发整体架构经历了从传统 MVC 到分层架构、MVP 及 MVVM 的演进。传统 MVC 以 Activity 为核心,维护困难且测试不便。分层架构通过引入 DataManager 层剥离数据处理逻辑,提升复用性。MVP 将 View 与 Model 交互剥离至 Presenter,但存在生命周期绑定问题。MVVM 利用 ViewModel 和 LiveData 实现数据绑定,有效解决数据恢复及多界面共享问题,是应对复杂场景的有效方案。详细对比了各架构优缺点,并给出了选型建议。

MqEngine发布于 2025/2/6更新于 2026/10/2494 浏览
Android 项目开发整体架构设计与演进

Android 项目开发整体架构设计与演进

前言

设计 App 的整体框架,首先要清楚我们做的是什么。一般我们与网络交互数据的方式有两种:主动请求 (HTTP) 和长连接推送。

结合网络交互数据的方式来说一下我们开发的 App 的类型和特点:

  • 数据展示类型的 App:特点是页面多,需要频繁调用后端接口进行数据交互,以 HTTP 请求为主;推送模块、IM 类型 App 的 IM 核心功能以长连接为主,比较看重电量、流量消耗。
  • 手机助手类 App:主要着眼于系统 API 的调用,达到辅助管理系统的目的,网络调用的方式以 HTTP 为主。
  • 游戏:一般分为游戏引擎和业务逻辑,业务脚本化编写,网络以长连接为主,HTTP 为辅。

一般我们做的 App 都是类型 1,简要来说这类 App 的主要工作就是:

  1. 把服务端的数据拉下来给用户展示
  2. 把用户在客户端修改的数据上传给服务端处理

所以这类 App 的网络调用相当频繁,而且需要考虑到网络差、没网络等情况下 App 的运行。成熟的商业应用的网络调用一般是如下流程:

UI 发起请求 - 检查缓存 - 调用网络模块 - 解析返回 JSON / 统一处理异常 - JSON 对象映射为 Java 对象 - 缓存 - UI 获取数据并展示

这之中可以看到很明显职责划分,即:数据获取、数据管理、数据展示。确定了职责,就可以进入正题了。

1. 传统的 Android App 架构

Android 最原生也是最基础的架构,可以理解为 MVC,Controller 即是 Activity 和 Fragment,但是这两者掌握了 Android 系统中绝大多数的资源,并且在内部直接控制 View,因此传统的 Android App 一般是以 Activity 和 Fragment 为核心,将网络模块、数据库管理模块、文件管理模块、常用工具类等分离成若干工具类包,供 Activity 和 Fragment 使用。

这是比较基础的 Android 项目架构,市面上大部分 App 都是这种造型。

  • 优点:开发简单,以页面为导向;如果构建水平可以,项目就已经基本实现模块化,基于 Activity、Fragment 这两个上帝般的存在,很多事情直接就妥了,不用绕。
  • 缺点:维护难,因为是以页面为导向的,有些需要共用的业务逻辑就会很烦,Don't Repeat Yourself,你要不要 Repeat?不想 Repeat 就要写模块,慢慢的项目就会多出一堆乱七八糟的小模块。另一方面,测试很困难,因为所有的数据处理都在 Activity 和 Fragment,假如现在想先用假数据显示,就要直接改 Activity 和 Fragment 的数据控制逻辑。

2. 分层架构

如果仔细看自己的项目,可以发现绝大多数数据处理的代码是不需要使用 Activity 和 Fragment 持有的资源的(比如 Context),而很多时候我们需要多个页面共用一套数据和请求逻辑,很经典的例子是应用中的 User 对象,一般来说都是全局单例。

这些全局的数据源写多了,很容易就能想到将数据处理统一抽出来形成一层,向上层提供数据接口,而上层并不关心数据的来源(内存、缓存、网络),因为不用从 Activity 和 Fragment 拿资源而且主要工作是数据处理,所以这一层是 UI 无关的,大幅提升了复用性,我把这一层称为 DataManager 层。

Activity 和 Fragment 剥离了数据处理的责任后,持有 DataManager 的引用,负责获取数据并展示,向 DataManager 传递数据,绝不进行网络请求和缓存读写。

举一个例子,分页加载一般来说分页加载接口返回的数据是这样的:

{
    "code": 0,
    "message": "success",
    "data": 
         
         
         
         
         ......
    

{
"page"
:
1
,
"totalPage"
:
10
,
"pageSize"
:
20
,
"total"
:
200
,
"list"
:
[
]
}
}

在传统的写法中,一般在 Activity/Fragment 中缓存 page、totalPage、pageSize 去进行分页请求,根据请求结果刷新数据并判断是否还有更多;每一个分页接口都要写一遍,假如把这段逻辑放到 DataManager 会怎么样?我是这么写的:

// 定义回调接口
public interface ActionCallback<T> {
    void onSuccess(T data);
    void onFailure(String message, Throwable e);
}

// 分页加载 DataManager 实现
public class PageLoadDataManager extends BaseDataManager {
    private static final int PAGE_COUNT = 20;
    private List<Data> mDataList = new ArrayList<>();
    private int currentPage = 0;
    private int totalPage = 0;

    public PageLoadDataManager() {
        // init something......
    }

    public void loadData(final boolean refresh, ActionListener<Boolean> listener) {
        if (refresh) {
            currentPage = 0;
        }
        currentPage++;
        RequestParams params = new RequestParams();
        params.put("page", currentPage);
        Request request = new Request(url, params);
        request.request(new RequestCallback() {
            @Override
            public void onSuccess(JSONObject data) {
                if (refresh) {
                    mDataList.clear();
                }
                totalPage = response.optInt("total_page");
                // 返回数据添加到 mDataList ......
                if (listener != null) {
                    boolean hasMore = currentPage <= totalPage;
                    listener.onSuccess(hasMore);
                }
            }

            @Override
            public void onFailure(String message, Throwable e) {
                if (listener != null) {
                    listener.onFailure(message, e);
                }
            }
        });
    }

    public List<Data> getDataList() {
        return mDataList;
    }
}

Activity/Fragment 初始化 DataManager 之后,只需要将数据源绑定到 Adapter,loadData 设置的回调告诉上层还有没有更多数据,UI 层调用 adapter.notifyDataSetChanged();至于数据从哪来,分页逻辑,根本不需要 UI 层管理。UI 层只需要通过 loadData(refresh),告诉 DataManager 是否需要重新加载分页,与下拉刷新的逻辑完美契合。

当然,在此基础上实现数据库缓存读写,也毫无压力。DataManager 也很容易实现对某一数据的多个接口的统一管理,通过单例模式或者其他管理方法,将数据配发给多个页面。

  • 优点:大幅减轻 Activity/Fragment 的压力,实现数据统一管理,DataManager 层成为了一个 UI 无关的 AppSDK 层。
  • 缺点:需要添加嵌套回调,这个问题在引入 RxJava 之后被完美处理。

其实到了这一步,已经能满足大多数几万行代码规模中小 App 的框架需求了,而且分层架构统一处理数据以及代码复用度高的特点,使得项目中按照框架思路实现业务成为最快速可靠的开发方法。

一个优秀的框架,很重要的特性就是方便业务开发而不是给开发找麻烦,比如在分层设计过后,就算开发时间再紧张,依托分层框架依然是最快最保险的开发方法。假如某个接口直接在 UI 中写了,就意味着数据管理层提供的一切便利都无法直接使用,而且假如其他 UI 用到这个接口,还得再复制粘贴一遍改来改去。相反,依托框架,网络调用只实现一遍,上层即可重复使用这一业务接口(比较典型的:关注、收藏等)。

即便如此,项目规模进一步往上之后,DataManager、Activity/Fragment 的压力仍然会增大,更高的测试需求,要求进一步分离 Activity/Fragment 的代码。这时候就可以看看 MVP 和 MVVM 了。

3. MVP 架构

MVC 的 C 是即持有具体 Model,又持有具体 View,所以 C 很臃肿,分层架构就算抽出了 DataManager,实质上仍然是一个 MVC 架构,而 MVP 和 MVVM 则是 C 持有具体 View 这个问题做了点文章,其中MVP 就是将大量的 View <-> Model 交互剥离出来交由 Presenter,Presenter 持有抽象的 View。

Presenter 听起来很吊,主导者啊,但是没有 Activity 和 Fragment 的资源啊,我要怎么才能让它主导?需要获取系统的一些信息(需要 Context)的时候怎么办?不持有 Context 难道再开接口吗?写这么多接口,接口实现,Presenter,多写了几百行代码 n 个类,就为了把 1~200 行代码从 Activity 移出去?还是放弃吧……后来 Google 出了 TODO-MVP,但是发现跟上面那种 Demo 写法一样很麻烦,我也没有实际运用。后来反编译了某个大型 App,发现其正好是 MVP 架构,于是仔细看了一下代码,就如同我最开始的想法,一个 IXXView 有多少功能就写多少接口。再看看 Presenter 的实现,我忽然就明白我为什么会感觉不实用了.

任何想要构建一个其他什么东西取代 Activity/Fragment 地位的尝试都是自找麻烦

MVP 正是一个典型。

既然 MVP 把 Activity/Fragment 抽象为 View,那么就意味着当它作为一个抽象 View 去使用的时候,生命周期、Context 这些极其重要的资源 Presenter 是看不到的,但是这些东西是不可能不使用的。为了能让 Presenter 使用到这些,Presenter 就必须持有 Context,绑定 Activity、Fragment 的生命周期,就算如此,在一些需要确定使用 Activity、Fragment 的场合,仍需要使用强制转型。

正因为 Presenter 这个'主导',导致 Presenter 和 Activity/Fragment 高度绑定,Presenter 和 IXXView,没有什么复用性。

4. MVVM 架构

MVVM 相比于 MVP,最重要的一个概念就是'数据绑定',Presenter 还持有抽象的 View,ViewModel 连这个都不需要,View 通过 ViewModel 订阅其所需的数据源,ViewModel 向 View 提供改变数据的接口,当 View 的操作引起数据改变或者数据源发生改变时,ViewModel 通过订阅告知 View,View 进行视图更新。 这就是 MVVM 吸引人的地方,ViewModel 只提供数据订阅和数据接口,做到了与 UI 分离,ViewModel 体量比 Presenter 小,复用性要比 Presenter 强太多,而且基于分层架构可以做到小幅修改就能实现。唯一的痛点在于:如何实现数据绑定?

之前提到的 data-binding,并不是那么如意,而这次 Google I/O 2017 放出的 android-architecture-components 则很好的解决了这个问题。

ViewModel 组件规范了 ViewModel 的所处地位,生命周期,生成方式,以及一个 Activity 下多个 Fragment 共享 ViewModel 数据的问题。 LiveData 组件则提供了在 Java 层面 View 订阅 ViewModel 数据源的实现方案,很轻量。

4.1 ViewModel 的使用

ViewModel 的设计初衷是为了在配置更改(如屏幕旋转)期间保存数据。它通过继承 androidx.lifecycle.ViewModel 类来实现。

public class MyViewModel extends ViewModel {
    private MutableLiveData<String> mText;

    public LiveData<String> getText() {
        if (mText == null) {
            mText = new MutableLiveData<>();
        }
        return mText;
    }

    public void doSomething() {
        mText.setValue("New Value");
    }
}

4.2 LiveData 的优势

LiveData 是一种可观察的数据持有类,它具有生命周期感知能力,能够确保界面状态正确更新。当 LifecycleOwner 处于活跃状态时,LiveData 才会通知观察者更新。

public class MainActivity extends AppCompatActivity {
    private MyViewModel model;

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

        model = new ViewModelProvider(this).get(MyViewModel.class);

        model.getText().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
    }
}

ViewModel 的引入能够很好应对 Activity 销毁重建时大规模数据的恢复问题,以及多个界面依赖一个接口返回数据的场景,在这两个组件的规范下实现 MVVM 架构会十分容易,而且十分有意义。

5. 架构选型建议

在实际项目中,选择哪种架构取决于项目的规模、团队的技术栈以及维护成本。

  1. 小型项目:MVC 或简单的分层架构即可满足需求,避免过度设计。
  2. 中型项目:推荐分层架构 + MVP 或 MVVM,提高代码复用性和可测试性。
  3. 大型项目:强烈推荐使用 MVVM + Clean Architecture,配合 Jetpack 组件库,确保代码的清晰度和可维护性。

无论选择哪种架构,核心原则都是解耦。将 UI 逻辑、业务逻辑和数据存储分离,才能让项目随着业务发展而保持健康。

结语

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,只有不断提升才能不被时代淘汰。选择合适的架构模式,结合具体的业务场景,才是王道。

目录

  1. Android 项目开发整体架构设计与演进
  2. 前言
  3. 1. 传统的 Android App 架构
  4. 2. 分层架构
  5. 3. MVP 架构
  6. 4. MVVM 架构
  7. 4.1 ViewModel 的使用
  8. 4.2 LiveData 的优势
  9. 5. 架构选型建议
  10. 结语

更多推荐文章

查看全部
  • Token 管理实践:前端登录凭证的获取、存储与过期处理
  • Arrow 游戏叙事设计工具:基于 Godot 4 的可视化剧情编辑器
  • Midjourney 官网地址及中文支持情况说明
  • AI 智能体辅助 C 语言与前端实训项目生成实战
  • 基于 Git 和 Termux 实现 Obsidian 平板多端同步方案
  • 基于 YOLO 的纺织品缺陷检测系统:Web 端集成与模型训练实战
  • Microsoft Visual C++ 运行库安装与 DLL 缺失修复指南
  • Llama 3.3 70B 模型部署输出异常排查与优化指南
  • 探索 Ovis 多模态大模型量化实战指南
  • 当养老遇上 AI 大模型:ATEC 2023 科技助老赛题解析
  • C++ 类和对象(二):默认成员函数详解
  • 滑动窗口算法:无重复字符的最长子串(数组模拟哈希表)
  • 机器人 3D 位姿详解:5 种旋转表示法与机器学习应用
  • C++驱动 spidev0.0 时 read 函数返回 255 的硬件电平分析
  • AI Agent 实战:生产级框架搭建与核心实现
  • JDK 23 本地开发环境搭建指南
  • Python 工程师常见基础面试题
  • 小鹏 VLA 2.0 与 IRON 人形机器人技术解析
  • Linux C++ 多线程编程入门:从核心概念到常用函数详解
  • AMD 显卡部署 llama.cpp 高性能配置实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online