最近在做spring boot的项目应用更新管理模块,上传android apk并自动解析apk信息。
我使用的方法有两种:
1.种用axmlprinter2.jar包解析,读取AndroidManifest文件相关信息。
2.使用aapt(Android Asset Packaging Tool),Android资源打包工具。
使用jar解析时有些apk读取不出来,因此推荐使用第二种方法。
aapt解析apk信息(支持Windows、Linux)
1.获取aapt.exe,Windows上使用。
android SDK安装目录下:\Android\Sdk\build-tools\28.0.3
2.获取aapt,Linux上使用。
首先下载最新工具包,apktool_2.3.4.jar
下载地址:https://ibotpeaches.github.io/Apktool/install/
解压缩,aapt在prebuit/aapt/linux/
3.将aapt放在src/main/resources
具体实现代码如下:
/*****
*
* @author ZhongXiang.Huang
* apk工具类。封装了获取Apk信息的方法。
*
* ******/
public class ApkUtil {
private static final Logger log = LoggerFactory.getLogger(AppUpdateController.class);
/********
* 使用aapt.exe工具解析
*
* *******/
public static final String APPLICATION = "application:";
public static final String APPLICATION_ICON = "application-icon";
public static final String APPLICATION_LABEL = "application-label";
public static final String APPLICATION_LABEL_N = "application: label";
public static final String DENSITIES = "densities";
public static final String LAUNCHABLE_ACTIVITY = "launchable";
public static final String PACKAGE = "package";
public static final String SDK_VERSION = "sdkVersion";
public static final String SUPPORTS_ANY_DENSITY = "support-any-density";
public static final String SUPPORTS_SCREENS = "support-screens";
public static final String TARGET_SDK_VERSION = "targetSdkVersion";
public static final String VERSION_CODE = "versionCode";
public static final String VERSION_NAME = "versionName";
public static final String USES_FEATURE = "uses-feature";
public static final String USES_IMPLIED_FEATURE = "uses-implied-feature";
public static final String USES_PERMISSION = "uses-permission";
private static final String SPLIT_REGEX = "(: )|(=')|(' )|'";
private ProcessBuilder builder;
// aapt 所在目录
// private String aaptToolPath = "src/main/resources/aapt/";
//
public ApkUtil() {
builder = new ProcessBuilder();
builder.redirectErrorStream(true);
}
//
// public String getAaptToolPath() {
// return aaptToolPath;
// }
//
// public void setAaptToolPath(String aaptToolPath) {
// this.aaptToolPath = aaptToolPath;
// }
public ApkInfo parseApk(String aaptToolPath, String apkPath) {
String aaptTool = aaptToolPath + getAaptToolName();
Process process = null;
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
process = builder.command(aaptTool, "d", "badging", apkPath).start();
inputStream = process.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
ApkInfo apkInfo = new ApkInfo();
apkInfo.setSize(new File(apkPath).length());
String temp = null;
while ((temp = bufferedReader.readLine()) != null) {
setApkInfoProperty(apkInfo, temp);
}
return apkInfo;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (process != null) {
process.destroy();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private String getAaptToolName() {
String aaptToolName = "aapt";
if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) {
aaptToolName += ".exe";
}
return aaptToolName;
}
private void setApkInfoProperty(ApkInfo apkInfo, String source) {
System.out.println("*******************************");
if (source.startsWith(APPLICATION)) {
System.out.println(APPLICATION + " : ");
String[] rs = source.split("( icon=')|'");
apkInfo.setIcon(rs[rs.length - 1]);
} else if (source.startsWith(APPLICATION_ICON)) {
System.out.println(APPLICATION_ICON + " : ");
apkInfo.addToIcons(getKeyBeforeColon(source), getPropertyInQuote(source));
} else if (source.startsWith(APPLICATION_LABEL)) {
System.out.println(APPLICATION_LABEL + " : ");
apkInfo.setLabel(getPropertyInQuote(source));
} else if (source.startsWith(LAUNCHABLE_ACTIVITY)) {
System.out.println(LAUNCHABLE_ACTIVITY + " : ");
apkInfo.setLaunchableActivity(getPropertyInQuote(source));
} else if (source.startsWith(PACKAGE)) {
System.out.println(PACKAGE + " : ");
String[] packageInfo = source.split(SPLIT_REGEX);
apkInfo.setPackageName(packageInfo[2]);
apkInfo.setVersionCode(packageInfo[4]);
apkInfo.setVersionName(packageInfo[6]);
} else if (source.startsWith(SDK_VERSION)) {
System.out.println(SDK_VERSION + " : ");
apkInfo.setSdkVersion(getPropertyInQuote(source));
} else if (source.startsWith(TARGET_SDK_VERSION)) {
System.out.println(TARGET_SDK_VERSION + " : ");
apkInfo.setTargetSdkVersion(getPropertyInQuote(source));
} else if (source.startsWith(USES_PERMISSION)) {
System.out.println(USES_PERMISSION + " : ");
apkInfo.addToUsesPermissions(getPropertyInQuote(source));
} else if (source.startsWith(USES_FEATURE)) {
System.out.println(USES_FEATURE + " : ");
apkInfo.addToFeatures(getPropertyInQuote(source));
} else {
System.out.println("Others : ");
}
System.out.println(source);
}
private String getKeyBeforeColon(String source) {
return source.substring(0, source.indexOf(':'));
}
private String getPropertyInQuote(String source) {
int index = source.indexOf("'") + 1;
return source.substring(index, source.indexOf('\'', index));
}
}
ApkUtil apkUtil = new ApkUtil();
ApkInfo apkInfo = apkUtil.parseApk(getApptToolPath(), path);
/*****
*/部署时aapt/目录下要放在jar包同级目录,并要给aapt赋予运行权限
***/
private String getApptToolPath() {
if(env.equals("dev")) {
return "src/main/resources/aapt/";
}else {
//ali环境 Linux,以jar包运行
String jarPath = System.getProperty("java.class.path");
int firstIndex = jarPath.lastIndexOf(System.getProperty("path.separator")) + 1;
int lastIndex = jarPath.lastIndexOf(File.separator) + 1;
String path = jarPath.substring(firstIndex, lastIndex);
return path + "/aapt/";
}
}
使用jar包解析
maven地址:
<dependency>
<groupId>ca.mcgill.sable</groupId>
<artifactId>axmlprinter2</artifactId>
<version>2016-07-27</version>
</dependency>
具体代码:
/*****
*
* @author ZhongXiang.Huang
* apk工具类。封装了获取Apk信息的方法。
*
* ******/
public class ApkUtil {
private static final Logger log = LoggerFactory.getLogger(AppUpdateController.class);
/********
* 使用axmlprinter2-1.21.jar解析
*
* *******/
public static Map<String,Object> readAPK(String apkUrl){
ZipFile zipFile;
Map<String,Object> map = new HashMap<String, Object>();
map.put("code", "success");
try {
zipFile = new ZipFile(apkUrl);
Enumeration<?> enumeration = zipFile.entries();
ZipEntry zipEntry = null;
while (enumeration.hasMoreElements()) {
zipEntry = (ZipEntry) enumeration.nextElement();
if (zipEntry.isDirectory()) {
} else {
if ("androidmanifest.xml".equals(zipEntry.getName().toLowerCase())) {
AXmlResourceParser parser = new AXmlResourceParser();
parser.open(zipFile.getInputStream(zipEntry));
while (true) {
int type = parser.next();
if (type == XmlPullParser.END_DOCUMENT) {
break;
}
String name = parser.getName();
if(null != name && name.toLowerCase().equals("manifest")){
for (int i = 0; i != parser.getAttributeCount(); i++) {
if ("versionName".equals(parser.getAttributeName(i))) {
String versionName = getAttributeValue(parser, i);
if(null == versionName){
versionName = "";
}
map.put("versionName", versionName);
} else if ("package".equals(parser.getAttributeName(i))) {
String packageName = getAttributeValue(parser, i);
if(null == packageName){
packageName = "";
}
map.put("package", packageName);
} else if("versionCode".equals(parser.getAttributeName(i))){
String versionCode = getAttributeValue(parser, i);
if(null == versionCode){
versionCode = "";
}
map.put("versionCode", versionCode);
}
}
break;
}
}
}
}
}
zipFile.close();
} catch (Exception e) {
map.replace("code", "fail");
map.put("error","读取apk失败,请手动填写。");
e.printStackTrace();
}
return map;
}
private static String getAttributeValue(AXmlResourceParser parser, int index) {
int type = parser.getAttributeValueType(index);
int data = parser.getAttributeValueData(index);
if (type == TypedValue.TYPE_STRING) {
return parser.getAttributeValue(index);
}
if (type == TypedValue.TYPE_ATTRIBUTE) {
return String.format("?%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_REFERENCE) {
return String.format("@%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_FLOAT) {
return String.valueOf(Float.intBitsToFloat(data));
}
if (type == TypedValue.TYPE_INT_HEX) {
return String.format("0x%08X", data);
}
if (type == TypedValue.TYPE_INT_BOOLEAN) {
return data != 0 ? "true" : "false";
}
if (type == TypedValue.TYPE_DIMENSION) {
return Float.toString(complexToFloat(data)) + DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type == TypedValue.TYPE_FRACTION) {
return Float.toString(complexToFloat(data)) + FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
return String.format("#%08X", data);
}
if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) {
return String.valueOf(data);
}
return String.format("<0x%X, type 0x%02X>", data, type);
}
private static String getPackage(int id) {
if (id >>> 24 == 1) {
return "android:";
}
return "";
}
// / ILLEGAL STUFF, DONT LOOK :)
public static float complexToFloat(int complex) {
return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
}
private static final float RADIX_MULTS[] =
{
0.00390625F, 3.051758E-005F,
1.192093E-007F, 4.656613E-010F
};
private static final String DIMENSION_UNITS[] = { "px", "dip", "sp", "pt", "in", "mm", "", "" };
private static final String FRACTION_UNITS[] = { "%", "%p", "", "", "", "", "", "" };
}
参考文献:https://blog.csdn.net/silent_paladin/article/details/57112105