一 data 文件夹作用
在 Android 项目架构中,data
文件夹(或称为“数据层”)是负责数据的获取、存储、转换和管理的核心模块,是应用与底层数据源(如网络、数据库、本地文件等)交互的“桥梁”。它的核心职责是为上层(尤其是领域层 domain
)提供干净、可靠的数据,同时屏蔽具体数据源的细节(比如是从网络接口还是本地数据库获取数据)。
data 文件夹的核心作用
统一管理数据源
应用的数据来源通常是多样的(网络 API、本地数据库、SharedPreferences、文件等),data
层会集中管理这些数据源,对外提供统一的访问接口,让上层(领域层)无需关心数据来自哪里。数据转换与适配
原始数据(如网络接口返回的 JSON 结构、数据库表结构)往往不直接适合业务使用,data
层会将这些原始数据(entity
)转换为适合上层处理的模型(model
),比如:- 把网络接口的时间戳字段转换为格式化的日期字符串
- 合并本地数据库和网络的零散数据为完整的业务模型
数据持久化
负责数据的本地存储逻辑,比如用 Room 操作数据库、用 DataStore/SharedPreferences 存储轻量数据,确保应用在离线时也能访问关键数据。实现领域层的抽象接口
数据层会实现领域层(domain
)定义的Repository
接口(抽象),通过这种“依赖倒置”的方式,让领域层与具体数据源解耦(领域层只依赖抽象,不依赖具体实现)。
data 文件夹的典型内部结构
data
层的包结构通常按“数据源类型”和“功能职责”划分,常见结构如下:
com.example.app.data/
├─ entity/ // 原始数据实体(与数据源一一对应)
│ ├─ local/ // 本地数据库实体(如 Room 的 @Entity 类)
│ │ └─ UserEntity.java // 对应数据库中的 user 表
│ └─ remote/ // 网络接口实体(如 Retrofit 解析的 JSON 类)
│ └─ UserRemote.java // 对应网络接口返回的用户数据结构
│
├─ model/ // 数据层内部的模型(用于转换后的数据传递)
│ └─ UserDetailModel.java // 合并本地和网络数据后的模型
│
├─ repository/ // 仓库实现类(实现 domain 层的 Repository 接口)
│ └─ UserRepositoryImpl.java // 实现 domain 定义的 UserRepository 接口
│
├─ datasource/ // 数据源操作类(直接与底层交互)
│ ├─ local/ // 本地数据源(如数据库操作)
│ │ └─ UserLocalDataSource.java // 用 Room 操作本地用户数据
│ └─ remote/ // 远程数据源(如网络请求)
│ └─ UserRemoteDataSource.java // 用 Retrofit 调用用户相关接口
│
└─ mapper/ // 数据转换工具(Entity ↔ Model)
└─ UserMapper.java // 将 UserEntity 转换为 UserDetailModel
data 层的核心组件
Entity(实体)
直接映射数据源的原始数据结构,不包含业务逻辑,仅作为数据的“容器”。例如:- 网络接口返回的 JSON 字段会被解析为
remote
包下的XXXRemote
类 - 数据库表结构会被定义为
local
包下的@Entity
类(Room)
- 网络接口返回的 JSON 字段会被解析为
DataSource(数据源)
直接操作底层数据源的类,封装了具体的读写逻辑:LocalDataSource
:操作本地数据库、文件等,如UserLocalDataSource
包含getUserFromDB()
、saveUserToDB()
等方法RemoteDataSource
:调用网络接口,如UserRemoteDataSource
包含fetchUserFromApi()
等方法
Repository Impl(仓库实现)
实现领域层(domain
)定义的Repository
接口,协调多个数据源(本地+远程)的交互,是数据层对外的“统一出口”。例如:// 实现 domain 层的 UserRepository 接口 public class UserRepositoryImpl implements UserRepository { private final UserLocalDataSource localDataSource; private final UserRemoteDataSource remoteDataSource; @Override public UserDetail getUserId(String userId) { // 先查本地缓存 UserEntity localUser = localDataSource.getUser(userId); if (localUser != null) { return UserMapper.toDomainModel(localUser); } // 本地没有则请求网络 UserRemote remoteUser = remoteDataSource.fetchUser(userId); // 缓存到本地 localDataSource.saveUser(UserMapper.toEntity(remoteUser)); return UserMapper.toDomainModel(remoteUser); } }
Mapper(映射器)
负责数据格式的转换,将Entity
(原始数据)转换为Model
(业务模型),或转换为领域层需要的Domain Model
。例如:public class UserMapper { // 网络实体 → 领域模型 public static UserDomain toDomainModel(UserRemote remote) { return new UserDomain( remote.getId(), remote.getName(), calculateAge(remote.getBirthday()) // 转换时间戳为年龄 ); } }
data 层与其他层的关系
- 依赖领域层:
data
层会依赖domain
层定义的Repository
接口和Domain Model
,但不会被领域层依赖(符合依赖倒置原则)。 - 被上层调用:领域层的
UseCase
通过调用data
层实现的Repository
接口获取数据,无需关心数据来自本地还是网络。 - 不依赖 UI 层:
data
层专注于数据处理,不涉及任何 UI 相关的类(如Activity
、ViewModel
),确保可独立测试。
总结
data
文件夹是 Android 项目的“数据中枢”,负责:
- 管理所有数据源(本地/远程)的读写
- 将原始数据转换为适合业务的模型
- 实现领域层的抽象接口,为上层提供统一的数据访问入口
通过 data
层的封装,上层业务逻辑可以专注于处理业务规则,而无需关心数据的具体来源和格式,这是实现“高内聚、低耦合”架构的核心环节。
二 model
、domain
、entity
、ui
的区别
在Android项目开发中,model
、domain
、entity
、ui
是常见的包结构划分,其核心目的是通过职责分离实现代码的可维护性、可测试性和扩展性。不同包对应不同的功能层次,下面详细解释它们的区别和使用场景。
一、四个包的核心区别与职责
1. entity
:数据实体层(最底层,原始数据载体)
核心职责:存储和传递“原始数据”,对应数据源的直接映射(如数据库表、网络接口返回的原始数据),不包含业务逻辑。
本质:数据的“最小单元”,是数据在系统中的“原始形态”。
具体场景:
- 数据库实体:如 Room 注解的
@Entity
类(对应数据库表结构)。 - 网络实体:如 Retrofit 接口返回的 JSON 解析类(直接映射接口字段,无额外处理)。
- 本地存储实体:如 SharedPreferences 存储的原始数据模型。
特点:
- 字段与数据源(数据库/接口)严格对应,不做业务转换。
- 无复杂逻辑,仅包含 getter/setter 或数据校验(如非空判断)。
2. model
:数据模型层(中间层,业务适配数据)
核心职责:对 entity
进行转换、封装或扩展,提供“适合业务场景”的数据模型(如 UI 展示、业务计算所需的数据格式)。
本质:原始数据(entity)的“加工品”,适配特定业务需求。
具体场景:
- UI 展示模型:将
UserEntity
(包含birthTimestamp
时间戳)转换为UserModel
(包含age
年龄字段,便于 UI 直接显示)。 - 业务计算模型:将多个
OrderEntity
聚合为OrderSummaryModel
(包含总金额、订单数量等计算后的数据)。 - 跨数据源整合模型:合并本地数据库
UserEntity
和网络接口UserExtraEntity
为UserDetailModel
。
特点:
- 依赖
entity
,通过转换工具(如 Mapper 类)生成。 - 包含数据转换逻辑(如时间戳转年龄、金额格式化),但不包含核心业务逻辑(如订单状态判断)。
3. domain
:领域层(核心层,业务逻辑中枢)
核心职责:封装核心业务逻辑、用例和领域模型,是系统的“大脑”,独立于具体框架(不依赖 Android 类)。
本质:定义“业务规则”,协调数据的获取、处理和流转。
具体场景:
- 用例(UseCase):如
GetUserDetailUseCase
(封装“获取用户详情”的业务流程:调用仓库获取数据→校验→返回结果)。 - 领域模型(Domain Model):抽象的业务实体(如
User
领域模型,包含业务核心属性,与数据源无关)。 - 仓库接口(Repository Interface):定义数据获取规范(如
UserRepository
接口,由数据层实现)。 - 业务工具类:如
OrderStatusCalculator
(判断订单状态的核心逻辑)。
特点:
- 不依赖 Android 框架(无
Context
、Activity
等类),可独立测试。 - 是连接数据层(
entity
)和表现层(ui
)的桥梁,不关心数据从哪里来(本地/网络),只关心如何处理。
4. ui
:表现层(最上层,用户交互)
核心职责:负责用户界面展示和交互,接收用户输入并反馈结果,依赖领域层获取数据。
本质:数据的“展示窗口”和用户交互的“入口”。
具体场景:
- 界面组件:
Activity
、Fragment
、Compose
页面等。 - 视图模型:
ViewModel
(管理 UI 数据,与领域层交互获取数据)。 - 交互相关:适配器(
Adapter
)、自定义 View、点击事件处理器等。
特点:
- 依赖
domain
层(通过ViewModel
调用UseCase
)或model
层(直接使用 UI 模型)。 - 不处理核心业务逻辑,仅做数据展示和用户输入传递。
二、Android 项目建包建议(结合架构分层)
在实际开发中,包结构通常与架构(如 Clean Architecture、MVVM)结合,核心原则是**“高内聚、低耦合”**,即每个包只做一类事,层与层通过接口交互。
推荐的包结构示例(以 Clean Architecture 为基础)
com.example.app/
├─ data/ // 数据层:处理数据获取和存储
│ ├─ entity/ // 原始数据实体(数据库、网络、本地存储)
│ │ ├─ local/ // 本地数据库实体(Room Entity)
│ │ └─ remote/ // 网络接口实体(Retrofit 解析类)
│ ├─ model/ // 数据层内部模型(可选,数据转换用)
│ └─ repository/ // 仓库实现(实现 domain 层的 Repository 接口)
│
├─ domain/ // 领域层:核心业务逻辑
│ ├─ model/ // 领域模型(抽象业务实体,与数据源无关)
│ ├─ repository/ // 仓库接口(定义数据获取规范)
│ └─ useCase/ // 用例(封装具体业务流程)
│
├─ ui/ // 表现层:UI 展示和交互
│ ├─ activity/ // Activity 组件
│ ├─ fragment/ // Fragment 组件
│ ├─ compose/ // Compose 页面(如用 Compose 开发)
│ ├─ adapter/ // 列表适配器
│ └─ viewModel/ // ViewModel(管理 UI 数据,调用 UseCase)
│
└─ common/ // 通用工具(全局工具类、常量等)
各包的协作流程(以“获取用户详情”为例)
- UI 层触发需求:用户在
UserDetailActivity
点击“查看详情”,UserDetailViewModel
接收事件。 - 调用领域层用例:
ViewModel
调用domain.useCase.GetUserDetailUseCase
。 - 领域层协调数据:
GetUserDetailUseCase
调用domain.repository.UserRepository
接口(不关心具体实现)。 - 数据层实现接口:
data.repository.UserRepositoryImpl
实现接口,从本地数据库获取entity.local.UserEntity
,从网络获取entity.remote.UserExtraEntity
。 - 数据转换为模型:数据层将
UserEntity
和UserExtraEntity
转换为data.model.UserDetailModel
,返回给领域层。 - 领域层处理业务:用例对
UserDetailModel
进行业务校验(如判断会员状态),转换为domain.model.User
领域模型。 - UI 层展示数据:
ViewModel
将domain.model.User
转换为ui.model.UserUIModel
(如格式化生日为年龄),通过LiveData
通知 UI 刷新。
三、注意事项
避免职责混淆:
- 不要在
entity
中写业务逻辑(如计算年龄),这是model
或domain
的职责。 - 不要在
ui
层(如ViewModel
)中写核心业务逻辑,应放在domain
的UseCase
中。 domain
层不依赖 Android 框架,确保可独立单元测试(用 JUnit 而非 InstrumentedTest)。
- 不要在
灵活调整粒度:
- 小型项目可简化结构,例如将
entity
和model
合并(如果数据无需复杂转换)。 - 大型项目可进一步细分,如
ui
层按功能模块拆分(ui.user
、ui.order
)。
- 小型项目可简化结构,例如将
依赖方向:
严格遵循“上层依赖下层,下层不依赖上层”:ui
依赖domain
和model
,但domain
和model
不依赖ui
。domain
依赖自身的repository
接口,不依赖data
层的实现。data
层依赖domain
的接口,实现数据获取逻辑。
通过合理划分这四个包,Android 项目可以实现“业务逻辑与 UI 分离、数据与业务分离”,大幅提升代码的可维护性和扩展性,尤其适合中大型项目。