问题背景
在 Android 17 之前的版本中,AIDL 编译生成的 Java 文件通常可以直接在 out/ 目录下找到:
bash
find out -name "IApplicationThread.java"
但在 Android 17 上,Soong 构建系统发生了变化。AIDL 生成的 .java 文件在编译完成后,往往会被打包进 jar 包 ,原始的独立文件被清理。这就导致用传统的 find 命令在 out/soong 下直接搜索 .java 文件时,经常找不到结果。
然而,如果你查看 .d 依赖文件或搜索 .class 文件,会发现编译确实发生了------只是产物被"藏"在了 jar 里。
解决方案
写了一个 extract_aidl_java.sh 脚本,放在项目根目录(out/ 的上一级),通过 AIDL 文件名自动提取对应的 Java 源码。
脚本源码
bash
#!/bin/bash
# =============================================================================
# extract_aidl_java.sh
# -----------------------------------------------------------------------------
# 功能: 从 Android 17+ 的 Soong 构建输出中提取 AIDL 生成的 Java 文件
# 位置: 放在 Android 项目根目录(out/ 的上一级)使用
# 用法: ./extract_aidl_java.sh <AIDL文件名>
# 示例: ./extract_aidl_java.sh IApplicationThread.aidl
# =============================================================================
set -e
# 参数检查
if [ $# -lt 1 ]; then
echo "用法: $0 <AIDL文件名>"
echo "示例: $0 IApplicationThread.aidl"
echo " $0 IActivityManager.aidl"
exit 1
fi
AIDL_NAME="$1"
# 去掉 .aidl 后缀,得到基础名
BASE_NAME="${AIDL_NAME%.aidl}"
if [ -z "$BASE_NAME" ]; then
echo "错误: 无效的 AIDL 文件名"
exit 1
fi
echo "=== 查找 AIDL 生成的 Java 文件: ${BASE_NAME}.java ==="
# 1. 先在 out/soong 中搜索独立的 .java 文件(编译后可能还存在)
JAVA_FILE=$(find out/soong -name "${BASE_NAME}.java" -type f 2>/dev/null | head -1)
if [ -n "$JAVA_FILE" ]; then
echo "找到独立文件: $JAVA_FILE"
OUTPUT_DIR="extracted_aidl/$(dirname "$JAVA_FILE" | sed 's|out/soong/||')"
mkdir -p "$OUTPUT_DIR"
cp "$JAVA_FILE" "$OUTPUT_DIR/"
echo "已复制到: $OUTPUT_DIR/${BASE_NAME}.java"
echo "=== 完成 ==="
exit 0
fi
# 2. 在 jar 中查找
echo "未找到独立文件,搜索 jar 包..."
# 找到包含该 .java 文件的 jar
JAR_FILE=$(find out/soong -name "*.jar" -exec sh -c '
jar tf "$1" 2>/dev/null | grep -q "/'${BASE_NAME}'.java$" && echo "$1"
' _ {} \; | head -1)
if [ -z "$JAR_FILE" ]; then
echo "错误: 未找到 ${BASE_NAME}.java"
echo "可能原因:"
echo " 1. 尚未编译对应模块(尝试执行: m framework 或 m <模块名>)"
echo " 2. AIDL 文件名拼写错误"
echo " 3. 该 AIDL 属于未编译的模块"
exit 1
fi
echo "找到 jar: $JAR_FILE"
# 3. 提取 jar 中的 Java 文件
MATCHED_FILES=$(jar tf "$JAR_FILE" 2>/dev/null | grep "/${BASE_NAME}\.java$" || true)
if [ -z "$MATCHED_FILES" ]; then
echo "错误: jar 中未找到 ${BASE_NAME}.java"
exit 1
fi
echo "jar 中包含以下匹配文件:"
echo "$MATCHED_FILES"
# 创建输出目录
OUTPUT_DIR="extracted_aidl"
mkdir -p "$OUTPUT_DIR"
# 解压文件
cd "$OUTPUT_DIR"
jar xf "../$JAR_FILE" $MATCHED_FILES
cd ..
# 显示提取结果
echo ""
echo "=== 提取完成 ==="
for file in $MATCHED_FILES; do
EXTRACTED_PATH="$OUTPUT_DIR/$file"
if [ -f "$EXTRACTED_PATH" ]; then
echo "文件: $EXTRACTED_PATH"
echo "大小: $(wc -c < "$EXTRACTED_PATH") 字节"
echo "--- 前 20 行预览 ---"
head -20 "$EXTRACTED_PATH"
echo "--- ... ---"
echo ""
fi
done
echo "所有文件已提取到: $OUTPUT_DIR/"
使用方法
bash
# 1. 保存到 Android 项目根目录,添加执行权限
chmod +x extract_aidl_java.sh
# 2. 执行(传入 AIDL 文件名,自动去掉 .aidl 后缀)
./extract_aidl_java.sh IApplicationThread.aidl
./extract_aidl_java.sh IActivityManager.aidl
./extract_aidl_java.sh IPackageManager.aidl
# 3. 结果输出到 extracted_aidl/ 目录,保留原始包路径
cat extracted_aidl/android/app/IApplicationThread.java
实际输出示例
bash
$ ./extract_aidl_java.sh IApplicationThread.aidl
=== 查找 AIDL 生成的 Java 文件: IApplicationThread.java ===
未找到独立文件,搜索 jar 包...
找到 jar: out/soong/.intermediates/frameworks/base/framework-minus-apex/android_common_combined/framework-minus-apex.jar
jar 中包含以下匹配文件:
android/app/IApplicationThread.java
android/app/IApplicationThreadExt.java
=== 提取完成 ===
文件: extracted_aidl/android/app/IApplicationThread.java
大小: 15234 字节
--- 前 20 行预览 ---
package android.app;
// ...
--- ... ---
所有文件已提取到: extracted_aidl/
设计思路
| 场景 | 处理方式 |
|---|---|
编译后 .java 仍作为独立文件存在 |
优先复制到 extracted_aidl/ |
.java 已被打包进 jar |
遍历 out/soong 下所有 jar,用 jar tf 匹配,再解压 |
| 未找到任何文件 | 提示可能原因(未编译、拼写错误、模块未参与构建) |
注意事项
- 确保已编译对应模块 :如果 jar 里也没有,先执行
m framework或m <模块名>编译 - 脚本位置 :必须放在项目根目录(即
out/的上一级),因为使用了相对路径out/soong - 输出目录 :结果放在
extracted_aidl/下,建议加入.gitignore
为什么不用 grep -r 直接搜?
grep -r 在二进制 jar 文件上无效,且 out/soong 下 jar 数量庞大,直接解压所有 jar 效率极低。本脚本通过 jar tf 快速列出 jar 内容,只匹配目标文件,再精准解压,兼顾速度和准确性。
结语
Android 构建系统在不断演进,Soong 替代 Make 后,很多中间产物的存放方式都发生了变化。希望这个小脚本能帮大家节省一些查找生成源码的时间。
如有问题或改进建议,欢迎交流。