当我们上传apk或者ipa文件的时候是要读取到里面的一些信息的,而对于未知的apk或者ipa,我们无法直接获取其中的包名、应用名、图标等,这时候通过工具类来获取信息就很有必要了。在这里,我总结了一下读取apk,ipa文件的代码和使用方法,大家可以参考下,取其精华、去其糟粕,以便适用于自己的需求。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 首先看一下使用到的工具
环境:Linux
apktool是google提供的apk编译工具,需要Java运行环境,推荐使用JDK1.6或者JDK1.7;dd-plist-1.16这个jar包需要JDK1.5以上。
所以,建议JDK环境1.6以上。
解析apk:使用windows开发环境的时候直接下载aapt.exe即可
(1)aapt
(2)apktool
(3)apktool.jar (google提供)
解析ipa:
(1)dd-plist-1.16.jar(google提供)
(2)python 2.6
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2 解析apk
对于apk的解析,我第一个想到的是导入相应的jar包,然后用一些代码就可以读取出来,这样的话就比较方便简单。事实上,这样做的确是可以读取到一些信息的,比如说apk的包名、版本号、版本名,但是对于apk的名称和图标就读取不到了,显然这个不能满足于我们的要求。所以,我希望可以找到一种能够读取尽量多信息的方法,通过在网上查询比较,我选择了aapt这个工具。尽管aapt工具很烦人,比我之前说的导jar包的方式繁琐很多,但是我们需要解析未知apk的图标,名称,不得不使用这个aapt工具了。
2.1 在Linux下安装aapt工具
apktool和aapt各种版本的下载地址:http://connortumbleson.com/apktool/
下载apktool
# wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool
下载apktool_2.2.1.jar并且重命名为apktool.jar
# wget http://connortumbleson.com/apktool/apktool_2.2.1.jar
# mv apktool_2.2.1.jar apktool.jar
下载aapt
http://connortumbleson.com/apktool/aapt/linux/aapt
新建/usr/local/apktool文件夹,将apktool,apktool.jar和aapt移进来
# mv apktool apktool.jar aapt /usr/local/apktool
赋予apktool,apktool.jar和aapt可执行权限
# chmod +x apktool apktool.jar aapt
将apktool加入环境变量,修改/etc/profile,在最后一行添加如下内容
export PATH="$PATH:/usr/local/apktool"
使环境变量立即生效
# source /etc/profile
使用aapt工具时可能报如下错误
aapt: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by aapt)
这是因为缺少glibc-2.14,安装一下就好了
# wget http://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.gz
# tar zxvf glibc-2.14.tar.gz
# cd glibc-2.14
# mkdir build
# cd build
# ../configure --prefix=/opt/glibc-2.14
# make -j4
# make install
修改/etc/profile,在最后一行添加如下内容
export LD_LIBRARY_PATH=/opt/glibc-2.14/lib:$LD_LIBRARY_PATH
使环境变量立即生效
# source /etc/profile
给aapt相应权限
# chmod 755 aapt
apktool和aapt简单使用方法
# apktool d /data/test.apk //反编译apk
# aapt dump badging /data/test.apk //查看apk包详细信息
2.2 建两个实体类
这两个实体类封装了要解析apk的一些信息字段,比如说apk的包名、版本号、版本名、所需设备特性等。
代码如下:

1/** 2 * apk实体信息 3 */ 4public class ApkInfo { 5 public static final String APPLICATION_ICON_120 = "application-icon-120"; 6 public static final String APPLICATION_ICON_160 = "application-icon-160"; 7 public static final String APPLICATION_ICON_240 = "application-icon-240"; 8 public static final String APPLICATION_ICON_320 = "application-icon-320"; 9 /** 10 * apk内部版本号 11 */ 12 private String versionCode = null; 13 /** 14 * apk外部版本号 15 */ 16 private String versionName = null; 17 /** 18 * apk的包名 19 */ 20 private String packageName = null; 21 /** 22 * 支持的android平台最低版本号 23 */ 24 private String minSdkVersion = null; 25 /** 26 * apk所需要的权限 27 */ 28 private List<String> usesPermissions = null; 29 /** 30 * 支持的SDK版本。 31 */ 32 private String sdkVersion; 33 /** 34 * 建议的SDK版本 35 */ 36 private String targetSdkVersion; 37 /** 38 * 应用程序名 39 */ 40 private String applicationLable; 41 /** 42 * 各个分辨率下的图标的路径。 43 */ 44 private Map<String, String> applicationIcons; 45 /** 46 * 程序的图标。 47 */ 48 private String applicationIcon; 49 /** 50 * 暗指的特性。 51 */ 52 private List<ImpliedFeature> impliedFeatures; 53 /** 54 * 所需设备特性。 55 */ 56 private List<String> features; 57 /** 58 * 启动界面 59 */ 60 private String launchableActivity; 61 62 public ApkInfo() { 63 this.usesPermissions = new ArrayList<String>(); 64 this.applicationIcons = new HashMap<String, String>(); 65 this.impliedFeatures = new ArrayList<ImpliedFeature>(); 66 this.features = new ArrayList<String>(); 67 } 68 69 /** 70 * 返回版本代码。 71 * 72 * @return 版本代码。 73 */ 74 public String getVersionCode() { 75 return versionCode; 76 } 77 78 /** 79 * @param versionCode 80 * the versionCode to set 81 */ 82 public void setVersionCode(String versionCode) { 83 this.versionCode = versionCode; 84 } 85 86 /** 87 * 返回版本名称。 88 * 89 * @return 版本名称。 90 */ 91 public String getVersionName() { 92 return versionName; 93 } 94 95 /** 96 * @param versionName 97 * the versionName to set 98 */ 99 public void setVersionName(String versionName) { 100 this.versionName = versionName; 101 } 102 103 /** 104 * 返回支持的最小sdk平台版本。 105 * 106 * @return the minSdkVersion 107 */ 108 public String getMinSdkVersion() { 109 return minSdkVersion; 110 } 111 112 /** 113 * @param minSdkVersion 114 * the minSdkVersion to set 115 */ 116 public void setMinSdkVersion(String minSdkVersion) { 117 this.minSdkVersion = minSdkVersion; 118 } 119 120 /** 121 * 返回包名。 122 * 123 * @return 返回的包名。 124 */ 125 public String getPackageName() { 126 return packageName; 127 } 128 129 public void setPackageName(String packageName) { 130 this.packageName = packageName; 131 } 132 133 /** 134 * 返回sdk平台版本。 135 * 136 * @return 137 */ 138 public String getSdkVersion() { 139 return sdkVersion; 140 } 141 142 public void setSdkVersion(String sdkVersion) { 143 this.sdkVersion = sdkVersion; 144 } 145 146 /** 147 * 返回所建议的SDK版本。 148 * 149 * @return 150 */ 151 public String getTargetSdkVersion() { 152 return targetSdkVersion; 153 } 154 155 public void setTargetSdkVersion(String targetSdkVersion) { 156 this.targetSdkVersion = targetSdkVersion; 157 } 158 159 /** 160 * 返回所需的用户权限。 161 * 162 * @return 163 */ 164 public List<String> getUsesPermissions() { 165 return usesPermissions; 166 } 167 168 public void setUsesPermissions(List<String> usesPermission) { 169 this.usesPermissions = usesPermission; 170 } 171 172 public void addToUsesPermissions(String usesPermission) { 173 this.usesPermissions.add(usesPermission); 174 } 175 176 /** 177 * 返回程序的名称标签。 178 * 179 * @return 180 */ 181 public String getApplicationLable() { 182 return applicationLable; 183 } 184 185 public void setApplicationLable(String applicationLable) { 186 this.applicationLable = applicationLable; 187 } 188 189 /** 190 * 返回应用程序的图标。 191 * 192 * @return 193 */ 194 public String getApplicationIcon() { 195 return applicationIcon; 196 } 197 198 public void setApplicationIcon(String applicationIcon) { 199 this.applicationIcon = applicationIcon; 200 } 201 202 /** 203 * 返回应用程序各个分辨率下的图标。 204 * 205 * @return 206 */ 207 public Map<String, String> getApplicationIcons() { 208 return applicationIcons; 209 } 210 211 public void setApplicationIcons(Map<String, String> applicationIcons) { 212 this.applicationIcons = applicationIcons; 213 } 214 215 public void addToApplicationIcons(String key, String value) { 216 this.applicationIcons.put(key, value); 217 } 218 219 public void addToImpliedFeatures(ImpliedFeature impliedFeature) { 220 this.impliedFeatures.add(impliedFeature); 221 } 222 223 /** 224 * 返回应用程序所需的暗指的特性。 225 * 226 * @return 227 */ 228 public List<ImpliedFeature> getImpliedFeatures() { 229 return impliedFeatures; 230 } 231 232 public void setImpliedFeatures(List<ImpliedFeature> impliedFeatures) { 233 this.impliedFeatures = impliedFeatures; 234 } 235 236 /** 237 * 返回应用程序所需的特性。 238 * 239 * @return 240 */ 241 public List<String> getFeatures() { 242 return features; 243 } 244 245 public void setFeatures(List<String> features) { 246 this.features = features; 247 } 248 249 public void addToFeatures(String feature) { 250 this.features.add(feature); 251 } 252 253 @Override 254 public String toString() { 255 return "ApkInfo [versionCode=" + versionCode + ",\n versionName=" 256 + versionName + ",\n packageName=" + packageName 257 + ",\n minSdkVersion=" + minSdkVersion + ",\n usesPermissions=" 258 + usesPermissions + ",\n sdkVersion=" + sdkVersion 259 + ",\n targetSdkVersion=" + targetSdkVersion 260 + ",\n applicationLable=" + applicationLable 261 + ",\n applicationIcons=" + applicationIcons 262 + ",\n applicationIcon=" + applicationIcon 263 + ",\n impliedFeatures=" + impliedFeatures + ",\n features=" 264 + features + ",\n launchableActivity=" + launchableActivity + "\n]"; 265 } 266 267 public String getLaunchableActivity() { 268 return launchableActivity; 269 } 270 271 public void setLaunchableActivity(String launchableActivity) { 272 this.launchableActivity = launchableActivity; 273 } 274}
ApkInfo

1/** 2 * 特性实体 3 */ 4public class ImpliedFeature { 5 6 /** 7 * 所需设备特性名称。 8 */ 9 private String feature; 10 11 /** 12 * 表明所需特性的内容。 13 */ 14 private String implied; 15 16 public ImpliedFeature() { 17 super(); 18 } 19 20 public ImpliedFeature(String feature, String implied) { 21 super(); 22 this.feature = feature; 23 this.implied = implied; 24 } 25 26 public String getFeature() { 27 return feature; 28 } 29 30 public void setFeature(String feature) { 31 this.feature = feature; 32 } 33 34 public String getImplied() { 35 return implied; 36 } 37 38 public void setImplied(String implied) { 39 this.implied = implied; 40 } 41 42 @Override 43 public String toString() { 44 return "Feature [feature=" + feature + ", implied=" + implied + "]"; 45 } 46}
ImpliedFeature
2.3 apk的工具类
代码如下:

1/** 2 * apk工具类。封装了获取Apk信息的方法。 3 * 包名/版本号/版本名/应用程序名/支持的android最低版本号/支持的SDK版本/建议的SDK版本/所需设备特性等 4 */ 5public class ApkUtil { 6 public static final String VERSION_CODE = "versionCode"; 7 public static final String VERSION_NAME = "versionName"; 8 public static final String SDK_VERSION = "sdkVersion"; 9 public static final String TARGET_SDK_VERSION = "targetSdkVersion"; 10 public static final String USES_PERMISSION = "uses-permission"; 11 public static final String APPLICATION_LABEL = "application-label"; 12 public static final String APPLICATION_ICON = "application-icon"; 13 public static final String USES_FEATURE = "uses-feature"; 14 public static final String USES_IMPLIED_FEATURE = "uses-implied-feature"; 15 public static final String SUPPORTS_SCREENS = "supports-screens"; 16 public static final String SUPPORTS_ANY_DENSITY = "supports-any-density"; 17 public static final String DENSITIES = "densities"; 18 public static final String PACKAGE = "package"; 19 public static final String APPLICATION = "application:"; 20 public static final String LAUNCHABLE_ACTIVITY = "launchable-activity"; 21 22 private ProcessBuilder mBuilder; 23 private static final String SPLIT_REGEX = "(: )|(=')|(' )|'"; 24 private static final String FEATURE_SPLIT_REGEX = "(:')|(',')|'"; 25 /** 26 * aapt所在的目录。 27 */ 28 //windows环境下直接指向appt.exe 29 //比如你可以放在src下 30 private String mAaptPath = "src/main/java/aapt"; 31 //linux下 32 //private String mAaptPath = "/usr/local/apktool/aapt"; 33 34 public ApkUtil() { 35 mBuilder = new ProcessBuilder(); 36 mBuilder.redirectErrorStream(true); 37 } 38 39 /** 40 * 返回一个apk程序的信息。 41 * 42 * @param apkPath apk的路径。 43 * @return apkInfo 一个Apk的信息。 44 */ 45 public ApkInfo getApkInfo(String apkPath) throws Exception { 46 System.out.println("================================开始执行命令========================="); 47 //通过命令调用aapt工具解析apk文件 48 Process process = mBuilder.command(mAaptPath, "d", "badging", apkPath) 49 .start(); 50 InputStream is = null; 51 is = process.getInputStream(); 52 BufferedReader br = new BufferedReader( 53 new InputStreamReader(is, "utf8")); 54 String tmp = br.readLine(); 55 try { 56 if (tmp == null || !tmp.startsWith("package")) { 57 throw new Exception("参数不正确,无法正常解析APK包。输出结果为:\n" + tmp + "..."); 58 } 59 ApkInfo apkInfo = new ApkInfo(); 60 do { 61 setApkInfoProperty(apkInfo, tmp); 62 } while ((tmp = br.readLine()) != null); 63 return apkInfo; 64 } catch (Exception e) { 65 throw e; 66 } finally { 67 process.destroy(); 68 closeIO(is); 69 closeIO(br); 70 } 71 } 72 73 /** 74 * 设置APK的属性信息。 75 * 76 * @param apkInfo 77 * @param source 78 */ 79 private void setApkInfoProperty(ApkInfo apkInfo, String source) { 80 if (source.startsWith(PACKAGE)) { 81 splitPackageInfo(apkInfo, source); 82 } else if (source.startsWith(LAUNCHABLE_ACTIVITY)) { 83 apkInfo.setLaunchableActivity(getPropertyInQuote(source)); 84 } else if (source.startsWith(SDK_VERSION)) { 85 apkInfo.setSdkVersion(getPropertyInQuote(source)); 86 } else if (source.startsWith(TARGET_SDK_VERSION)) { 87 apkInfo.setTargetSdkVersion(getPropertyInQuote(source)); 88 } else if (source.startsWith(USES_PERMISSION)) { 89 apkInfo.addToUsesPermissions(getPropertyInQuote(source)); 90 } else if (source.startsWith(APPLICATION_LABEL)) { 91 //windows下获取应用名称 92 apkInfo.setApplicationLable(getPropertyInQuote(source)); 93 } else if (source.startsWith(APPLICATION_ICON)) { 94 apkInfo.addToApplicationIcons(getKeyBeforeColon(source), 95 getPropertyInQuote(source)); 96 } else if (source.startsWith(APPLICATION)) { 97 String[] rs = source.split("( icon=')|'"); 98 apkInfo.setApplicationIcon(rs[rs.length - 1]); 99 //linux下获取应用名称 100 apkInfo.setApplicationLable(rs[1]); 101 } else if (source.startsWith(USES_FEATURE)) { 102 apkInfo.addToFeatures(getPropertyInQuote(source)); 103 } else if (source.startsWith(USES_IMPLIED_FEATURE)) { 104 apkInfo.addToImpliedFeatures(getFeature(source)); 105 } else { 106// System.out.println(source); 107 } 108 } 109 110 private ImpliedFeature getFeature(String source) { 111 String[] result = source.split(FEATURE_SPLIT_REGEX); 112 ImpliedFeature impliedFeature = new ImpliedFeature(result[1], result[2]); 113 return impliedFeature; 114 } 115 116 /** 117 * 返回出格式为name: 'value'中的value内容。 118 * 119 * @param source 120 * @return 121 */ 122 private String getPropertyInQuote(String source) { 123 int index = source.indexOf("'") + 1; 124 return source.substring(index, source.indexOf('\'', index)); 125 } 126 127 /** 128 * 返回冒号前的属性名称 129 * 130 * @param source 131 * @return 132 */ 133 private String getKeyBeforeColon(String source) { 134 return source.substring(0, source.indexOf(':')); 135 } 136 137 /** 138 * 分离出包名、版本等信息。 139 * 140 * @param apkInfo 141 * @param packageSource 142 */ 143 private void splitPackageInfo(ApkInfo apkInfo, String packageSource) { 144 String[] packageInfo = packageSource.split(SPLIT_REGEX); 145 apkInfo.setPackageName(packageInfo[2]); 146 apkInfo.setVersionCode(packageInfo[4]); 147 apkInfo.setVersionName(packageInfo[6]); 148 } 149 150 /** 151 * 释放资源。 152 * 153 * @param c 将关闭的资源 154 */ 155 private final void closeIO(Closeable c) { 156 if (c != null) { 157 try { 158 c.close(); 159 } catch (IOException e) { 160 e.printStackTrace(); 161 } 162 } 163 } 164 165 public static void main(String[] args) { 166 try { 167 String demo = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\150211100441.apk"; 168 ApkInfo apkInfo = new ApkUtil().getApkInfo(demo); 169 System.out.println(apkInfo); 170 } catch (Exception e) { 171 e.printStackTrace(); 172 } 173 } 174 175 public String getmAaptPath() { 176 return mAaptPath; 177 } 178 179 public void setmAaptPath(String mAaptPath) { 180 this.mAaptPath = mAaptPath; 181 } 182}
ApkUtil
如上所示,我做了个测试,我使用的是神庙逃亡的apk,然后解析到如下信息:

从上可以看到解析到的图标信息是以字符串的格式存在的,所以我们还需要一个工具类,通过解析这个字符串解压出icon图片并且存放到磁盘上

1/** 2 * 通过ApkInfo 里的applicationIcon从APK里解压出icon图片并存放到磁盘上 3 */ 4public class ApkIconUtil { 5 6 /** 7 * 从指定的apk文件里获取指定file的流 8 * 9 * @param apkPath 10 * @param fileName 11 * @return 12 */ 13 public static InputStream extractFileFromApk(String apkPath, String fileName) { 14 try { 15 ZipFile zFile = new ZipFile(apkPath); 16 ZipEntry entry = zFile.getEntry(fileName); 17 entry.getComment(); 18 entry.getCompressedSize(); 19 entry.getCrc(); 20 entry.isDirectory(); 21 entry.getSize(); 22 entry.getMethod(); 23 InputStream stream = zFile.getInputStream(entry); 24 return stream; 25 } catch (IOException e) { 26 e.printStackTrace(); 27 } 28 return null; 29 } 30 31 /** 32 * 从指定的apk文件里解压出icon图片并存放到指定的磁盘上 33 * 34 * @param apkPath apk文件路径 35 * @param fileName apk的icon 36 * @param outputPath 指定的磁盘路径 37 * @throws Exception 38 */ 39 public static void extractFileFromApk(String apkPath, String fileName, String outputPath) throws Exception { 40 InputStream is = extractFileFromApk(apkPath, fileName); 41 42 File file = new File(outputPath); 43 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), 1024); 44 byte[] b = new byte[1024]; 45 BufferedInputStream bis = new BufferedInputStream(is, 1024); 46 while (bis.read(b) != -1) { 47 bos.write(b); 48 } 49 bos.flush(); 50 is.close(); 51 bis.close(); 52 bos.close(); 53 } 54 55 /** 56 * demo 获取apk文件的icon并写入磁盘指定位置 57 * 58 * @param args 59 */ 60 public static void main(String[] args) { 61 try { 62 String apkPath = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\shenmiaotaowang_966.apk"; 63 /* if (args.length > 0) { 64 apkPath = args[0]; 65 }*/ 66 ApkInfo apkInfo = new ApkUtil().getApkInfo(apkPath); 67 System.out.println(apkInfo); 68 long now = System.currentTimeMillis(); 69 extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), "D:\\image\\apkIcon" + now + ".png"); 70 } catch (Exception e) { 71 e.printStackTrace(); 72 } 73 } 74 75}
ApkIconUtil
经测试,此方案可行,所以如果不需要解析图标的话,ApkUtil就够用了;如果需要解析图标到本地的话,就需要用ApkIconUtil。
这是我解析到的图标
![]()
apk的解析到这里就可以告一段落了!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3 解析ipa
解析ipa基本属性需要用到dd-plist-1.16.jar,有了这个jar包,我们解析ipa基本属性就相对来说简单一点了,可以通过NSDictionary根据相应的字段就可以解析到一些基本信息,比如包名之类的;
而对于icon的解析,首先我们先来看一下plist的结构:

我们可以层层递进来解析图标,先解析CFBundleIcons,然后CFBundlePrimaryIcon,再CFBundleIconFiles。
3.1 ipa工具类
完整代码如下:

1/** 2 * 解析ipa的工具类 3 * 包名/版本名/版本号/应用名称/应用展示名称/所需IOS最低版本 4 */ 5public class IpaUtil { 6 /** 7 * 8 * @param ipaURL 安装包的绝对路径 9 * @param path 指定图标的存放位置 10 * @return 11 */ 12 public static Map<String, Object> readIPA(String ipaURL,String path) { 13 Map<String, Object> map = new HashMap<String, Object>(); 14 try { 15 File file = new File(ipaURL); 16 InputStream is = new FileInputStream(file); 17 InputStream is2 = new FileInputStream(file); 18 19 ZipInputStream zipIns = new ZipInputStream(is); 20 ZipInputStream zipIns2 = new ZipInputStream(is2); 21 ZipEntry ze; 22 ZipEntry ze2; 23 InputStream infoIs = null; 24 NSDictionary rootDict = null; 25 String icon = null; 26 while ((ze = zipIns.getNextEntry()) != null) { 27 if (!ze.isDirectory()) { 28 String name = ze.getName(); 29 if (null != name && name.toLowerCase().contains("info.plist")) { 30 ByteArrayOutputStream _copy = new ByteArrayOutputStream(); 31 int chunk = 0; 32 byte[] data = new byte[1024]; 33 while (-1 != (chunk = zipIns.read(data))) { 34 _copy.write(data, 0, chunk); 35 } 36 infoIs = new ByteArrayInputStream(_copy.toByteArray()); 37 rootDict = (NSDictionary) PropertyListParser.parse(infoIs); 38 39 NSDictionary iconDict = (NSDictionary) rootDict.get("CFBundleIcons"); 40 41 //获取图标名称 42 while (null != iconDict) { 43 if (iconDict.containsKey("CFBundlePrimaryIcon")) { 44 NSDictionary CFBundlePrimaryIcon = (NSDictionary) iconDict.get("CFBundlePrimaryIcon"); 45 if (CFBundlePrimaryIcon.containsKey("CFBundleIconFiles")) { 46 NSArray CFBundleIconFiles = (NSArray) CFBundlePrimaryIcon.get("CFBundleIconFiles"); 47 icon = CFBundleIconFiles.getArray()[0].toString(); 48 if (icon.contains(".png")) { 49 icon = icon.replace(".png", ""); 50 } 51 System.out.println("获取icon名称:" + icon); 52 break; 53 } 54 } 55 } 56 break; 57 } 58 } 59 } 60 61 //根据图标名称下载图标文件到指定位置 62 while ((ze2 = zipIns2.getNextEntry()) != null) { 63 if (!ze2.isDirectory()) { 64 String name = ze2.getName(); 65 if (icon!=null){ 66 if (name.contains(icon.trim())) { 67 //图片下载到指定的地方 68 FileOutputStream fos = new FileOutputStream(new File(path)); 69 int chunk = 0; 70 byte[] data = new byte[1024]; 71 while (-1 != (chunk = zipIns2.read(data))) { 72 fos.write(data, 0, chunk); 73 } 74 fos.close(); 75 System.out.println("=================下载图片成功"); 76 break; 77 } 78 } 79 } 80 } 81 82 83 //如果想要查看有哪些key ,可以把下面注释放开 84// for (String string : dictionary.allKeys()) { 85// System.out.println(string + ":" + dictionary.get(string).toString()); 86// } 87 88 // 应用包名 89 NSString parameters = (NSString) rootDict.get("CFBundleIdentifier"); 90 map.put("package", parameters.toString()); 91 // 应用版本名 92 parameters = (NSString) rootDict.objectForKey("CFBundleShortVersionString"); 93 map.put("versionName", parameters.toString()); 94 //应用版本号 95 parameters = (NSString) rootDict.get("CFBundleVersion"); 96 map.put("versionCode", parameters.toString()); 97 //应用名称 98 parameters = (NSString) rootDict.objectForKey("CFBundleName"); 99 map.put("name", parameters.toString()); 100 //应用展示的名称 101 parameters = (NSString) rootDict.objectForKey("CFBundleDisplayName"); 102 map.put("displayName", parameters.toString()); 103 //应用所需IOS最低版本 104 //parameters = (NSString) rootDict.objectForKey("MinimumOSVersion"); 105 //map.put("minIOSVersion", parameters.toString()); 106 107 infoIs.close(); 108 is.close(); 109 zipIns.close(); 110 111 } catch (Exception e) { 112 e.printStackTrace(); 113 map.put("code", "fail"); 114 map.put("error", "读取ipa文件失败"); 115 } 116 return map; 117 } 118 119 public static void main(String[] args) { 120 String ipaUrl = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\com.baosight.securityCloudHD.ipa"; 121 String imgPath = "D:\\image\\ipaIcon" + System.currentTimeMillis() + ".png"; 122 Map<String, Object> mapIpa = IpaUtil.readIPA(ipaUrl,imgPath); 123 for (String key : mapIpa.keySet()) { 124 System.out.println(key + ":" + mapIpa.get(key)); 125 } 126 } 127}
IpaUtil
经测试,可以得到如下信息:

但是这时候,因为我使用的是windows环境,下载的图标就出现问题了(linux环境也有此问题,在mac环境下无此问题),可能是IOS对ipa进行压缩的原因,图标是黑色的。
如下所示:

网上找了很久,发现用ipin.py(python脚本)对图片进行反序列化就可以恢复正常图片,而且这个办法也很简单。
3.2 安装python环境
首先下载python http://www.runoob.com/python/python-install.html
建议下载python2.x系列的,版本太高的话执行ipin.py可能会出问题,我下载的是python2.6。
下载好之后,解压目录,然后在环境变量上配置python的根目录。

3.3 ipin.py展示
这个是我直接在网上下载的,并且做出了一点修改:

1#--- 2# iPIN - iPhone PNG Images Normalizer v1.0 3# Copyright (C) 2007 4# 5# Author: 6# Axel E. Brzostowski 7# http://www.axelbrz.com.ar/ 8# axelbrz@gmail.com 9# 10# References: 11# http://iphone.fiveforty.net/wiki/index.php/PNG_Images 12# http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html 13# 14# This program is free software: you can redistribute it and/or modify 15# it under the terms of the GNU General Public License as published by 16# the Free Software Foundation, either version 3 of the License. 17# 18# This program is distributed in the hope that it will be useful, 19# but WITHOUT ANY WARRANTY; without even the implied warranty of 20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21# GNU General Public License for more details. 22# 23#--- 24from struct import * 25from zlib import * 26import stat 27import sys 28import os 29import shutil 30import glob 31def getNormalizedPNG(filename): 32pngheader = "\x89PNG\r\n\x1a\n" 33 34file = open(filename, "rb") 35oldPNG = file.read() 36file.close() 37if oldPNG[:8] != pngheader: 38return None 39 40newPNG = oldPNG[:8] 41 42chunkPos = len(newPNG) 43 44idatAcc = "" 45breakLoop = False 46 47# For each chunk in the PNG file 48while chunkPos < len(oldPNG): 49skip = False 50 51# Reading chunk 52chunkLength = oldPNG[chunkPos:chunkPos+4] 53chunkLength = unpack(">L", chunkLength)[0] 54chunkType = oldPNG[chunkPos+4 : chunkPos+8] 55chunkData = oldPNG[chunkPos+8:chunkPos+8+chunkLength] 56chunkCRC = oldPNG[chunkPos+chunkLength+8:chunkPos+chunkLength+12] 57chunkCRC = unpack(">L", chunkCRC)[0] 58chunkPos += chunkLength + 12 59# Parsing the header chunk 60if chunkType == "IHDR": 61width = unpack(">L", chunkData[0:4])[0] 62height = unpack(">L", chunkData[4:8])[0] 63# Parsing the image chunk 64if chunkType == "IDAT": 65# Store the chunk data for later decompression 66idatAcc += chunkData 67skip = True 68# Removing CgBI chunk 69if chunkType == "CgBI": 70skip = True 71# Add all accumulated IDATA chunks 72if chunkType == "IEND": 73try: 74# Uncompressing the image chunk 75bufSize = width * height * 4 + height 76chunkData = decompress( idatAcc, -15, bufSize) 77 78except Exception, e: 79# The PNG image is normalized 80print e 81return None 82chunkType = "IDAT" 83# Swapping red & blue bytes for each pixel 84newdata = "" 85for y in xrange(height): 86i = len(newdata) 87newdata += chunkData[i] 88for x in xrange(width): 89i = len(newdata) 90newdata += chunkData[i+2] 91newdata += chunkData[i+1] 92newdata += chunkData[i+0] 93newdata += chunkData[i+3] 94# Compressing the image chunk 95chunkData = newdata 96chunkData = compress( chunkData ) 97chunkLength = len( chunkData ) 98chunkCRC = crc32(chunkType) 99chunkCRC = crc32(chunkData, chunkCRC) 100chunkCRC = (chunkCRC + 0x100000000) % 0x100000000 101breakLoop = True 102if not skip: 103newPNG += pack(">L", chunkLength) 104newPNG += chunkType 105if chunkLength > 0: 106newPNG += chunkData 107newPNG += pack(">L", chunkCRC) 108if breakLoop: 109break 110 111return newPNG 112def updatePNG(filename): 113data = getNormalizedPNG(filename) 114if data != None: 115file = open(filename, "wb") 116file.write(data) 117file.close() 118return True 119return data 120def getFiles(base): 121global _dirs 122global _pngs 123if base == ".": 124_dirs = [] 125_pngs = [] 126 127if base in _dirs: 128return 129files = os.listdir(base) 130for file in files: 131filepath = os.path.join(base, file) 132try: 133st = os.lstat(filepath) 134except os.error: 135continue 136 137if stat.S_ISDIR(st.st_mode): 138if not filepath in _dirs: 139getFiles(filepath) 140_dirs.append( filepath ) 141 142elif file[-4:].lower() == ".png": 143if not filepath in _pngs: 144_pngs.append( filepath ) 145 146if base == ".": 147return _dirs, _pngs 148print "-----------------------------------" 149print " iPhone PNG Images Normalizer v1.0" 150print "-----------------------------------" 151print " " 152print "[+] Searching PNG files...", 153dirs, pngs = getFiles(".") 154print "ok" 155if len(pngs) == 0: 156print " " 157print "[!] Alert: There are no PNG files found. Move this python file to the folder that contains the PNG files to normalize." 158exit() 159 160print " " 161print " - %d PNG files were found at this folder (and subfolders)." % len(pngs) 162print " " 163while True: 164normalize = "y" 165if len(normalize) > 0 and (normalize[0] == "y" or normalize[0] == "n"): 166break 167normalized = 0 168if normalize[0] == "y": 169for ipng in xrange(len(pngs)): 170perc = (float(ipng) / len(pngs)) * 100.0 171print "%.2f%% %s" % (perc, pngs[ipng]) 172if updatePNG(pngs[ipng]): 173normalized += 1 174print " " 175print "[+] %d PNG files were normalized." % normalized 176for filename in glob.glob(r'/F:/image/*.png'): 177shutil.move(filename,"/F:/image2") 178print filename
ipin.py
最后三行代码是自己加上的,意思是把反序列的正常图片全部移到另一个文件夹里,这样做的好处是如果我们上传解析的很多ipa文件,那么也会生成很多图标,如果都放在反序列化的文件夹,而不移走的话,那么,程序每次都会去查找有哪些图片需要反序列化,这些都是耗时的;还有一个好处就是及时将反序列好的图片移到另一个文件夹,防止再次反序列化损坏图片。
3.4 反序列化图标
将ipin.fy文件放在image目录下,并且没有将ipa图标进行反序列化是这样的:

点shift+右键,然后点击在此处打开窗口,在打开的命令窗口上输入python ipin.py命令


然后就可以将上面黑色的图标进行反序列化,得到下面这样的:

但是,这边得注意的是:
1.windows环境下如果得到的ipa图标是黑色的,输入一次命令后,会得到正常的,这时候不能再输入上述的命令了,不然会损坏文件!!!
2.mac环境下不需要进行这些操作
ipa的解析到这里也可以告一段落了。
4.工具类的调用方法
4.1 apk工具类的调用
-
首先传入一个apkPath(apk安装包的绝对路径:可以存放在项目根目录,然后通过这个相对路径获取绝对路径,再加上安装包名即可),这边分享一个处理上传文件的工具类UploadUtil

1public class UploadUtil { 2 /** 3 * 处理文件上传 4 * 5 * @param file 6 * @param basePath 7 * 存放文件的目录的绝对路径 servletContext.getRealPath("/upload") 8 * @return 9 */ 10 public static String upload(MultipartFile file, String basePath) { 11 String orgFileName = file.getOriginalFilename(); 12 String fileName = System.currentTimeMillis() + "." + FilenameUtils.getExtension(orgFileName); 13 try { 14 File targetFile = new File(basePath, fileName); 15 FileUtils.writeByteArrayToFile(targetFile, file.getBytes()); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 return fileName; 20 } 21}UploadUtil
-
然后通过这个apkPath调用ApkUtil的getApkInfo方法就可以得到apkInfo,这个apkInfo里面有包名,版本名,版本号,图标等信息
-
最后调用ApkIconUtil的extractFileFromApk方法就可以将apk的图标存放到指定的位置
-
伪代码示例:

1@Autowired 2private ServletContext context; 3 4public void do(MultipartFile file){ 5 //根据项目根路径得到这个根路径的绝对路径 6 String absolutePath = context.getRealPath(""); 7 //项目根路径的绝对路径+安装包名就是这个安装包名的绝对路径 8 String apkPath = absolutePath + "/" + UploadUtil.upload(file , absolutePath); 9 String imaPath = System.currentTimeMillis() + ".png"; 10 //得到apkInfo 11 ApkInfo apkInfo = new ApkUtil.getApkInfo(apkPath); 12 //将解析出来的图标存放到项目根路径下 13 ApkIconUtil.extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), absolutePath + "/" + imaPath); 14 //包名、版本名、版本号、应用名称 15 String packageName = apkInfo.getPackageName(); 16 String versionName = apkInfo.getVersionName(); 17 String versionCode = apkInfo.getVersionCode(); 18 String displayName = apkInfo.getApplicationLable(); 19}View Code
4.2 ipa工具类的调用
-
调用IpaUtil的readIpa方法,这个方法有两个参数,第一个参数是安装包的绝对路径,第二个参数是指定解析出来的图标存放位置,得到的是一个map
-
然后就可以从这个map中取出包名、版本名、版本号、应用名称
-
伪代码示例

1//接着上面伪代码使用的do方法来写,实际上可以根据apkPath包含".apk"或".ipa"来判断是apk或者ipa 2public void do(MultipartFile file){ 3 Map<String, Object> map = IpaUtil.readIPA(apkPath, absolutePath + "/" + imaPath); 4 String packageName = (String) map.get("package"); 5 String versionName = (String) map.get("versionName"); 6 String versionCode = (String) map.get("versionCode"); 7 String displayName = (String) map.get("displayName"); 8}View Code