Skip to content

工程目录结构

项目初创

一个Android项目刚创建时会生成一套默认的目录结构,它是 Android 架构和 Gradle 构建系统的基础。典型的项目目录如下:

my-android-project/
├── app/                          
│   ├── build/                    # 编译输出文件
│   ├── libs/                     # 外部库文件 (.jar/.aar)
│   ├── src/                      
│   │   ├── main/                 # 主应用代码和资源
│   │   │   ├── java/             # 应用的 Java/Kotlin 代码
│   │   │   ├── Kotlin/           # 应用的 Kotlin 代码 (7.0以下语言分类需要手动配置)
│   │   │   ├── res/              # 应用的资源文件
│   │   │   ├── AndroidManifest.xml  # Android 应用配置文件
│   │   └── test/                 # 单元测试代码
│   │   └── androidTest/          # Android UI 测试代码
│   ├── build.gradle              # 模块级 Gradle 构建脚本
│   └── proguard-rules.pro        # ProGuard 配置文件 (混淆规则)
├── build.gradle                  # 项目级 Gradle 构建脚本
├── settings.gradle               # 项目配置文件,用于定义项目模块 (7.0版本以后它可以用来管理插件、依赖、控制模块加载等)
└── gradle/                       
    ├── wrapper/                  
    │   ├── gradle-wrapper.jar    # Gradle Wrapper 可执行文件
    │   └── gradle-wrapper.properties  # Wrapper 配置文件
    └── libs.versions.toml # 7.0以后引入,该文件用于统一的依赖版本,简化版本控制

如上所示,在基础目录结构上,虽然某些文件和文件夹可以根据需要进行扩展或调整,但部分核心目录和层级结构是不可随意更改的。因此,在日常开发中,我们通常关注的是 main 和 res 目录中的变化,而其中最关键的部分就是项目的 包名目录res的目录结构一般是固定的

res 资源目录

基本轮廓如下,文件和文件夹新增都是有固定规则的。“一些目录下的资源文件需要有命名规范后续会提到”

res/
├── drawable/                   
├── drawable-xxhdpi/            
├── layout/                     
├── values/                     
│   ├── strings.xml             
│   ├── colors.xml              
│   └── styles.xml              
├── mipmap/                     
└── raw/

谷歌官方指南:《资源目录存放指引》

所以本文将重点探讨如何合理管理包名目录的层级结构。

包名目录

main 目录下,如果项目是Java和kotlin混开,那么代码可以分别存放在 javakotlin 目录中,或者只使用 java 目录。 如果java和kotlin文件都很多的情况下推荐根据语言类型进行区分,就像这样:

main/java/
    └──com/example/myapp
main/kotlin
    └──com/example/myapp

两个语言分类的目录下的包名目录都应该完全一致。

🚫 重要注意事项

Java 类严禁放在 kotlin 文件夹下,否则在编译后的 APK 中可能会找不到这些类。确保将 Java 类放在 java 目录下,以避免编译和运行时的错误。

正确做法:

├── main/
    ├── java/
        ├── com.example.myapp (Java 类放置这里)
    ├── kotlin/
        ├── com.example.myapp (Kotlin 类放置这里)

将Java类放入kotlin文件夹里:《引发的问题》,这个文章其实际写的不好,只说了问题的出现和解决,没有说原因。

  • Java 类为什么不能放在 Kotlin 文件夹中?
    1. 主要是因为 Android 项目的构建和编译机制是基于文件夹结构来区分语言类型的。将 Java 类放入 Kotlin 文件夹可能会导致编译器无法正确识别文件类型,进而引发类加载错误或运行时找不到类的情况。具体来说,Android 构建系统根据项目目录结构推断类的语言类型,例如 java 文件夹通常包含 Java 文件,kotlin 文件夹包含 Kotlin 文件。如果一个 Java 类误放入 Kotlin 文件夹中,可能会引发路径解析问题,从而导致无法正确编译和加载该类。

    2. 然而,Kotlin 类可以放在 java 文件夹中而不会出现类似问题,因为 Android 构建工具支持 Kotlin 文件与 Java 文件的混合编译,能够自动识别 Kotlin 文件并进行正确的处理。这种设计允许 Kotlin 类与 Java 类在同一项目中顺利协作。

  • 官方文档 java_kotlin_file_group.png《Android 官方文档》中,虽然没有明确表示java类不能放在kotlin文件夹中,但是也由 java 目录可同时包含 Java 和 Kotlin 源代码。kotlin优先 这些关键词隐含指出,kotlin 文件夹并没有设计用于混合代码类型,而是仅为 Kotlin 代码服务。如果将 Java 类放入此文件夹,可能会违反此隐性约定,引发潜在问题。

在进行开发时,包名目录的层级结构对项目的可维护性、可扩展性和代码的清晰度起到至关重要的作用。因此,开发者应当遵循一定的层级管理规范,确保项目代码能够清晰且有条理地组织。典型的包名目录结构如下:


├── /app # 存放应用全局变量和对象,一般Application也放在这里
├── /basic # 存放各个组件的通用基类 (取名basic或base都可以)
│   ├── /activity           # Activity 基类
│   ├── /fragment           # Fragment 基类
│   ├── /dialog             # Dialog 基类
│   ├── /holder             # ViewHolder 基类
│   └── .../                
├── /consts # 存放全局静态常量类
├── /model # 主要存放数据模型,也可以分其他子文件夹,如request(接口参数请求体)
├── /ui # 存放ui相关的东西
│   │   ├── /activity   
│   │   ├── /fragment   
│   │   ├── /dialog   
│   │   ├── /adapter  
│   │   │   ├── /holder # java一般会这么放,kotlin可以直接将holder也放到adapter里
│   │   ├── /widget # 一般通用的自定义view会放在这里  
│   │   ├── .../  
├── /utils # 工具类

每个项目选择的架构不同,包名目录下的目录结构就不同,但以上所示的目录结构可以视为一个通用的项目目录结构。因为我想一个再小的一个Android项目,通常也应具备这些基本目录。而且这些目录也会为后续选择扩展架构时提供了一个良好的起点。

常见目录扩展

除项目基础目录之外,还有一些常见的目录扩展,比如开发中用到的Android四大组件的其他各个组件、第三方框架、其他目录等。一般都会按照功能模块进行管理,以下是一个常见的目录组织方案。

组件目录

四大组件除了Activity和ui相关,被放置在ui目录下外,其他的三大组件都可独立使用,所以一般都会放置在包名目录下的一级目录中。如下:

  • Service
com/example/myapp/
            ├──app/
            ├──.../
            └──services/ # 管理各个服务组件
  • BroadcastReceiver
com/example/myapp/
            ├──app/
            ├──.../
            └──receivers/ # 管理各个广播组件(全局和本地广播都在此存放)
  • ContentProvider
com/example/myapp/
            ├──app/
            ├──.../
            └──providers/ # 管理各个内容提供器组件

第三方框架

随着引入的第三方框架和库增多,通常会将与第三方框架相关的代码分类管理,以便于项目的模块化和可维护性。比如以下常见框架:

  • 图片加载框架glide
groovy
// 图片加载框架:https://github.com/bumptech/glide
// 官方使用文档:https://github.com/Muyangmin/glide-docs-cn
implementation 'com.github.bumptech.glide:glide:4.15.1' // 核心依赖

但是如果你还引用了glide的compiler库,就会产生一些配置,所以就要新建一级目录,为了职责的单一原则。

groovy
kapt 'com.github.bumptech.glide:compiler:4.12.0' // 用于生成与 Glide 一起使用的代码
com/example/myapp/
            ├──app/
            ├──.../
            └──glide/ # 存放glide框架相关的配置类
  • 网络请求库
groovy
// OkHttp 开源地址:https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:3.12.13'

比如okhttp,当你引用了该库以后,就会有一些扩展或配置类存放在一个单独的http一级目录,就像这样:

com/example/myapp/
            ├──app/
            ├──.../
            └──http/ # 存放http相关
               ├──server/ # 服务器相关配置
               └──glide/ # 因为glide和网络加载相关,当有了http这样的一级目录时,glide就可以被移动到这里
  • 数据库框架
groovy
// room 是一个 SQLite 数据库的封装库,提供了更高级别的抽象,简化了数据库的操作。
// 开源地址: https://github.com/androidx/androidx/tree/androidx-main/room
// 官方文档: https://developer.android.com/jetpack/androidx/releases/room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
annotationProcessor(libs.androidx.room.compiler)

比如引用了room,那么必然会产生相关配置和具体数据库和表的相关派生类,那么也会去新建一级目录:

com/example/myapp/
            ├──app/
            ├──.../
            └──db/ # 数据库相关
               ├──dao/ # dao层分类
               ├──entity/ # 相关实体类
               └──.../
  • 依赖注入
groovy
// 依赖注入库 Hilt 是谷歌推荐的用于 Android 的依赖注入框架,可以简化依赖的创建和管理过程。
// 开源地址: https://github.com/google/dagger
// 官方文档: https://developer.android.com/training/dependency-injection/hilt-android
implementation(libs.hilt.android)

// Hilt 的注解处理器(compiler)在编译时生成代码,用于自动创建依赖类,从而实现自动注入。
// 开源地址: https://github.com/google/dagger
// 官方文档: https://developer.android.com/training/dependency-injection/hilt-android
kapt(libs.hilt.compiler)

同样这个库也需要一个配置类,标记负责提供依赖:

com/example/myapp/
            ├──app/
            ├──.../
            └──di/ # 依赖注入

其他目录

比如管理一些注解:

com/example/myapp/
            ├──app/
            ├──.../
            └──annotation/ # 存放自定义的注解类

还有像kotlin中的扩展函数:

com/example/myapp/
            ├──app/
            ├──/...
            └──/extension # 管理各扩展函数的分类

多个任务类:

com/example/myapp/
            ├──app/
            ├──/...
            └──/tasks # 存放各个任务类

小总结

通过对常见目录的扩展我们能够发现新建一级目录时,有一些共同的原则,这些原则通常基于以下几个核心点,以确保项目结构清晰、易于维护和扩展。

  • 单一职责原则

每个一级目录应专注于一个职责或模块。避免将不相关的代码或功能混合在同一个目录下。

  • 功能聚合原则

一级目录应该围绕项目的核心功能模块或应用层级来划分。像某些功能在项目中具有独立的业务逻辑或用途,且代码量较大,可以为其单独创建一级目录。

  • 可维护性和可扩展性

一级目录的设计要有足够的灵活性,以便随着项目的扩展,新的功能模块或业务逻辑能够方便地加入,而不需要大幅度重构项目结构。例如:

可扩展的目录设计:

/service/ 可以用于存放所有服务类,方便后续增加新的业务服务。
/model/ 可以存放所有数据模型,后续可以根据需要划分不同的子目录。

当然还需再注意避免过度分类

避免过度分类

不要在初期过度创建过多一级或子级目录,尤其是当功能模块还没有发展到足够复杂的程度时。优先保持简单的目录结构,当代码规模扩大后再细分。

架构目录扩展

在架构设计上,一级目录应根据项目的实际架构来进行层次来划分。业务逻辑层、数据层可以分别放在不同的一级目录中,保持架构的清晰性。

mvvm

反应分层架构,将MVVM架构中的ViewModel层进行了拆分,分为了ViewModel和Repository两层,使得ViewModel层更加轻量化,更加专注于UI逻辑处理,而将api接口对接、数据获取、数据处理等操作交给了Repository层,使得ViewModel层更加专注于UI逻辑处理,使得代码更加清晰,更加易于维护。

目录层级划分

com/example/myapp/
            ├──app/
            ├──.../
            ├──model/ # 存放数据模型类
            ├──sources/ # 数据源
            │  ├──local/ # 本地api接口和实现类
            │  ├──remote/ # 远端api接口和实现类
            │  └──repository/ # 存放数据仓库接口和实现类
            ├──viewmodel/ # 存放viewmodel类
            └──.../

保持一级和子级目录的单一职责原则,避免将不相关的类混合在一起。

多提一嘴

单一职责体现在软件开发的各个环节,ViewModel中尽量避免业务逻辑和视图控制代码高度耦合,否则就和 MVC 模式中的耦合没有本质区别,只是耦合场景发生了变化。这种情况会使得维护和扩展变得格外复杂,因为 ViewModel 不再是一个简单的数据承载者,而是承担了过多的职责。

总结

工程目录的合理管理有助于提高代码的可维护性、可读性和可扩展性。本文通过对包名目录、常见组件和扩展目录进行讲解和示例,并提供一些常见的原则和注意事项,帮助开发者在日常开发中更好地组织项目结构。

目录扩展原则

1.单一职责

每一级目录应专注于一个职责。

2.功能聚合

目录应该围绕项目的核心功能或模块划分。

3.可维护性和扩展性

目录设计要灵活,能够适应未来的功能扩展。

此外,开发者需要避免过度分类,保持目录简单易懂,并根据项目需求适时调整和扩展。

在 Apache-2.0 许可证下发布。