Android APP目录结构完全解析:从根目录到资源与Gradle配置
我记得刚接触Android开发那会儿光是把一个新建工程里各个目录和文件的功能搞明白就花了我不少功夫。别看现在各种框架和工具链越来越完善Android APP目录结构依然是每个开发者绕不开的基本功——无论是排查构建失败、做资源适配还是优化包体积最终都要落到你知道哪个文件在哪、它干什么用这件事上。这篇文章就把我这些年对Android工程目录结构的理解完整拆一遍从工程根目录到资源文件再到Gradle配置一步步说清楚每个目录存在的意义和实际操作中应该注意的坑。这篇文章适合谁刚入门Android、对着Android Studio新建的工程一脸懵的新人以及做了几年开发但一直在用IDE自动生成目录、没仔细想过结构设计逻辑的朋友。搞懂这套结构你排查问题会快很多也不容易在多人协作时把工程搞得一团乱。1. 为什么每个Android开发者都应该把目录结构刻在脑子里先说个我自己的亲历场景。某次接手一个别人做到一半的模拟项目X工程打开第一眼源代码、资源文件、第三方库的so文件混在一起values目录里塞了一堆layout文件drawable和mipmap里图片随便放release和debug的资源混淆开关也没分开。结果打包release版本的时候图片资源硬生生多了十几兆启动闪屏图因为放错了mipmap目录在某些机型上显示模糊。排查下来根源就是目录结构从一开始就没规划好。Android APP目录结构本质上是约定优于配置这套理念在移动端的体现。Gradle构建系统默认按固定路径去寻找源码、资源、清单文件——你不需要告诉它我的MainActivity在哪个目录因为它默认就是src/main/java下的那个路径。这个约定简化了构建配置但也意味着你一旦乱放文件构建时找不着资源、清单合并失败这些妖问题就会接踵而来。另一个层面目录结构直接决定了代码的可维护性。多人协作时如果每个人对layout放哪、drawable放哪、values放哪、模块怎么划分的理解不一致review代码和merge分支的时候冲突多到让人头皮发麻。我见过不少团队一开始不重视结构等到业务膨胀到几十个模块再回来重构成本是当初规划的好几倍。所以不管是个人项目还是团队项目把这个结构吃透受益是长期的。再说实际价值编译报错时什么resource linking failedfile not found十有八九和资源的目录归属有关打多渠道包时要控制资源混淆、按屏幕密度放不同尺寸的图分析包体积时逐目录排查哪类资源占了大头。这些操作全部依赖你对目录结构的敏感度。有了这份敏感度你拿到一个陌生工程十分钟内就能摸清它的代码量、资源组织方式、模块划分逻辑对快速上手项目非常有帮助。2. 工程级目录从项目根目录开始的整体认知用Android Studio创建一个新工程后默认会在根目录生成一大串文件和文件夹。很多新手习惯只盯着src看其实根目录这些文件才是整个工程能被Gradle识别和构建的基础它们决定了这个项目用什么版本的工具链、依赖哪些仓库、有哪些模块。2.1 根目录核心文件逐个说根目录下最显眼的通常是这几个settings.gradle、build.gradle、gradle.properties以及gradle目录和gradlew脚本。settings.gradle是工程的总闸门它告诉Gradle这个项目包含哪些模块。假如你打开settings.gradle会看到类似include :app这样的语句有些多模块工程还会有:library, :core等。这里有一个操作细节新增一个模块时IDE通常会帮你自动改这个文件但如果你想手动建模块目录然后加进来改的就是这里。改了之后还要Sync ProjectGradle才会识别新模块。根目录的build.gradle是顶层构建脚本它不负责编译具体的模块代码而是给所有子模块声明共享的配置。最典型的是plugins块里的版本声明比如com.android.application插件版本、Kotlin版本等。你在顶层build.gradle里声明了插件的版本子模块里只需要应用插件而不用重复写版本号。在大型项目里还会把依赖版本统一抽到这里或者单独的version catalog里便于升级管理。gradle.properties是Gradle构建的JVM参数和全局属性配置比如jvmargs用来设置构建时的堆内存大小。很多构建卡顿、内存溢出问题改的就是这个文件里的-Xmx参数。另外android.useAndroidXtrue这个开关也在这里如果项目要迁移到AndroidX这一行是关键。再说gradlew和gradlew.bat分别是Unix和Windows下的Gradle包装脚本。它们配合gradle/wrapper目录下的gradle-wrapper.properties固定了项目使用的Gradle版本。这样团队协作时不管谁拉取代码用的Gradle版本都是一致的避免我本地能构建你那儿不行这种版本差异问题。我建议所有项目都使用Wrapper方式构建尽量别用本地安装的全局Gradle免得版本漂移带来诡异问题。2.2 app模块目录与多模块工程默认工程只有一个app模块这是Android应用的载体模块。app目录下同样有一个build.gradle这个才是真正配置应用插件、SDK版本、依赖库的地方。单模块项目结构简单但一旦业务变大你会需要拆出更多模块比如独立的网络模块、独立的UI组件库模块。这时根目录会出现多个与app平级的模块目录各自的目录内部又是相似的src/main结构。模块拆分带来的目录结构变化值得提前了解。Android Studio的Project视图下每个模块都是独立目录模块之间通过顶层settings.gradle注册然后在高层的build.gradle里统一管理依赖版本。这种多模块结构把代码物理隔离编译时Gradle会针对每个模块单独构建改动一个模块不会全量重编其他模块提升构建速度的同时也让职责边界更清晰。不过模块化也不是越多越好模块间的依赖关系如果理不清构建时反复重新计算依赖反而拖慢速度。2.3 Project视图与Package视图的切换Android Studio默认展示的是Project视图它忠实地反映了磁盘上的真实目录结构。但很多人工作一段时间后更喜欢切到Package视图这个视图会合并一些空目录同时把java和res目录里的文件自动按包结构分组看起来更符合代码逻辑。需要特别提醒的是切换视图不会改变磁盘上文件的实际位置只是显示方式不同。如果你在Package视图下新建了一个包然后在Project视图下去找会发现它实际位于src/main/java下的对应路径中间可能自动插入了一些父级包目录。正确使用这两个视图能帮你兼顾按逻辑看代码和按文件操作资源。我的习惯是写代码时用Package视图排查资源文件、处理manifast合并问题时切到Project视图。别小看这个习惯遇到布局文件明明存在但R文件引用不到这类问题在Project视图下能看到真实的目录路径比在Package视图下瞎猜快得多。3. 源码目录Java/Kotlin代码的组织方式源码目录是整个工程的核心它默认位于src/main/java下使用Kotlin时路径同样是src/main/javaKotlin文件与Java文件会放在一起。需要注意的是有些历史工程或者特殊配置会用src/main/kotlin但Android Studio默认新建的Kotlin工程文件也还是放在java目录里这点刚转Kotlin的朋友容易困惑。3.1 src/main的三层结构在app模块下展开src第一层通常有main和androidTest、test三个目录。main是正式代码和资源所在androidTest放仪器化测试代码跑在真机或模拟器上test放本地单元测试代码跑在JVM上。这三者物理隔离测试代码即使写错了也不会污染正式的App逻辑构建时Gradle也会区分处理。main目录下又有三个关键子目录java放置源码、res放置资源、AndroidManifest.xml清单文件。如果你看到main下面多了assets目录或者jniLibs目录那也是正常的。assets用于存放原始资源文件比如字体文件、WebView加载的本地HTML、游戏资源包等它和res的区别在于assets下的文件不会生成资源ID只能通过AssetManager按文件名读取jniLibs存放.so动态库通常按ABI架构再分子目录比如armeabi-v7a、arm64-v8a每种架构放一份对应的so文件。3.2 包名与Application ID两个经常被混淆的概念在java目录下你会看到一层层的包目录。默认情况下新建工程的包名路径长得跟applicationId一样比如com.example.myapp于是很多人以为它们是同一个东西。实际不是——包名是源码的命名空间用于定位Java/Kotlin类比如com.example.myapp.MainActivity这个全类名applicationId是应用在应用市场和系统眼中的唯一标识它写在build.gradle里、体现在AndroidManifest的package属性中。两者可以不同很多大厂会把applicationId设为com.company.app而源码包名保持com.company.internal这样的内部命名方式。修改applicationId不会影响源码目录结构但如果你为了“看起来一致”去手动改源码的包目录需要同步修改所有文件里的package声明以及AndroidManifest里的相关引用操作量不小且容易漏。我遇到过有人直接改目录名而忘记改package声明编译时一堆类找不到排查了老半天。所以一般不建议动默认的包目录真要改的话优先用IDE的重构功能而不是手动挪文件。3.3 test目录与androidTest目录的定位test和androidTest目录容易被忽略但它们对代码质量非常重要。test目录下的单元测试不依赖Android框架直接跑在本地JVM上适合测试纯逻辑部分比如Utils工具类、数据解析模块。androidTest目录下的测试需要构建出APK并在设备上运行适合做UI测试、数据库测试、组件交互测试。目录结构上这两个目录跟main一样有java子目录包结构也跟main保持一致这样测试类才能访问被测类的包级私有成员。新建测试类时IDE默认会放到对应包路径下别为了图省事全部塞到一个测试根包里破坏包结构会让测试的可读性和可维护性明显下降。4. 资源目录res目录的完整地图如果源码目录是工程的大脑那res目录就是工程的脸面——所有界面、文案、颜色、图片都从这里来。Android的资源系统通过AAPTAndroid Asset Packaging Tool把res下各类文件编译、索引生成R类中的静态常量代码里通过R.drawable.xxx这样的引用来访问。搞懂res的子目录划分是资源管理的核心。4.1 drawable与mipmap图片该放哪个文件夹很多新手一开始搞不清drawable和mipmap的区别甚至把启动图标放进drawable。官方建议是应用图标ic_launcher放mipmap因为它会根据不同屏幕密度自动选择合适尺寸在布局或代码中用到的普通图片放drawable。drawable目录后面还常跟着密度的后缀例如drawable-xxhdpi、drawable-mdpi这些后缀决定了设备在加载图片时会优先选择哪个密度的资源。一个常见误区是为了省事只放一套高分辨率图让系统兼容低密度设备时自动缩放这样确实能显示但会浪费内存而且低端机容易出现OOM。实践上至少要适配mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi这五档中的主流档位。drawable目录里除了图片还支持XML类型的Drawable资源比如selector根据状态切换背景、shape圆角矩形、渐变、layer-list图层叠加等。这些XML资源放在drawable目录下并无密度后缀系统会统一处理。相比切图用XML Drawable实现圆角、按下效果、进度条样式体积小、适配好是我非常推荐的做法。比如一个按钮的背景用一个shape XML比五张不同密度的点九图要轻量得多而且改颜色只需改一个字段。4.2 layout布局目录的组织方法layout目录存放界面布局XML文件。布局文件的命名业内普遍采用视图类型_业务场景的方式比如activity_main、fragment_home、item_goods_list。这样命名的好处是看到名字就知道这个布局对应哪个页面或哪个列表项。有些团队会把布局再细分到子目录下比如layout/activity、layout/fragment、layout/item但Android默认并不支持layout下再分子目录强制分的话需要在build.gradle里配置sourceSets没有特殊需求我不建议这么干会增加配置复杂度收益有限。布局文件里还有一个常用技巧通过include标签复用公共布局通过ViewStub延迟加载不紧急的视图。include的布局文件通常会放在layout/common或者layout/widget这种子命名空间下自身不依赖Activity纯粹作为可复用组件存在。ViewStub则适合那些不一定展示的模块比如网络错误提示、新手引导用了它可以让首帧渲染更快目录结构上依然归类到layout目录即可。4.3 values系列不要把所有东西都塞进strings.xmlvalues目录下默认有strings.xml、colors.xml、dimens.xml、styles.xml等它们都是资源定义文件。资源文件里的每一项都会生成对应的R引用比如strings.xml里的app_name字符串代码和布局里都能通过R.string.app_name访问。很多小项目习惯把所有字符串都堆在strings.xml里这个做法在项目变大后会非常痛苦——一个上千行的strings.xml检索麻烦翻译多语言时也不好维护。我建议按页面或模块拆分比如strings_home.xml、strings_login.xml、strings_common.xml只要资源名不重复Gradle构建时会自动合并代码里的引用方式完全不变。colors.xml和dimens.xml同样建议按语义来定义而不是按具体值来定义。比如颜色用color_primary、color_bg_page这种语义名而不是blue_500这种值名尺寸用dp_4、dp_8这类带数字的命名还是用空间名如space_small、space_medium团队内部定好规范即可。关键是避免同一个颜色值在布局里散落出现十几次——后期设计师调整色值你得一处处找很容易漏。用语义化资源名集中管理这种问题就不再是问题了。styles.xml用于定义主题和样式它与AndroidManifest里的application主题配置直接关联。如果工程的UI主题统一可以定义AppTheme作为基础样式然后针对不同场景派生样式夜间模式、深色模式适配时可以配置values-night目录放一套夜间资源。这里的目录结构逻辑也是通过资源限定符实现系统根据当前状态选择加载哪套资源而不是你在代码里手动切换。4.4 限定符与多屏幕适配目录名里的学问res目录下的子目录名可以带上各种限定符用来限定资源适配的条件。最常见的是密度限定符drawable-hdpi、drawable-xhdpi等还有屏幕尺寸限定符layout-small、layout-large以及API级别限定符values-v21、drawable-v24还有语言限定符values-en、values-zh-rCN等。这些限定符的组合使用构成了Android资源适配的核心机制。系统在运行时会根据设备的当前配置在资源表中匹配最合适的资源项匹配不到的则使用无后缀的默认资源。这个机制的实际用途非常广泛。例如沉浸式状态栏的样式你可以在values-v21里定义使用系统API的新样式在values默认目录里定义旧样式兼容版本再比如横竖屏不同布局在layout和layout-land里各放一套布局。我见过不少项目适配做不好原因是只放了默认目录的资源不同设备上显示效果只能靠系统硬撑但适配也不等于每一种限定符都放一套资源那样APK体积会爆炸合理做法是针对主流配置做有限维度的适配以项目实际需求为准。5. AndroidManifest.xml整个APP的地基AndroidManifest.xml是Android应用的核心配置文件它不仅是给开发者看的更是给系统安装器、给组件扫描器看的说明书。这个文件在src/main目录下多模块工程里每个模块也可以有自己的Manifest构建时Gradle会按规则合并为一个最终Manifest。大部分构建问题比如权限冲突、Activity无法找到、provider重复声明都和Manifest配置有关。5.1 四大组件的声明manifest根节点下activity、service、receiver、provider四大组件都需要在这里注册。以activity为例一个页面如果要被系统识别为可启动入口需要在intent-filter里声明MAIN和LAUNCHER两个action/category否则它虽然能在应用内部通过Intent跳转但不会出现在桌面启动图标菜单中。很多新手遇到安装后点图标没反应的问题八成就是入口Activity没有正确配置或者配置到了别的Activity上。组件声明里还有个容易踩的坑Activity如果设置了exportedtrue那表示其他应用可以调用它。如果你的某个Activity包含WebView并且处理了外部URL安全建议是不要让exported随便为true。targetSdkVersion升级到31及以上之后系统对exported有明确强制要求不显式声明的话编译就会报错。这块内容在目录结构看起来只是一个配置节点但出了问题往往是安全漏洞级别的需要格外重视。5.2 权限声明与权限策略use-permission节点申明的是App需要的系统权限。新手常见错误是往Manifest里堆一堆权限不管用不用的到先声明再说。这不仅影响审核还会降低应用安全性。比如一个纯离线工具应用如果声明了READ_CONTACTS很容易被市场判定为过度索取隐私。权限的使用原则是运行时权限要动态申请Manifest里只声明那些必须在安装时授予的权限和用于功能权限申请时的声明依据。另外系统权限分组和maxSdkVersion属性也值得了解——某些老旧权限在新版本已废弃加上maxSdkVersion可以避免在高版本系统上被误判。5.3 application节点与全局配置application节点里定义了应用级别的属性icon、label、theme、allowBackup、supportsRtl等。icon指向mipmap下的图标资源label指向字符串资源theme指向styles里的主题。allowBackup如果你不希望用户数据被备份带走比如包含敏感缓存的App可以设为false不过默认值是true需要显式改。还有networkSecurityConfig可以指向一个网络安全配置文件用于控制明文流量、信任证书的范围。这个配置文件一般放在res/xml目录下如果项目对网络安全有要求会用得上。6. Gradle构建体系目录结构背后的驱动力表面上看目录结构是文件放哪的约定但真正把这些目录串起来、决定它们如何参与构建的是Gradle的构建体系。理解build.gradle的层级逻辑你对目录结构的认知就会从死记硬背升级到动态理解。6.1 构建脚本与目录的对应关系每个模块目录下的build.gradle文件本质上就是该模块的构建配置清单。它声明了模块类型application还是library、SDK编译版本、依赖库列表、签名配置、混淆配置等。顶层build.gradle维护了所有模块共享的插件与依赖版本。还有settings.gradle决定哪些模块参与构建。这三个文件与目录结构的对应关系是settings.gradle决定目录树上哪些模块被纳入build.gradle定义模块内部如何编译gradle.properties定义构建运行参数。排查构建问题时我习惯先看这三个文件有没有异常再去看具体代码和资源。6.2 构建产物build目录里发生了什么每次构建Gradle会在模块下生成build目录里面存放中间产物和最终APK。build/outputs/apk/debug和release分别对应调试包和正式包build/intermediates存放合并后的Manifest、R.java、资源索引等中间态数据。很多人遇到改了Manifest但运行没效果的怪问题其实是改了源码里的Manifest而运行时用的是intermediates里合并后的Manifest——clean一下目录再重新构建就会解决。理解了build目录的生成机制这类问题就不再是玄学。6.3 签名、混淆与构建类型文件签名文件通常放在模块根目录或者工程根目录的keystore目录下key.properties里保存签名文件路径和密码这个文件一定不能提交进版本库。混淆配置proguard-rules.pro在模块根目录与build.gradle的minifyEnabled开关配合使用。release和debug两个构建类型可以在build.gradle里分别配置不同的混淆策略、签名信息代码目录上则可以通过src/debug和src/release子目录放各自独有的资源和源文件。合理利用这些构建类型对应的目录调试代码和正式代码可以物理隔离避免把调试逻辑带到线上。7. 常见问题与排查技巧实录关于目录结构下面这些都是我在实际开发中反复遇到的典型问题和排查思路整理成速查表方便大家对照。现象可能原因排查思路R文件找不到或资源引用报错资源文件放错目录、资源命名含非法字符、资源冲突先看R文件报错位置再检查res目录下资源归属与命名是否符合规范最后Clean工程改了Manifest没生效使用了合并后的Manifest缓存清理build/intermediates重新Sync并构建新增模块后Gradle不识别settings.gradle未注册模块检查include语句补上新模块路径后Sync桌面图标显示为默认图标图标放在drawable而非mipmap或manifest的icon指向错误检查manifest的icon引用路径确认mipmap目录与资源名多语言文案不生效限定符目录名写错检查values-zh-rCN等目录命名是否标准注意大小写与中划线打包release后体积异常大多余的so库或未开启资源缩减检查jniLibs架构目录、开启shrinkResources、按密度精简图片资源名与系统资源冲突自定义资源名与Android原生资源名重复避免使用android:开头或系统常见的简单名称给资源名加前缀常见问题里资源名冲突算是比较高发的一类。Android资源索引是全局平铺的不同目录下的资源只要名字相同在代码中的引用R.xxx.name就会指向同一个id。如果你在一个项目里同时引入了多个第三方库它们各自又定义了相同名称的资源合并资源时就会产生冲突。解决方案就是给资源名统一加模块前缀比如library模块的资源名全部以lib_开头。这个规范看似麻烦但对依赖了多个SDK的项目来说是保命的。还有一个细节是关于values里的字符串格式化。如果你在strings.xml里写了含有占位符的字符串比如欢迎%1$s在代码里直接用getString然后传参是可以的但如果你在布局XML里直接引用这个字符串占位符会原样显示出来。解决方法是借助formattrue在XML中使用或者干脆在代码里拼接。这不是目录结构本身的问题但和strings.xml资源文件的管理强相关顺带提醒一下。关于目录结构的调试技巧我再分享一个我自己常用的方法在Project视图下单独给每个模块的src/main/java、src/main/res等目录加一个颜色标记File → File Colors。这样多模块工程里属于哪个模块的代码和资源一眼就能分辨找错文件和误改他人模块的概率明显下降。这个技巧虽然简单但用了都说好。最后再分享一点我个人的实操体会刚工作前两年我对目录结构的态度是IDE能生成的就不管直到有一次在真机上排查一个诡异的闪退问题某个机型加载图片总是内存溢出最后发现是因为mipmap里只放了超高密度图设备实际密度低时系统缩放导致内存翻了几倍。那之后我就养成了一个习惯——每次拿到新项目先把res目录、Manifest、Gradle配置通读一遍心里对资源组织方式有个完整地图再动手写代码。如果你现在正处在目录结构有什么好学的这个阶段我建议你找一个真实的开源项目打开Project视图不借助IDE帮助尝试回答这几个问题每个目录的作用是什么、哪些资源在什么条件下被加载、Manifest里的组件和代码如何对应。能流利回答出来你对Android工程的理解就已经超过大多数同龄开发者了。