最近在项目中需要使用 php 提取 apk 包的主要信息如包名、应用名称、版本号、入口地址和应用 Icon 等。安卓 apk 的大部分信息都保存在包内的 AndroidManifest.xml 文件中,Icon 则保存在 drawable | drawable-hdpi | drawable-nodpi | drawable-ldpi | drawable-mdpi 这些文件夹中。
使用 php 读取这些信息,网友大头爸爸提供了一种做法:在服务器上安装 java 包 和 反编译工具 apktool ,通过 php 的 exec() 调用 java 命令来反编译 apk 包,然后获取 AndroidManifest.xml 文件的内容,再根据正则表达式匹配出所需要的信息。提取图标则是通过遍历 drawable | drawable-hdpi | drawable-nodpi | drawable-ldpi | drawable-mdpi 这些文件夹来获取一个 size 最大的 Icon 。但是 反编译 相当耗时,不适用于实时程序。(源代码见大头爸爸 博客:http://www.voidcn.com/article/p-wkluywgx-gw.html)。
还有经典的 ApkParser,原理是使用 PHP 的 zip 功能函数直接解压 apk 包而不需要外加 java 或命令行工具来获取 AndroidManifest.xml 文件的内容,然后也是根据正则表达式匹配出所需信息。这种方法执行速度快但是得不到 Icon 。(源代码见该方法作者 Katana's碎碎念 博客:http://blog.katcin.com/archives/69)。
再后来看到了 cn王帅 的方案,觉得挺棒的。他这样做:
1、用 aapt 读取包信息
aapt dump badging ./xxx.apk
2、用 unzip 直接提取需要的图标文件
#解压apk包中指定图片
unzip ./xxx.apk res/drawable-mdpi/icon.png -d /tmp
#将解压出来的图片移到我们需要的位置
mv /tmp/res/drawable-mdpi/icon.png /tmp/temp.png
aapt 包含在 android sdk 中的 build-tools 文件夹,可以把这个文件拷到服务器上单独使用,大小约1.2M。要记得用 chmod 赋予可执行权限。还要注意的是,这个程序是32位环境的程序,64位系统运行可能需要额外安装大概多个扩展包支持才能运行。aapt 命令执行很快,PHP 调用这个命令可以实时返回数据。返回的数据中包含了包名、应用名称、 Icon 在 apk 包中的位置(类似 res/drawable-mdpi/icon.png)。返回的信息是纯文本,不方便 PHP 直接使用,需要将其转为数组。
因为 apk 本身就是 zip 文件重命名而已,所以无需改名,可以直接解压。另外 unzip 可以指定只解压压缩包中某文件,也节省了很多资源。
class Service_Android {
/**
* 获取Apk包信息
* 需要/usr/bin/aapt
*
* @param $apkFile
* @return array
*/
public function getApkInfo($apkFile) {
try {
exec('/usr/bin/aapt dump badging ' . $apkFile, $out, $return);
$apkInfo = array();
foreach($out as $line) {
$lineana = array();
$a = explode(":", $line);
$key = trim($a[0]);
$value = trim($a[1]);
preg_match_all('/((?P\S+)=)?\'(?P.*?)\'/', $value, $matches, PREG_SET_ORDER);
foreach($matches as $match) {
if ($match['key']) {
$lineana[$match['key']] = $match['value'];
} else {
$lineana[] = $match['value'];
}
}
$apkInfo[$key][] = $lineana;
}
//checkRet会把上面读出来的配置整理一下
$ret = $this->checkRet($apkInfo);
} catch(Exception $e) {
echo $e->getMessage();
$ret = array();
}
return $ret;
}
/**
* 从Apk包中提取指定文件,并移到$toFile
*
* @param $apkFile apk文件
* @param $sourceFile apk文件中相应文件路径
* @param $toFile 输出文件
* @return bool
*/
function getFileFromApk($apkFile, $sourceFile, $toDir, $toFile) {
exec("unzip {$apkFile} {$sourceFile} -d {$toDir}", $out, $return);
if (rename("{$toDir}{$sourceFile}", "{$toDir}{$toFile}")) {
exec("rm -rf {$toDir}/*");
return true;
} else {
exec("rm -rf {$toDir}/*");
return false;
}
}
/**
* 辅助函数,处理Apk信息数组
*
* @param $info
* @return mixed
*/
function checkRet($info) {
foreach($info as $key => $lineana) {
if (is_array($lineana)) {
$info[$key] = $this->checkRet($lineana);
if (count($info[$key]) == 1) {
$info[$key] = current($info[$key]);
}
}
}
return $info;
}
}
使用方法:
include('Service_Android.class.php');
$apk = "b.apk";
$iconDir = '../icon/';
$iconFile = 'icon.png';
$android = new Service_Android();
$res = $android->getApkInfo($apk);
$android->getFileFromApk($apk, $res['application']['icon'], $iconDir , $iconFile);
另外,如果你恰巧真是64位系统,使用过程可能会出现以下错误:
错误1:
-bash: /usr/bin/aapt: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
错误处理:
#查包
yum whatprovides ld-linux.so.2
#安装
yum install -y glibc-2.12-1.149.el6_6.4.i686
错误2:
/usr/bin/aapt: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
错误处理:
#查包
yum whatprovides */libz.so.1
#安装
yum install -y zlib-1.2.3-29.el6.i686
错误3:
/usr/bin/aapt: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
错误处理:
yum install -y libstdc++
#查包
yum whatprovides libstdc++.so.6
#安装
yum install -y libstdc++-4.4.7-11.el6.i686
其实上面的那些错误都是缺少 aapt 的扩展包导致的,使用命令:
#查看依赖
ldd /usr/bin/aapt
来查看缺失哪些包,把它们装上就好了