Skip to content

Commit ecfcd36

Browse files
committed
新增支持导出 apks 格式的安装包
1 parent 32d9e90 commit ecfcd36

4 files changed

Lines changed: 110 additions & 31 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160

161161
* 获取栈顶 Activity 内容
162162

163-
* 导出应用 apk
163+
* 导出应用安装包
164164

165165
* 导出 ANR 日志
166166

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ if [[ ! -d "${shellDirPath}" ]]; then
66
exit 1
77
fi
88

9-
shellFilePath="${shellDirPath}/device-tools/ExportApkFile.sh"
9+
shellFilePath="${shellDirPath}/device-tools/ExportInstallPackageFile.sh"
1010

1111
if [[ ! -f "${shellFilePath}" ]]; then
1212
echo "❌ 没找到 shell 文件,请检查 ${shellFilePath} 路径是否正确"

shell/device-tools/ExportApkFile.sh renamed to shell/device-tools/ExportInstallPackageFile.sh

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
# author : Android 轮子哥
44
# github : https://github.com/getActivity/AndroidCmdTools
55
# time : 2026/01/25
6-
# desc : 应用 APK 导出脚本(从设备导出已安装应用)
6+
# desc : 应用安装包导出脚本
77
# ----------------------------------------------------------------------
88
scriptDirPath=$(dirname "${BASH_SOURCE[0]}")
99
originalDirPath=$PWD
1010
cd "${scriptDirPath}" || exit 1
1111
source "../common/SystemPlatform.sh" && \
1212
source "../common/EnvironmentTools.sh" && \
1313
source "../business/DevicesSelector.sh" && \
14-
source "../common/FileTools.sh" || exit 1
14+
source "../common/FileTools.sh" && \
15+
source "../business/ResourceManager.sh" || exit 1
1516
cd "${originalDirPath}" || exit 1
1617
unset scriptDirPath
1718
unset originalDirPath
@@ -33,7 +34,7 @@ waitUserInputParameter() {
3334
exit 1
3435
fi
3536
fi
36-
echo "请输入 apk 导出目录(可空,默认当前目录):"
37+
echo "请输入安装包导出目录(可空,默认当前目录):"
3738
read -r exportDirPath
3839
exportDirPath=$(parseComputerFilePath "${exportDirPath}")
3940

@@ -65,47 +66,125 @@ exportSingleApk() {
6566
echo "${apkPathResult}"
6667
return 1
6768
fi
68-
local apkSourceFilePath=${apkPathResult//package:/}
6969
local versionName
7070
versionName=$(adb -s "${deviceId}" shell dumpsys package "${packageName}" < /dev/null 2>&1 | tr -d '\r' | sed -n 's/.*versionName=\([^ ]*\).*/\1/p' | head -n 1)
71-
local tempApkFilePath
72-
tempApkFilePath="${exportDirPath}$(getFileSeparator)${packageName}-${versionName}.apk"
73-
local outputPrint
74-
outputPrint=$(MSYS_NO_PATHCONV=1 adb -s "${deviceId}" pull "${apkSourceFilePath}" "${tempApkFilePath}" < /dev/null 2>&1)
75-
local exitCode=$?
76-
if (( exitCode != 0 )); then
77-
echo "❌ [${deviceId}] 设备导出 [${packageName}] 失败,原因如下:"
78-
echo "${outputPrint}"
71+
local apkPaths=()
72+
while IFS= read -r line; do
73+
local p=${line#package:}
74+
[[ -n "${p}" ]] && apkPaths+=("${p}")
75+
done < <(echo "${apkPathResult}" | tr -d '\r' | grep '^package:')
76+
if (( ${#apkPaths[@]} == 0 )); then
77+
echo "❌ [${deviceId}] 未解析到 apk 路径"
7978
return 1
8079
fi
81-
if [[ -f ${tempApkFilePath} ]]; then
82-
local appLabel=""
80+
if (( ${#apkPaths[@]} == 1 )); then
81+
local apkSourceFilePath="${apkPaths[0]}"
82+
local tempApkFilePath
83+
tempApkFilePath="${exportDirPath}$(getFileSeparator)${packageName}-${versionName}.apk"
84+
local outputPrint
85+
outputPrint=$(MSYS_NO_PATHCONV=1 adb -s "${deviceId}" pull "${apkSourceFilePath}" "${tempApkFilePath}" < /dev/null 2>&1)
86+
local exitCode=$?
87+
if (( exitCode != 0 )); then
88+
echo "❌ [${deviceId}] 设备导出 [${packageName}] 失败,原因如下:"
89+
echo "${outputPrint}"
90+
return 1
91+
fi
92+
if [[ -f ${tempApkFilePath} ]]; then
93+
local appLabel=""
94+
if existCommand "aapt"; then
95+
local localeKeys=("application-label-zh-CN" "application-label-zh" "application-label-zh-Hans" "application-label-zh-Hant" "application-label-zh-rCN" "application-label" "application-label-en")
96+
for key in "${localeKeys[@]}"; do
97+
local label
98+
label=$(aapt dump badging "${tempApkFilePath}" < /dev/null 2>/dev/null | sed -n "s/.*${key}:'\\([^']*\\)'.*/\\1/p" | head -n 1)
99+
if [[ -n "${label}" ]]; then
100+
appLabel="${label}"
101+
break
102+
fi
103+
done
104+
fi
105+
if [[ -z "${appLabel}" ]]; then
106+
appLabel="${packageName}"
107+
fi
108+
local safeLabel
109+
safeLabel=$(echo "${appLabel}" | tr -d '\r' | sed 's/[\\/:*?"<>|]/_/g' | sed 's/[[:space:]]\{1,\}/ /g' | sed 's/^ *//;s/ *$//')
110+
if [[ -n "${versionName}" ]]; then
111+
apkTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel} ${versionName}.apk"
112+
else
113+
apkTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel}.apk"
114+
fi
115+
mv -f "${tempApkFilePath}" "${apkTargetFilePath}"
116+
echo "✅ [${deviceId}] 设备导出 [${safeLabel}] 成功,保存至:${apkTargetFilePath}"
117+
return 0
118+
else
119+
echo "❌ [${deviceId}] 设备已拉取 [${packageName}],但保存到电脑失败:${tempApkFilePath}"
120+
return 1
121+
fi
122+
fi
123+
124+
local tempRootDirPath
125+
tempRootDirPath="${exportDirPath}$(getFileSeparator)${packageName} ${versionName}"
126+
local splitsDirPath
127+
splitsDirPath="${tempRootDirPath}$(getFileSeparator)splits"
128+
mkdir -p "${splitsDirPath}"
129+
for remotePath in "${apkPaths[@]}"; do
130+
local fileName
131+
fileName=$(basename "${remotePath}")
132+
local normFileName="${fileName}"
133+
if [[ "${fileName}" == "base.apk" ]]; then
134+
normFileName="base-master.apk"
135+
elif [[ "${fileName}" =~ ^split_config\.([^.]+(\.[^.]+)*)\.apk$ ]]; then
136+
local suffix="${BASH_REMATCH[1]}"
137+
normFileName="base-${suffix}.apk"
138+
elif [[ "${fileName}" =~ ^config\.([^.]+(\.[^.]+)*)\.apk$ ]]; then
139+
local suffix2="${BASH_REMATCH[1]}"
140+
normFileName="base-${suffix2}.apk"
141+
fi
142+
local localPath
143+
localPath="${splitsDirPath}$(getFileSeparator)${normFileName}"
144+
local outputPrint
145+
outputPrint=$(MSYS_NO_PATHCONV=1 adb -s "${deviceId}" pull "${remotePath}" "${localPath}" < /dev/null 2>&1)
146+
local exitCode
147+
exitCode=$?
148+
if (( exitCode != 0 )) || [[ ! -f "${localPath}" ]]; then
149+
echo "❌ [${deviceId}] 拉取拆分 apk 失败:${remotePath}"
150+
echo "${outputPrint}"
151+
return 1
152+
fi
153+
done
154+
local baseLocalApk
155+
baseLocalApk=$(ls -1 "${splitsDirPath}" | grep -E '^base-master\.apk$|^base\.apk$' | head -n 1)
156+
local appLabel=""
157+
if [[ -n "${baseLocalApk}" ]]; then
83158
if existCommand "aapt"; then
84159
local localeKeys=("application-label-zh-CN" "application-label-zh" "application-label-zh-Hans" "application-label-zh-Hant" "application-label-zh-rCN" "application-label" "application-label-en")
85160
for key in "${localeKeys[@]}"; do
86161
local label
87-
label=$(aapt dump badging "${tempApkFilePath}" < /dev/null 2>/dev/null | sed -n "s/.*${key}:'\\([^']*\\)'.*/\\1/p" | head -n 1)
162+
label=$(aapt dump badging "${splitsDirPath}$(getFileSeparator)${baseLocalApk}" < /dev/null 2>/dev/null | sed -n "s/.*${key}:'\\([^']*\\)'.*/\\1/p" | head -n 1)
88163
if [[ -n "${label}" ]]; then
89164
appLabel="${label}"
90165
break
91166
fi
92167
done
93168
fi
94-
if [[ -z "${appLabel}" ]]; then
95-
appLabel="${packageName}"
96-
fi
97-
local safeLabel
98-
safeLabel=$(echo "${appLabel}" | tr -d '\r' | sed 's/[\\/:*?"<>|]/_/g' | sed 's/[[:space:]]\{1,\}/ /g' | sed 's/^ *//;s/ *$//')
99-
if [[ -n "${versionName}" ]]; then
100-
apkTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel} ${versionName}.apk"
101-
else
102-
apkTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel}.apk"
103-
fi
104-
mv -f "${tempApkFilePath}" "${apkTargetFilePath}"
105-
echo "✅ [${deviceId}] 设备导出 [${safeLabel}] 成功,保存至:${apkTargetFilePath}"
169+
fi
170+
if [[ -z "${appLabel}" ]]; then
171+
appLabel="${packageName}"
172+
fi
173+
local safeLabel
174+
safeLabel=$(echo "${appLabel}" | tr -d '\r' | sed 's/[\\/:*?"<>|]/_/g' | sed 's/[[:space:]]\{1,\}/ /g' | sed 's/^ *//;s/ *$//')
175+
local apksTargetFilePath
176+
if [[ -n "${versionName}" ]]; then
177+
apksTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel} ${versionName}.apks"
178+
else
179+
apksTargetFilePath="${exportDirPath}$(getFileSeparator)${safeLabel}.apks"
180+
fi
181+
(cd "${tempRootDirPath}" && zip -q -r "${apksTargetFilePath}" .)
182+
if [[ -f "${apksTargetFilePath}" ]]; then
183+
rm -rf "${tempRootDirPath}"
184+
echo "✅ [${deviceId}] 设备导出 [${safeLabel}] 成功,保存至:${apksTargetFilePath}"
106185
return 0
107186
else
108-
echo "❌ [${deviceId}] 设备已拉取 [${packageName}],但保存到电脑失败:${tempApkFilePath}"
187+
echo "❌ [${deviceId}] 设备已拉取 [${packageName}],但保存到电脑失败:${apksTargetFilePath}"
109188
return 1
110189
fi
111190
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ set "currentDirPath=%~dp0"
1414
pushd "!currentDirPath!\..\.."
1515
set "tempDirPath=%CD%"
1616
popd
17-
set "shellFilePath=!tempDirPath!\shell\device-tools\ExportApkFile.sh"
17+
set "shellFilePath=!tempDirPath!\shell\device-tools\ExportInstallPackageFile.sh"
1818

1919
if not exist "!shellFilePath!" (
2020
echo ❌ 没找到 shell 文件,请检查 !shellFilePath! 路径是否正确

0 commit comments

Comments
 (0)