一、引言
最近在做移动发布平台,其中有一个功能,需要上传apk/zip/ipa包文件并拿到各个包所对应的版本号和版本编号。不用解压文件,而是直接读取文件流,从文件流中解析得到数据。
代码也是自己从网上各个地方找然后汇总并测试通过的,中间也踩过一些坑,就当记录一下,不喜勿喷。
二、工具类说明:
该工具类有三个解析方法parseZip、parseApk、parseIpa。解析的代码就在下面,测试过,不用多说,拿来就能用。此处要说一下需要的依赖包。parseApk需要一个AXMLPrinter2.jar包,这个包在Maven仓库里没有,需要自己找,然后在Maven工程里外部依赖,如果不是Maven工程那就太简单了吧。parseIpa需要一个dd-plist.jar,这个包在Maven仓库里可以找到,直接添加依赖就好了。
/**
* @description: 解析文件工具类,如:zip,apk,ipa
**/
public class ParseFileUtil {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(ParseFileUtil.class);
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", "", "", "", "", "", "" };
/**
* 解析zip包中的manifest.json文件和读取图标
* @param filePath
* @throws Exception
*/
public static Map parseZip(String filePath,String dir) {
Map map = new HashMap();
String iconPath = dir + "/icon";
try {
File file = new File(filePath);
ZipFile zf = new ZipFile(file);
InputStream in = new BufferedInputStream(new FileInputStream(file));
ZipInputStream zis = new ZipInputStream(in);
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
// 如果为'static'的目录
if (ze.isDirectory() && ze.getName().contains("static")) {
while((ze = zis.getNextEntry()) != null){
if (ze.getName().contains("logo.png") && ze.getSize() > 0){
// icon 后缀
String iconSuffix = ze.getName().substring(ze.getName().trim().lastIndexOf("."));
String iconTid = Utility.newUUID();
String iconFullPath = iconPath + "/" + iconTid + iconSuffix;
File icon = new File(iconFullPath);
InputStream is = zf.getInputStream(ze);
FileOutputStream fos = new FileOutputStream(icon);
int len = 0;
while ((len = is.read()) != -1) {
fos.write(len);
}
map.put("iconTid",iconTid);
map.put("iconPath",iconFullPath);
fos.close();
is.close();
}
}
} else {
if (ze.getName().contains("manifest.json")) {
if (ze.getSize() > 0) {
BufferedReader br = new BufferedReader(new InputStreamReader(zf.getInputStream(ze), "UTF-8"));
String line = null;
String content = "";
while ((line = br.readLine()) != null) {
content = content + line;
}
br.close();
String s = java.net.URLDecoder.decode(content, "utf-8");
JSONObject json = JSONObject.parseObject(s);
String key = json.get("id").toString();
String name = json.get("name").toString();
JSONObject version = json.getJSONObject("version");
String versionName = version.get("name").toString();
String versionCode = version.get("code").toString();
map.put("key",key);
map.put("name",name);
map.put("versionCode",versionCode);
map.put("versionName",versionName);
}
}
}
}
zis.closeEntry();
zis.close();
zf.close();
}catch (Exception e){
log.info("小程序包Manifest.json文件解析异常!");
}
return map;
}
/**
* 解析apk文件
* @param apkUrl apk的文件路径
* @return
*/
public static Map parseAPK(String apkUrl) {
Map map = new HashMap();
ZipFile zipFile;
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) {
log.info("APK解析异常!");
}
return map;
}
/**
* 解析ipa文件
* @param ipaUrl ipa的文件路径
* @return
*/
public static Map parseIpa(String ipaUrl) {
Map map = new HashMap();
try {
File file = new File(ipaUrl);
InputStream is = new FileInputStream(file);
ZipInputStream zipIns = new ZipInputStream(is);
ZipEntry ze;
InputStream infoIs = null;
while ((ze = zipIns.getNextEntry()) != null) {
if (!ze.isDirectory()) {
String name = ze.getName();
if (null != name && name.toLowerCase().contains(".app/info.plist")) {
ByteArrayOutputStream _copy = new ByteArrayOutputStream();
int chunk = 0;
byte[] data = new byte[1024];
while (-1 != (chunk = zipIns.read(data))) {
_copy.write(data, 0, chunk);
}
infoIs = new ByteArrayInputStream(_copy.toByteArray());
break;
}
}
}
NSDictionary rootDict = (NSDictionary) PropertyListParser.parse(infoIs);
// 如果想要查看有哪些key ,可以把下面注释放开
// for (String keyName : rootDict.allKeys()) {
// System.out.println(keyName + ":" + rootDict.get(keyName).toString());
// }
// 应用包名
NSString parameters = (NSString) rootDict.get("CFBundleIdentifier");
map.put("package", parameters.toString());
// 应用版本名
parameters = (NSString) rootDict.objectForKey("CFBundleShortVersionString");
map.put("versionName", parameters.toString());
// 应用版本号
parameters = (NSString) rootDict.get("CFBundleVersion");
map.put("versionCode", parameters.toString());
infoIs.close();
is.close();
zipIns.close();
} catch (Exception e) {
log.info("ipa包解析异常!");
}
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_FIRST_INT && type >> 24 == 1) {
return "android:";
}
return "";
}
public static float complexToFloat(int complex) {
return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
}
public static void printMap(Map map){
for (String key : map.keySet()) {
log.info(key + ":" + map.get(key));
}
}
}
public static void main(String[] args) {
String filePath = "F:\\www\\com\\mobile\\publish\\file\\json.zip";
String dir = "F:/www/com/mobile/publish";
System.out.println("************解析Zip开始****************");
Map mapZip = parseZip(filePath, dir);
for (String key : mapZip.keySet()) {
System.out.println(key + ":" + mapZip.get(key));
}
System.out.println("************解析Zip结束****************");
System.out.println("************解析apk开始****************");
String apkUrl = "F:\\www\\com\\mobile\\publish\\app\\App_V1.0.4.apk";
Map mapApk = parseAPK(apkUrl);
for (String key : mapApk.keySet()) {
System.out.println(key + ":" + mapApk.get(key));
}
System.out.println("************解析apk结束****************");
System.out.println("************解析ipa开始****************");
String ipaUrl = "F:\\www\\com\\mobile\\publish\\ipa\\Payload.ipa";
Map mapIpa = parseIpa(ipaUrl);
for (String key : mapIpa.keySet()) {
System.out.println(key + ":" + mapIpa.get(key));
}
System.out.println("************解析ipa结束****************");
}
测试结果:(中间涉及公司名称的地方被去掉了)