【Android之路】安卓资源与编译初步
在制作安卓客户端的时候,,如果我们仔细观察,会看到这样的文件目录:
咱们今天着重于介绍assest
和res
目录
二者的区别
assest
目录中常用于存放软件是视频和音频等文件,这些文件通常是不需要做适配的,而res
目录中则是存放需要进行设备适配的文件,就比如图片、文字等。在进行资源编译的时候,aeerst
目录是不会被AAPT编译的,而res
则是会被编译的。
什么是AAPT?
AAPT 全称是 Android Asset Packaging Tool,是 Android SDK 自带的一个底层命令行工具,用来在构建 APK 时处理资源文件。
你在用 Android Studio 编译 APK / AAB 时,它会在后台自动调用 AAPT。
🌟 主要功能
-
打包资源(Packaging Resources)
- 把
res/
目录中的图片、布局 XML、字符串、主题等资源编译成二进制格式,输出到resources.arsc
。 - 同时会把资源 ID 写入
R.java
或R.class
,让代码可以通过R.string.xxx
、R.layout.xxx
等访问。
- 把
-
生成
R.java
- 每次你添加/修改资源文件,AAPT 会自动扫描并分配唯一的整型 ID,生成
R.java
,供应用代码引用。
- 每次你添加/修改资源文件,AAPT 会自动扫描并分配唯一的整型 ID,生成
-
压缩与合并资源
- 会对 PNG、XML 等资源进行优化压缩,减少 APK 体积。
-
校验与构建 APK
- 在打包过程中检查资源是否有效(如 XML 格式、图片路径),并最终打包成
.apk
文件。
- 在打包过程中检查资源是否有效(如 XML 格式、图片路径),并最终打包成
🛠️ 常用命令(AAPT1)
如果你在命令行中运行:
aapt dump badging app-debug.apk
可以查看 APK 的应用包名、版本号、支持的屏幕分辨率、权限等信息。
例如输出:
package: name='com.example.myapp' versionCode='1' versionName='1.0'
sdkVersion:'21'
targetSdkVersion:'33'
uses-permission: name='android.permission.INTERNET'
application-label:'MyApp'
其他常用命令:
aapt list app.apk
查看 APK 内部所有文件。aapt dump permissions app.apk
查看声明的权限。
⚡ AAPT1 与 AAPT2 的区别
-
AAPT1:旧版打包工具,Android Gradle Plugin 3.0 以前默认使用。
-
AAPT2(现在默认):对资源编译做了增量优化,提高速度和错误提示质量。
- 支持并行编译资源。
- 错误提示更直观。
📌 总结
AAPT 是 Android 构建过程中专门处理资源的工具。
开发者一般不用手动调用,但在分析 APK、反编译、或 CI/CD 自动化中常用aapt dump badging
来查看包信息。
如果你在分析开源 Android 项目时,看到日志里出现:
AAPT: error: resource xxx not found
就是资源打包或引用出错时 AAPT 报告的错误。
res
是怎么进行资源适配的?
在 Android 里,res/
目录就是资源管理系统的入口。
所谓“资源适配”,就是让系统在不同设备特征(屏幕密度、分辨率、语言、暗色模式、API 等)下自动选择最合适的资源文件。
这一切都是由 资源限定符(Resource Qualifiers) 和 AAPT + 运行时资源选择机制共同完成的。
1️⃣ res/
目录的基本结构
res/├─ drawable/ # 默认的图片、shape、selector├─ drawable-hdpi/ # 针对 hdpi 屏幕的图片├─ layout/ # 默认布局├─ layout-land/ # 横屏布局├─ mipmap-xxxhdpi/ # 启动图标├─ values/ # 默认的 colors、strings、styles├─ values-zh/ # 中文(简体)资源├─ values-night/ # 夜间模式资源└─ raw/ # 原始文件
每个子目录可以带限定符(Qualifier),用来声明这个目录的资源针对什么情况使用。
2️⃣ 常用资源限定符
类别 | 形式示例 | 含义 |
---|---|---|
语言/地区 | values-zh/ 、values-zh-rCN/ | 中文,或中文(中国) |
屏幕方向 | layout-land/ 、layout-port/ | 横屏 / 竖屏 |
屏幕尺寸 | layout-sw600dp/ | 最小宽度 600dp(平板常用) |
屏幕密度 | drawable-mdpi/ 、drawable-hdpi/ 、drawable-xhdpi/ 、drawable-xxhdpi/ | 对应 1x、1.5x、2x、3x 像素密度 |
UI 模式 | values-night/ | 夜间模式(暗色主题) |
平台版本 | values-v21/ 、drawable-v24/ | Android 5.0 (API 21) 及以上才用 |
触控方式 | drawable-notouch/ | 没有触摸屏的设备 |
布局方向 | layout-ldrtl/ | 支持从右到左布局(阿语等) |
系统在运行时会根据设备实际配置(Configuration)自动选择最匹配的目录。
📌 示例:不同屏幕密度的图片适配
假设我们放置以下资源:
res/drawable-mdpi/logo.png (48x48)drawable-hdpi/logo.png (72x72)drawable-xhdpi/logo.png (96x96)drawable-xxhdpi/logo.png (144x144)
- mdpi 设备(160dpi)→ 用
mdpi/logo.png
- xhdpi 设备(320dpi)→ 用
xhdpi/logo.png
如果缺少某一密度的资源,系统会自动缩放最接近的那个(有时会导致模糊)。
📌 示例:不同语言适配
res/values/strings.xmlvalues-zh/strings.xmlvalues-en/strings.xml
如果设备系统语言是中文,加载 values-zh/strings.xml
;
是英文就加载 values-en/strings.xml
;
找不到就回退到默认 values/
。
📌 示例:夜间模式适配
res/values/colors.xml # 白天模式颜色values-night/colors.xml # 夜间模式颜色
当用户切换到夜间模式 (UiModeManager
或主题切换),系统自动用 -night
资源。
3️⃣ 资源选择顺序(匹配规则)
Android 在运行时根据当前 Configuration 按以下优先级选择:
- 完全匹配所有限定符的资源
- 匹配度高的目录优先
- 如果没有找到完全匹配的,就回退到默认资源目录
例如当前设备:
zh-CN
、横屏、xhdpi
系统会按:layout-zh-rCN-land-xhdpi
→layout-zh-rCN-land
→layout-zh-land
→layout-land
→layout/
顺序去找。
4️⃣ 适配实践建议
- 图片:放在
mipmap-xxxhdpi
或drawable-xxxhdpi
,至少提供xxhdpi
,系统可缩放。 - 布局:针对平板用
layout-sw600dp
,横竖屏用layout-land/port
。 - 语言:在
values-xx
下放多语言strings.xml
。 - 夜间模式:用
-night
。 - 版本差异:在
values-v21/
提供 Material 主题资源,旧版本用默认。
⚡ 总结
res 适配的核心是“限定符 + 系统自动匹配”。
你只要按规则命名不同目录,Android 在运行时会根据设备特性自动加载最合适的资源。
对于图片、布局、字符串、颜色、样式等都通用,几乎不需要手动写逻辑判断。
资源的访问
在 Android 里,“访问资源”有两种主要途径:
- 通过代码 (Java/Kotlin)
- 通过 XML(布局文件、样式文件等)
1️⃣ 资源在构建时的组织
- AAPT/AAPT2 会扫描
res/
下的所有资源文件,并为它们生成一个全局唯一的 ID,写进自动生成的R
类(R.java
或R.class
)。 - 你在代码中访问的
R.drawable.logo
、R.string.app_name
等,其实就是一个int
常量,指向打包后的资源表中的索引。
例如:
public final class R {public static final class drawable {public static final int logo = 0x7f080057;}public static final class string {public static final int app_name = 0x7f100004;}
}
2️⃣ 在 代码 中访问资源
几乎所有资源都通过 Context
或其子类的方法访问(Activity
、Application
、View
都是 Context
)。
资源类型 | 示例访问方式 |
---|---|
字符串 | getString(R.string.app_name) |
带占位符的字符串 | getString(R.string.welcome, userName) |
颜色 | ContextCompat.getColor(context, R.color.primary) |
Drawable | ContextCompat.getDrawable(context, R.drawable.ic_launcher) |
尺寸 (dp/sp) | getResources().getDimension(R.dimen.padding_large) |
数组 | getResources().getStringArray(R.array.planets) |
原始文件 (raw) | InputStream is = getResources().openRawResource(R.raw.music); |
Assets (非res目录) | AssetManager am = getAssets(); InputStream is = am.open("file.txt"); |
⚠️ 注意:
assets/
目录下的内容不会生成R
ID,需要用AssetManager
访问。
示例:加载图片并显示
val imageView: ImageView = findViewById(R.id.myImage)
imageView.setImageResource(R.drawable.logo)
示例:加载颜色并设置背景
val color = ContextCompat.getColor(this, R.color.primary)
myLayout.setBackgroundColor(color)
示例:加载字符串并格式化
val welcome = getString(R.string.welcome_user, "张博")
textView.text = welcome
3️⃣ 在 XML 中访问资源
在布局或样式文件里,你通常通过 @
前缀访问资源:
用法 | 示例 |
---|---|
布局属性 | android:src="@drawable/logo" |
字符串 | android:text="@string/app_name" |
颜色 | android:background="@color/primary" |
尺寸 | android:padding="@dimen/padding_large" |
引用另一个资源 | @style/AppTheme 、@array/planets |
系统内置资源 | @android:color/black 、@android:style/Theme.Material |
⚡ @
和 ?
的区别
@
:直接引用一个资源 ID
例:android:text="@string/app_name"
?
:引用当前主题中定义的属性
例:android:colorBackground="?attr/colorPrimary"
这使得主题可以动态换肤。
4️⃣ 特殊资源访问方式
-
TypedArray / obtainStyledAttributes
用于读取自定义属性(自定义控件常用)。 -
Resources.Theme
可以根据当前主题解析?attr/
属性。 -
getIdentifier(name, defType, packageName)
通过字符串动态获取资源 ID(不推荐频繁使用,因为性能较低):val resId = resources.getIdentifier("logo", "drawable", packageName) imageView.setImageResource(resId)
5️⃣ 资源访问的运行时特性
- 系统在访问资源时会根据当前设备配置(语言、夜间模式、屏幕密度等)选择最合适的版本。
- 你在代码中只写
R.drawable.logo
,运行时会自动加载合适的drawable-hdpi/logo.png
或drawable-xxhdpi/logo.png
。 - 当配置变化(如横竖屏切换、语言切换、夜间模式)时,
Activity
会重新创建并加载对应资源。
🚀 总结
资源访问的核心:
- 构建时: AAPT 为所有资源生成 ID 并写入
R
类。- 运行时:
Context
根据设备配置选择最匹配的资源文件。- 代码访问: 用
getResources()
、getString()
、ContextCompat
等。- XML 访问: 用
@
、?attr/
。