概述

  在前一段时间的工作中,我得到了一个新的任务,写一个命令行工具,修改apk中的某个文件,再进行重新打包。这个过程实际上十分简单:首先,apk文件就是一个zip文件,但是在安装的时候安卓系统会校验签名,因此我们需要对重新打包的apk进行签名。下面是实现这个工具的重难点和具体思路:

命令行数组

  如果想要生成一批apk,他们中需要修改的位置都是相同的,只有写入的文本不同。那么,第一时间我们会想到字符串数组。但是在命令行中是没有数组的,有的文章采用本地文件逐行读取的方法,但我们也可以使用下面的方法来对数组进行模拟:

参考资料:https://www.yiibai.com/batch_script/batch_script_arrays.html

1
2
3
4
5
6
7
@echo off
set a[0]=1
set a[1]=2
set a[2]=3
echo The first element of the array is %a[0]%
echo The second element of the array is %a[1]%
echo The third element of the array is %a[2]%

批处理压缩与解压缩

  在windows环境中,我们不能方便地像隔壁那样,直接调用zip和unzip命令进行压缩和解压,需要下载相应的程序来帮助我们完成这一过程。下载地址:http://stahlworks.com/dev/index.php?tool=zipunzip

  下载完两个exe文件之后,我们将它放在与脚本相同目录下或者是C:/Windows目录下,推荐后者,然后就可以使用压缩与解压缩命令了。

1
zip -r fileName.apk ./*

  这里的命令是将当前目录压缩为指定名称的apk文件,其中参数-r为递归压缩子目录下所有文件。不加的话只会得到一个空的文件夹。这里需要注意的是,在打包apk的时候,我们需要cd到解压好的文件目录下进行压缩,否则会出现压缩好之后的apk因为打开后是一个文件夹,不是标准apk目录结构的情况而无法进行安装。

1
unzip -o -d /path fileName.apk

  这里的命令是将指定apk解压到指定的目录中,-o是不提示覆盖,-d是指定路径。

关于apk签名

  我们可以通过下面的方法对apk的签名进行查看:解压出位于META-INF位置下的CERT.RSA文件,使用命令查看:

1
keytool -printcert -file [file path]

  想要对apk重新签名,需要删除apk中原先的签名文件。有博客说删掉两个文件就行,但是会出现错误。jarsigner: java.lang.SecurityException:SHA1 digest error for META-INF/CERT.RSA根据stack overflow上某个老哥给的答案:https://stackoverflow.com/questions/37513084/jarsigner-java-lang-securityexceptionsha1-digest-error-for-meta-inf-cert-rsa-a我们需要删除整个META-INF文件进行

  apk的重新签名需要项目的证书文件,后缀名为:.keystores,命令为:

1
jarsigner -verbose -keystore [keystore path] -signedjar [out apk name].apk [current apk name].apk [alias name] -storepass [password]

  这里需要填写的参数分别是证书文件的路径、签名后apk名及路径、签名前apk名及路径,别名,密码。

  签名完毕后,还可以对生成的apk进行验证。

1
jarsigner -verify [out apk name].apk

  至此,整个工具的重点就介绍完毕了。

工具

  在很多文章中,有人会推荐使用apktool的工具进行上面的一些工作,这里非常不推荐。apktool是一个java写的工具,在解包和打包过程结束后,会自动关闭当前的命令行。解包打包签名,可能就需要有三个脚本了,给工具的制作造成了很大的不便。

  下面是工具的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@echo off
setlocal enabledelayedexpansion
rem 拖入apk并获取其路径和文件名
echo 请拖入需要处理的apk
set /p input=
set apkUrl=
for /f "delims=" %%i in ('dir /b %input%') do (set apkUrl=!apkUrl!%%i)
set fileName=%apkUrl:.apk=%
rem 解包并删除签名
if exist output rmdir /s/q output
md output
cd output
md %fileName%
unzip -o -d %fileName% %input%
rmdir /s/q %fileName%\META-INF
rem 修改文件打包并签名
for do (
[这里填写修改操作]
cd !fileName!
zip -r [your file name].apk ./*
cd..
jarsigner -verbose -keystore [keystore path] -signedjar [out apk name].apk [current apk name].apk [alias name] -storepass [password]
)
pause

  在这里我使用了for循环获取了解压后的文件夹名的方法,实现起来可能显得会比较扭曲。但是如果在事先不确定拖入文件名,并且打出的包对原文件名有需求的话,就得用这种方法去取了。除此之外,在进行签名的时候,签名前和签名后文件名可以是同一个,这样就不会出现新的apk,而是在原有未签名apk的基础上进行签名。