项目结构解析
逐层剖析 Android 项目结构:app 模块、Manifest、res 目录、Gradle 脚本、签名配置与 ProGuard 规则
学习目标
- 理解 app 模块与库模块的差异,能在多模块项目中定位 app 级 build.gradle
- 逐行读懂 AndroidManifest.xml 中的 application、activity、intent-filter 与权限声明
- 区分 res/layout、values、drawable、mipmap 的职责与多限定符(如 values-night、mipmap-hdpi)
- 看懂项目级与模块级 build.gradle、settings.gradle、gradle.properties 的协作关系
- 了解签名配置、proguard-rules.pro 与 multiDex 的基本用途
学习目标
第 2 章我们用模板跑出了 Hello World,但模板“帮我们填了什么”还没说清。本章把项目结构拆开看:从根目录的 settings.gradle 一路到 proguard-rules.pro,把“配置即文档”读透。目标:
- 理解
app模块的本质(application 类型)与library模块(com.android.library)的差异; - 读懂
AndroidManifest.xml中application与activity节点及intent-filter的语义; - 能在
res/下正确定位并新增layout/values/drawable/mipmap资源,理解多限定符目录; - 看懂项目级与模块级
build.gradle、settings.gradle、gradle.properties三者协作; - 了解 release 构建时用到的签名配置、
proguard-rules.pro与 multidex 的基本用途。
app 模块结构
1. 模块类型
Android Studio 的“项目”由若干 模块(Module) 组成。常见的两类:
| 类型 | plugin | 产物 | 用途 |
|---|---|---|---|
| Application | com.android.application |
APK / AAB | 可独立安装的应用 |
| Library | com.android.library |
AAR | 被其他模块依赖的代码库 |
本教程的单模块项目只有 app 一个 application 模块。多模块项目通常会拆出 core、feature-home、feature-detail 等多个 library 模块,由 settings.gradle 聚合:
// settings.gradle
include ':app', ':core', ':feature-home', ':feature-detail'
app 模块依赖其它 library 模块:
// app/build.gradle
dependencies {
implementation project(':core')
implementation project(':feature-home')
}
2. app 模块目录
app/
├── build.gradle # 模块级构建脚本
├── proguard-rules.pro # 混淆规则
├── consumer-rules.pro # AAR 消费者规则(仅 library 有)
└── src/
├── main/
│ ├── AndroidManifest.xml
│ ├── java/com/example/helloapp/
│ │ └── MainActivity.java
│ └── res/...
├── androidTest/java/... # 设备端集成测试
└── test/java/... # JVM 单元测试
src/main/ 是发版的源集,src/debug/ 与 src/release/ 可分别存放不同构建变体的代码与资源,AGP 会自动合并。
3. 命名空间与 applicationId 的区别
namespace(build.gradle中的android { namespace '...' }):决定生成R类、BuildConfig类的包名,是源码层的包结构。applicationId(defaultConfig中):决定 APK 在系统中的唯一标识,等同 AndroidManifest 旧版的package属性。
模板中两者常常相同,但生产环境可不同:例如发布免费版 com.example.app.free、付费版 com.example.app.pro,但 namespace 都用 com.example.app。
AndroidManifest.xml
1. 完整模板
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 应用级硬件要求 -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloApp"
android:name=".App"> <!-- 继承 Application 的入口类 -->
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="standard"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:exported="false"
android:parentActivityName=".MainActivity" />
</application>
</manifest>
2. 节点说明
| 节点 | 作用 |
|---|---|
<uses-permission> |
申请系统权限,运行时危险权限还需在代码中请求 |
<uses-feature> |
声明硬件特性,影响 Google Play 可见性 |
<application> |
全局配置,name 指定自定义 Application 子类 |
<activity> |
注册 Activity;exported=true 才能被其它应用启动 |
<intent-filter> |
声明该 Activity 响应的 Intent,MAIN+LAUNCHER 组合即“桌面图标入口” |
3. 自定义 Application 类
模板默认不生成 Application 子类,全局初始化需要时手动添加:
package com.example.helloapp;
import android.app.Application;
import android.util.Log;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.d("HelloApp", "App onCreate");
}
}
然后在 manifest 中 application 节点加 android:name=".App"。Application#onCreate 在主进程启动时被调用,且 只在进程创建时一次,适合放全局 SDK 初始化、日志库初始化等。
不要在
Application#onCreate同步做重活(如读取大文件、网络请求),会导致冷启动变慢。复杂初始化用App Startup库做按需懒加载。
4. 自定义权限与 exported 行为
Android 12(API 31)起,所有带 intent-filter 的组件必须显式声明 android:exported,否则安装失败。仅本 App 内调用的 Activity 设 exported="false";要被其它 App 通过 Intent 唤起的 Activity 设 true 并加权限保护:
<activity
android:name=".ShareActivity"
android:exported="true"
android:permission="com.example.helloapp.permission.SHARE">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
对应自定义权限要在 manifest 中声明:
<permission
android:name="com.example.helloapp.permission.SHARE"
android:protectionLevel="signature" />
res 目录详解
1. 资源分类总览
res/
├── drawable/ # 矢量图、PNG、shape XML
├── layout/ # 布局 XML
├── values/ # 字符串、颜色、尺寸、样式
│ ├── strings.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── themes.xml
│ └── styles.xml
├── mipmap-anydpi-v26/ # API 26+ 自适应图标
├── mipmap-hdpi/ # 屏幕密度限定符
├── mipmap-mdpi/
├── mipmap-xhdpi/
├── mipmap-xxhdpi/
├── mipmap-xxxhdpi/
├── xml/ # 备份规则、路径配置等
├── font/ # 字体
└── raw/ # 原始资源(mp3、json 等)
AGP 在编译时把所有资源编译进 APK 的 resources.arsc,并为每个资源生成唯一 ID,由 R 类暴露:
int layoutId = R.layout.activity_main; // 0x7f030000
int stringId = R.string.app_name; // 0x7f0e0000
int colorId = R.color.purple_500; // 0x7f050000
2. layout 资源
res/layout/activity_main.xml 是布局,运行时通过 setContentView(R.layout.activity_main) 加载。AGP 会解析 XML 并实例化 View 树。同一布局可按屏幕尺寸限定符拆分:
res/
├── layout/main.xml # 默认
├── layout-land/main.xml # 横屏
├── layout-w600dp/main.xml # 平板宽度 ≥ 600dp
└── layout-v29/main.xml # API 29+ 走这个版本
匹配规则:限定符越多越优先,且按“版本 > 屏幕尺寸 > 屏幕方向”等顺序逐层筛选。
3. values 资源
strings.xml:
<resources>
<string name="app_name">HelloApp</string>
<string name="greeting">你好,%1$s!</string>
</resources>
代码中带格式化参数的用法:
String text = getString(R.string.greeting, "Android");
colors.xml:
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
</resources>
dimens.xml:
<resources>
<dimen name="margin_normal">16dp</dimen>
</resources>
themes.xml 与 styles.xml:
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.HelloApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_200</item>
<item name="colorOnPrimary">@color/white</item>
</style>
</resources>
4. 多限定符示例:夜间模式与多语言
夜间模式只需新建 values-night/:
res/
├── values/colors.xml
├── values-night/colors.xml # 系统切到深色模式时被选中
├── values/strings.xml
├── values-en/strings.xml # 系统语言为英语时
└── values-zh-rTW/strings.xml # 繁体中文(台湾)
values-night/colors.xml:
<resources>
<color name="background">#FF000000</color>
</resources>
values/colors.xml:
<resources>
<color name="background">#FFFFFFFF</color>
</resources>
引用方式不变:@color/background,框架会根据当前 Configuration 选对应目录。
5. drawable 与 mipmap 区别
drawable/:放普通图片、矢量图、shape XML、selector;mipmap/:仅用于启动器图标,系统可在不同密度 Launcher 中选择最优资源,不参与运行时 density 限定符匹配。
启动器图标自 API 26 起推荐自适应图标(adaptive icon),由前景、背景两层组成:
<!-- res/mipmap-anydpi-v26/ic_launcher.xml -->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
build.gradle(app 级)
完整模板(注释版):
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.helloapp'
compileSdk 34 // 编译用 SDK 版本
buildToolsVersion "34.0.0" // 可省,默认取最新
defaultConfig {
applicationId "com.example.helloapp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
// 默认 debug,可覆盖 applicationId 后缀
applicationIdSuffix ".debug"
debuggable true
minifyEnabled false
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
flavorDimensions += "channel"
productFlavors {
google { dimension "channel" }
huawei { dimension "channel" }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
buildFeatures {
viewBinding true // 启用 ViewBinding
buildConfig true // 启用 BuildConfig 类生成
}
packagingOptions {
resources {
excludes += ['META-INF/*.kotlin_module']
}
}
}
dependencies {
// API:依赖会传递给上游(库被其它模块用时)
// implementation:依赖不传递,仅当前模块可见(推荐)
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
关键点:
compileSdk 34决定编译器能引用哪些 API;targetSdk 34决定系统是否对老 API 行为做兼容;minSdk 24决定能装在哪些设备上;Build Variants=productFlavors × buildTypes,本例生成googleDebug、googleRelease、huaweiDebug、huaweiRelease四个变体。
settings.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "HelloApp"
include ':app'
pluginManagement决定 Gradle 从哪下插件;dependencyResolutionManagement决定依赖仓库,FAIL_ON_PROJECT_REPOS表示禁止模块级repositories {},统一收敛;include列出所有模块。
gradle.properties
# JVM 内存
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# 并行构建
org.gradle.parallel=true
# 缓存
org.gradle.caching=true
# 配置按需
org.gradle.configureondemand=false
# AndroidX
android.useAndroidX=true
android.nonTransitiveRClass=true
# Kotlin
kotlin.code.style=official
android.useAndroidX=true强制用 AndroidX,否则会出现 support 库冲突;android.nonTransitiveRClass=true自 AGP 8 起默认,每个模块只引用自己的R,减少资源冲突。
签名配置与 proguard-rules.pro
1. 签名配置
android {
signingConfigs {
release {
// 从 ~/.gradle/gradle.properties 读取,避免硬编码
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
enableV1Signing true
enableV2Signing true
enableV3Signing false
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
gradle.properties中以明文保存密码仍不安全,生产环境推荐sdkmanager的signingReport任务、或把密码放 CI 加密变量。V2/V3签名是 APK Signature Scheme v2/v3,比V1(JAR 签名)更安全且更快。
2. proguard-rules.pro
开启 minifyEnabled true 后,AGP 调用 R8/ProGuard 对字节码做混淆、压缩、优化。默认规则来自 getDefaultProguardFile('proguard-android-optimize.txt'),自身规则写在模块 proguard-rules.pro,常见保留:
# 保留反射调用的类
-keep class com.example.helloapp.model.** { *; }
# 保留 Gson 序列化的字段
-keepclassmembers class com.example.helloapp.data.** {
<fields>;
}
# 保留 enum 的 name() 与 valueOf()
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留 Native 方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留 Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
3. multidex 概览
minSdk 21+ 自动开启 multidex,无需配置。如果 minSdk < 21,需要:
android {
defaultConfig {
multiDexEnabled true
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
并在 manifest 加 android:name="androidx.multidex.MultiDexApplication" 或在自定义 Application 中调用 MultiDex.install(this)。原因是单 dex 的方法数上限是 65535,超了就要拆。
常见坑与最佳实践
compileSdk与targetSdk混淆:compileSdk是编译期可用的 API 集合;targetSdk是运行期兼容开关,targetSdk=34时系统会按 Android 14 行为约束你(如前台服务类型限制)。implementationvsapi:api会让依赖传递到上游,可能引发依赖冲突与构建变慢;默认用implementation,只有当 SDK 需要对外暴露类型时才用api。- 资源名重复导致合并失败:不同模块都定义
R.string.app_name时会冲突,给资源加模块前缀(如home_app_name)能避免。 - Manifest 节点合并顺序:主 manifest 在
src/main/,library 模块或src/debug/的同名节点会合并;冲突时用tools:replace="android:label"显式覆盖。 proguard-rules.pro漏 keep 导致 release 崩:反射、@Parcelize、Gson POJO、JNI 都要 keep,否则类被改名导致ClassNotFoundException。可用@Keep注解替代手写规则。gradle.properties改了不生效:必须File → Sync Project with Gradle Files才能生效;某些项需重启 Android Studio。buildToolsVersion写死成旧版:AGP 8 不强制要求,写错反而报“version too low”。删掉这行让 AGP 自选。- 多渠道打包 applicationId 冲突:用
applicationIdSuffix ".debug"区分变体,方便同一设备同时装 debug 和 release。
章节小结
app模块是com.android.application,产物 APK;library模块产物 AAR;AndroidManifest.xml是“应用契约”,组件、权限、入口都靠它声明;自 Android 12 起exported必须显式;res/分layout/values/drawable/mipmap等,多限定符(-land、-night、-v29、-w600dp)让一套代码适配多端;build.gradle(模块级)控制 SDK 版本、签名、混淆、构建变体;settings.gradle收敛模块与仓库;gradle.properties控 Gradle 与 AndroidX 全局开关;- release 构建需要
.jks+ signingConfigs +proguard-rules.pro,minSdk < 21 时还要 multidex。
下一章预告
第 4 章我们将进入 Java 速成:从变量、运算符、流程控制,到类与对象、继承多态、接口、内部类、泛型、Lambda、集合、异常与 IO,并结合 Android 开发场景说明 Java 在 Android 中的特殊用法,为零基础同学铺好通往后续章节的路。