尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Android分层架构落地指南:开发工具与工程实践
搞了这么多年Android有个感悟越来越深架构分层这件事从来不是画一张漂亮的分层图就完事了。真正让分层落地到项目里的是那一整套开发工具和工程实践的配合。你光有MVVM、Data Domain Data的分层意识没有靠谱的构建工具、依赖注入框架和代码检查工具在背后撑着这套架构最终只会在无尽的Activity里慢慢腐烂。这篇文章我想把android开发工具和android分层架构这两条线合在一起聊为什么项目需要分层分层到底该怎么分以及哪些工具能帮你把分层严格地“焊死”在代码库里。不管是刚入门的同学还是已经被老项目折磨许久的维护者应该都能找到点自己能直接抄作业的东西。1. 为什么非得搞分层架构1.1 不分层的项目到底有多难受我有段时间接手过一个维护了三四年的业务App当时整个工程只有一个大app模块一个Activity里动辄两三千行网络请求直接写在Activity的onCreate里数据解析逻辑顺手也塞同一页。业务逻辑散落得到处都是改一个支付金额的逻辑得全局搜formatPrice这类方法因为每个人写的命名还不一样。最痛苦的是一次上线前要补测试结果发现这种代码根本无测可写——业务和UI焊死在一起UI没法脱离真机环境跑逻辑也没办法单独验证。这种项目最典型的特征是每次新需求都像在旧账本上又叠一张白条。你小心翼翼地只改自己那几行但编译一跑挂掉的偏偏是别人半年前写的隐式依赖代码。团队里没人能拍胸脯说“这块逻辑只存在一个地方”因为事实就是同一块逻辑至少散落在三四处。所以我后来一直在强调分层架构不是为了赶技术时髦它解决的是非常现实的三个问题——依赖混乱、改动扩散、测试困难。大家从第一天把代码分好层后面能少受很多罪。1.2 分层带来的四个直接收益分层不是纯理论它能给日常开发带来肉眼可见的提升改动变得可控一个需求要改底层数据格式你有明确的目标层可以“雷霆一击”其他层只要接口稳定就不受影响。代码结构可读新的同事接手项目不用通读全部代码只看各层接口就能快速建立心智模型。我常说好的分层设计比再详细的文档都管用。可测试性大幅提升因为核心业务逻辑不依赖安卓生命周期用纯JVM单元测试就能覆盖掉大头测试成本低大家才愿意写测试。替换风险隔离哪天要把本地数据库从原生SQLite换成别的方案只要数据层内部变业务层没有任何感知。1.3 谁最需要分层架构我遇到过有人问我一个人写一款独立开发的小应用有必要分层吗我的回答通常是如果你是做原型验证、Demo、短期内部工具确实怎么爽怎么来。但只要你对自己这个项目有明确的预期——会持续维护、会加功能、可能会商业化那从第一天就把层次分清楚绝对不亏。分层的成本就是一开始多写几个接口和包结构而它避免的是“项目写到一半改不动”这种大麻烦。顺带多说一句分层架构不是说“越多层越好”。最近几年有人把分层越分越细每层之间再加一堆转发接口结果写一个小需求要横跨六七个类和接口这种我也很不推荐。对大多数项目来说三层到四层就是甜点位。2. Android分层架构的正确打开方式2.1 我是怎么理解“分层”的很多教程喜欢直接丢一张图UI层、Domain层、Data层然后每层职责各写一句。图没问题但我在实际落地时更关注的是三个字依赖边界。所谓分层本质上是在代码里画几条“别人不能乱跨的线”然后通过工具和工程手段让这些线变得难以逾越。以我目前最常用的三代结构来说核心分层是这样的表现层UI/App层只负责展示和交互包括Activity、Fragment、各类Adapter、自定义View以及它们直接对应的ViewModel或Presenter。领域层Domain层可为空装的是业务规则和用例。比如“下单”“取消订单”“计算购物车价格”这一类动作。这层不依赖任何安卓SDK类也不依赖具体的数据来源。数据层Data层负责给领域层提供数据包括网络请求、本地缓存、数据库读写、偏好存储等。向外暴露的是抽象仓库接口具体实现藏在仓库类内部。关键点在于依赖方向只能从外层指向内层。表现层可以依赖领域层和数据层接口领域层可以依赖数据层接口但反过来不行。数据层永远不知道UI长什么样。2.2 各层的职责边界与常见越界分层这件事说得轻巧“边界”这个词最容易被模糊掉。我结合平时踩的坑把各层的“红线”列一下给你参考层职责不应该做的事表现层管理UI状态、响应用户事件、渲染布局直接拼URL、解析JSON、写SQL领域层执行业务规则、组合多个数据源引用android.widget.*、持有Context数据层读写网络/本地数据、做缓存策略感知页面生命周期、直接刷新UI公共层/基础层放工具类、基类、通用组件包含任何业务功能2.3 用登录功能看分层长什么样光说概念不容易上头我拿最常见的“登录”举个小例子。一个不分层的写法大概是登录按钮点击 →Activity里直接写OkHttp请求 → 解析JSON → 拿token存SharedPreferences→ 跳转首页。看起来很快但所有这些逻辑全在一个Activity里改个接口域名你要进UI层翻代码。分层后的写法是表现层LoginViewModel暴露login(username, password)方法观察loginState页面只感知这个状态并决定跳不跳转。领域层简单时可跳过LoginUseCase封装“用户输入账号密码并提交”这个动作负责调用认证仓库并返回结果。数据层AuthRepository接口定义login(username, password): AuthResult实现类AuthRepositoryImpl内部协调RemoteDataSource网络和LocalDataSource本地token缓存。这样写完之后最直观的好处是登录页面想换成指纹登录只需要改表现层调用的用例。想从手动输入改成静默自动登录数据层加个实现就完事。UI层和业务层各自能独立演进。3. 架构要落地离不开这套开发工具3.1 先把构建工具和依赖管理理顺分层架构再到代码里第一件要做的就是确认你的工程结构能支撑这个分层。我这里说的不只是包名更是**模块module**拆分。我比较推崇的是按层拆模块按功能拆业务模块的组合方式。公共部分拆成core、common、network、storage这些基础模块业务部分按功能域拆比如feature:login、feature:home、feature:profile。每个业务模块内部再按表现/领域/数据组织包结构。光靠人肉维持这些依赖关系太累了所以我强烈建议用Gradle Version Catalog来统一管理依赖版本。你的libs.versions.toml文件里可以清晰看到每个依赖的版本升级一次全局生效不再出现“A模块用的是OkHttp3B模块用的是OkHttp4”这种分叉情况。另外千万别小看build.gradle.kts里的implementation和api的区别。我见过不少项目为了图省事把对下游模块的依赖全部写成api结果一个底层库升级全模块连锁重新编译。正确的做法是只对外暴露的类型所在的依赖才用api内部实现细节一律用implementation。3.2 依赖注入分层之间的“粘合剂”分层架构搞好了层与层之间怎么优雅地传递依赖手动在Activity里AuthRepositoryImpl()一把梭重建对象其实也是一种粘合但它会带来两个问题一是对象创建和生命周期管理全搅和在一起二是不方便替换测试替身Mock。我个人的选型是用Hilt这套依赖注入框架。它跟Jetpack组件配合得很好ViewModel可以直接依赖注入仓库接口想换Mock实现就改依赖绑定。这一层工具的价值在于它让“高层依赖接口、低层提供实现”真正变得顺手。具体落地时我会给每个数据层的仓库接口都做一个Hilt绑定模块。比如Module InstallIn(SingletonComponent::class) abstract class RepositoryModule { Binds abstract fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository }这样领域层拿到的永远是接口测试时也可以很方便地用Module整体替换成假实现。3.3 网络层与数据层的最佳拍档网络请求属于数据层最典型的工作。标准姿势是Retrofit定义接口配OkHttp做拦截器返回的数据在数据层内部完成对业务模型的映射。这里我要单独强调一个很多项目会踩的坑不要在业务层和UI层直接使用网络层的数据结构。很多网络框架支持直接把JSON映射成模型对象于是大家图省事把服务端返回的UserDto直接塞给UI导致UI和协议强耦合。一旦后端调整字段你的UI层代码跟着遭殃。我的习惯是在数据层里处理完映射向领域层暴露的模型一定是业务模型。比如网络返回一个UserInfoResponse我会在仓库实现里转成UserProfileUI拿到的永远是后者。这套映射逻辑可能看着多写了几行代码但它把后端变化牢牢隔离在了数据层内部是分层架构真正吃到红利的地方。3.4 模块化时代图片加载和本地存储怎么选图片加载这一环Glide是老牌选择Coil则是轻量后起之秀。我对分层的建议是不要让UI层直接持有图片库的加载器而是封装一个统一的ImageLoader接口内部再委托给Glide或Coil。这样万一某天要切换图片库UI层代码一行都不用改。本地存储方面Room作为官方推荐的ORM框架很够用。分层的正确姿势是Room的Entity、Dao、Database只存在于数据层内部领域层和UI层操作的是仓库接口暴露的模型而不是直接拿Entity到处传。如果项目里既有网络缓存又有本地数据库我通常会再包一层Repository让上层感知不到数据到底来自缓存还是远端。这种“数据源不可见”的设计特别适合离线优先的应用场景。3.5 用自动化工具守住架构边界分层架构最大的敌人不是一开始没设计好而是随着迭代慢慢被侵蚀。我今天加一个需求图顺手在Activity里开启一个OkHttp请求明天同事在ViewModel里调了SQLiteOpenHelper。每个人的“省事一下”最终都变成架构腐烂的加速器。要守住边界靠Code Review提醒是上策但人总有个疏忽所以我非常建议接入架构约束检查工具。比较常见的方案有Android Lint自定义规则可以写规则拦截“数据层不允许直接使用android.widget”这类问题。ArchUnitJava/Kotlin单测层面的架构断言库把分层规则定义成测试用例。它能在CI上跑架构违规直接让构建变红。Dependency Analysis Gradle Plugin扫描模块间的依赖关系提示你哪里出现了“不该有的依赖”。我自己的工程里最少会保留两条ArchUnit断言一条是“数据层代码不能依赖表现层的任何类”另一条是“表现层只能调用领域层的接口不能直接实例化数据层实现类”。这两条测试跑起来之后我发现架构腐烂的速度明显慢了下来别人乱起手走捷径也会被测试当场拦住。4. 从零搭建一个分层架构项目的实操记录4.1 模块拆分的具体步骤如果你是从空项目开始我推荐按下面这个顺序来搭建好根工程和三个基础模块先创建core、common、network、storage这些底层模块这些模块不包含任何业务含义只提供通用能力。定义数据层模块可以拆成或放data模块里面放网络API、数据库DAO、仓库实现类。定义领域层如果需要独立领域层建一个domain模块放用例和领域模型。如果你的项目比较简单领域层可以直接合到业务模块里不必强行独立。按功能拆业务模块比如feature:login、feature:home。每个模块内部再按ui、domain、data分包。装配App壳工程最后建一个app模块它只是负责把各模块依赖进来、配置导航依赖关系不写什么业务逻辑。这套结构的依赖方向大概是这样的app装配入口 ├─ feature:login、feature:home ...功能模块依赖core/common/domain/data的接口 ├─ domain可复用、无UI依赖的业务用例 ├─ data仓库实现、网络、数据库依赖core └─ core / common纯工具、基础扩展注意功能模块之间不要互相依赖。如果登录后首页要展示用户信息做法应该是app层或某个公共入口层来编排而不是feature:login直接依赖feature:home。交互通过共享的领域模型或导航框架来实现能避免模块间的循环耦合。4.2 存量老项目如何安全地迁移到分层大多数朋友遇到的不是新项目而是跟我一样需要给乱得一塌糊涂的存量项目续命。这时候千万别搞“砸碎重来”的大重构我的实践经验是渐进式迁移第一步先只做“物理隔离”。把现有的代码按包名重新归位比如把所有网络相关类挪到data包里把所有的工具类挪到common包。这一步不改变任何行为只做移动风险最低。第二步挑一两个频繁变动的业务点做试点分层。我通常选“登录”或“设置”因为这类功能改动多、边界清晰。把它的网络请求、存储、业务判断逐个抽出来。第三步给试点业务封装好仓库接口让UI层只面对接口。老代码情愿暂留在UI层也不要一次挪完但要确保新的改动都遵循分层规则。第四步等试点稳定后再把同样的模式复制到其他模块。每完成一个模块的迁移跑一次全量测试和Ada架构检查保证没把原有逻辑搞坏。这个方案我推过好几次最长的一个老项目用了大约大半年时间把几十个模块从“大杂烩”逐步整理成清晰分层结构期间业务照样发版、双周迭代没有因为重构而停摆。4.3 团队协作里需要咬死的约定分层架构在多人协作的项目里最怕的不是有人不会写而是“我写完就跑了管你后面怎么维护”。所以团队规范必须同步立起来代码评审里明确看分层凡是看到UI层直接拼URL、看到Data层引用View类直接打回。开新功能先问“放哪一层”这个问题想明白了代码基本也就有八成谱了。接口命名要有共识仓库接口统一XxxRepository用例统一XxxUseCase数据源统一XxxDataSource整个项目检索和心智负担都会小很多。包结构身为架构文档的一部分每个人打开工程先看包名就能知道属于哪一层比阅读架构设计书更效率。我不太建议把架构规范写成长篇文档丢Wiki里因为没人看。我一般只写不到一页的“分层约定清单”贴到项目README顶部新同学进组第一件事就是读这个。5. 常见问题与排查技巧实录5.1 分层之后代码量变多、编译变慢怎么办这是最常被吐槽的一点。本来一个Activity能干完的事分层后变成了五六个类感觉有点小题大做。我的看法是代码量在初期确实会膨胀但换来的是后续变更时“只改一行就能收工”的轻松。至于编译慢八成是模块拆分和依赖管理没做好。检查点有三个一是api依赖不要滥用能用implementation的都用implementation二是善用Gradle Build Cache和Configuration Cache三是观察是不是某个公共模块的改动导致大量业务模块连锁重编。通常做好这三点增量编译会舒服很多。如果某个功能模块特别大还可以再拆细或者把该模块中“纯Kotlin逻辑”部分抽到JVM模块变成Java/Kotlin库这样CI跑测试和本地编译都更轻快。5.2 接口满天飞适配成本比想象中高怎么办分层一旦做久了有人会往“面向接口”的极端走每个类都配一个接口导致项目里全是Foo和FooImpl。这种过度设计我自己也踩过。经验是接口的意义在于“有出现替换需求的可能性”如果没有就不要为了设计而设计。我现在的原则是数据层仓库接口一定要有因为你很可能会换实现或做Mock。领域层用例类可以不需要接口它本身就是纯逻辑的稳定单元。表现层的ViewModel一般不搞接口UI直接面对ViewModel的具体类就好。真正需要接口的地方才给接口不需要的地方强行抽象只会让阅读代码和理解系统变成一场灾难。5.3 领域层死活不需要出现怎么处理很多中小项目写完会发现领域层除了几个空壳用例什么都没放。这时候就有人纠结“要不要保留这个层”。我的建议是如果体系里没有复杂的业务规则领域层完全可以取消。表现层直接面对数据层接口也就是大家常用的“UI层 Data层”两层模型。等业务慢慢复杂、出现多个数据源组合、状态机流转这些场景时再把领域层补回来也不迟。架构是为业务服务的不是反过来。5.4 模块循环依赖怎么排查多个模块之间难免出现“你依赖我、我依赖你”的尴尬。典型的表现为feature:login依赖data模块同时data模块又想去监听登录状态来刷新缓存于是一不小心把依赖指了回去。排查顺序我一般是这样的先用./gradlew :app:dependencies看一下模块依赖树哪个模块反向依赖了一目了然。再用IDE的Dependency View插件或ArchUnit测试把循环节点圈出来。最后把循环依赖的公共部分下沉到一个更低层级的模块双方都依赖它问题自然化解。比如“登录状态”可以下沉到core:session里而不是让data反向依赖feature:login。5.5 架构约束总被人打破怎么根治如果只是一两个人偶尔打破提醒一下就好。如果反复打破说明光靠“意识”已经不够了必须上工具。我的最终方案是把ArchUnit或Android Lint自定义检查接入CI流水线让约束变成一种硬性构建规则。比如Test fun dataLayerShouldNotDependOnUiLayer() { val classes ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) .importPackages(com.example.data) classes.shouldNotDependOn() .packages(com.example.ui, com.example.viewmodel) .check() }这条测试一旦跑了谁要是将来偷偷在data层里写了UI逻辑构建直接红掉。具象的失败比抽象的理念更能阻止手滑。结尾分层架构这条路我在不同项目里反复实践过最大的一个体会是架构不应该是画完图就束之高阁的。分层真正有价值的地方在于它需要用开发工具去落实、用自动化检查去养护才能在一次次迭代中活下来。如果你准备开始重构手头那个攒了无数“技术债”的Android工程我的建议是别想一步到位。选一条最痛的业务线按我上面说的渐进式迁移方案先走通一遍。等到账号体系、订单这类核心业务都被成功分层之后你会感受到那种久违的踏实感——改一处不用再祈祷其他角落别跟着爆炸。再分享一个我自己常年保留的小习惯每次发版前跑一遍架构检查测试和全量单元测试。只要这两样是绿的我心里就有底。架构这件事说到底不是给别人看的是用来让自己能睡个安稳觉的。
RELATED

相关推荐

深入理解人工智能 AI-RAN Alliance(AI无线接入网联盟)

深入理解人工智能 AI-RAN Alliance(AI无线接入网联盟)

AI-RAN Alliance(AI无线接入网联盟)是一个成立于2024年的全球性产业联盟,旨在推动人工智能(AI)与无线接入网(RAN)的深度融合,构建“AI原生”的下一代网络架构。该联盟发展迅速&#…

📅 2026/10/9 21:43:34
[特殊字符] OpenClaw 完整命令手册:从入门到精通的 CLI 终极指南(TaoToken 统一 Key 接入篇)

[特殊字符] OpenClaw 完整命令手册:从入门到精通的 CLI 终极指南(TaoToken 统一 Key 接入篇)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/9 21:38:34
FastAPI静态文件托管全解析:挂载、路由顺序与生产部署

FastAPI静态文件托管全解析:挂载、路由顺序与生产部署

做后端最容易被低估的环节,往往是静态文件请求。接口能跑通只是第一步,浏览器里能正常渲染出页面,才谈得上“能用”。FastAPI 本身是个异步 API 框架,处理 JSON 得心应手,但 HTML、CSS、JS、图片这类静态资源怎么托管&…

📅 2026/10/9 21:38:34
MORE NEWS

更多资讯

📰

适合办公的Agent工具:TRAE Work如何提升日常办公效率

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

矿山机器人现状与落地挑战:从巡检到数字孪生的技术解读

简介:围绕矿山机器人的现状、问题与发展的PPT学习教案,适合矿业工程、机器人技术及安全工程等相关专业的教学与自学场景。内容系统梳理了矿山机器人对煤矿安全和应急救援的重要性,对比美国、英国、德国、澳大利亚、日本及我国在救援机器人领域…

📰

FastGPT + OneAPI 构建知识库:把 endpoint 改到 TaoToken 的完整配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

变电站异物检测:168张VOC数据集的实战加载与模型适配

简介:本资源是面向电力系统智能化运维与计算机视觉算法研发者的专业图像数据集,聚焦变电站及输电线路场景下的异物识别任务,为安全巡检类目标检测模型的训练与验证提供高质量标注基础。数据集包含168张真实场景JPG图像及配套VOC格式XML标注文…

📰

微信小程序旅游服务平台源码解析:从项目结构到二次开发实战

1. 从一份“超全”标题说起:这类旅游小程序项目到底交付了什么我经常在技术社区里看到类似"【超全】基于微信小程序的旅游服务平台【包括源码文档调试】"这样的标题,说实话,第一反应是警惕,第二反应是好奇。警惕是因为&…

📰

相变材料工程落地指南:选型、封装与系统集成实战

1. 从实验室到货架:相变材料到底解决了什么问题第一次接触相变材料是在一个储能项目里,当时团队想给一个户外设备做恒温保护,试过加热片、保温棉、甚至半导体制冷,效果都不理想——要么耗电太狠,要么温度波动压不住。后…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬