Java读取apk、ipa包名、版本名、版本号、图标等信息

当我们上传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的包名、版本号、版本名、所需设备特性等。

代码如下:

/**
 * apk实体信息
 */
public class ApkInfo {
 public static final String APPLICATION_ICON_120 = "application-icon-120";
 public static final String APPLICATION_ICON_160 = "application-icon-160";
 public static final String APPLICATION_ICON_240 = "application-icon-240";
 public static final String APPLICATION_ICON_320 = "application-icon-320";
 /**
 * apk内部版本号
 */
 private String versionCode = null;
 /**
 * apk外部版本号
 */
 private String versionName = null;
 /**
 * apk的包名
 */
 private String packageName = null;
 /**
 * 支持的android平台最低版本号
 */
 private String minSdkVersion = null;
 /**
 * apk所需要的权限
 */
 private List<String> usesPermissions = null;
 /**
 * 支持的SDK版本。
 */
 private String sdkVersion;
 /**
 * 建议的SDK版本
 */
 private String targetSdkVersion;
 /**
 * 应用程序名
 */
 private String applicationLable;
 /**
 * 各个分辨率下的图标的路径。
 */
 private Map<String, String> applicationIcons;
 /**
 * 程序的图标。
 */
 private String applicationIcon;
 /**
 * 暗指的特性。
 */
 private List<ImpliedFeature> impliedFeatures;
 /**
 * 所需设备特性。
 */
 private List<String> features;
 /**
 * 启动界面
 */
 private String launchableActivity;

 public ApkInfo() {
 this.usesPermissions = new ArrayList<String>();
 this.applicationIcons = new HashMap<String, String>();
 this.impliedFeatures = new ArrayList<ImpliedFeature>();
 this.features = new ArrayList<String>();
 }

 /**
 * 返回版本代码。
 * 
 * @return 版本代码。
 */
 public String getVersionCode() {
      return versionCode;
 }

 /**
 * @param versionCode
 * the versionCode to set
 */
 public void setVersionCode(String versionCode) {
      this.versionCode = versionCode;
 }

 /**
 * 返回版本名称。
 * 
 * @return 版本名称。
 */
 public String getVersionName() {
      return versionName;
 }

 /**
 * @param versionName
 * the versionName to set
 */
 public void setVersionName(String versionName) {
      this.versionName = versionName;
 }

 /**
 * 返回支持的最小sdk平台版本。
 * 
 * @return the minSdkVersion
 */
 public String getMinSdkVersion() {
      return minSdkVersion;
 }

 /**
 * @param minSdkVersion
 * the minSdkVersion to set
 */
 public void setMinSdkVersion(String minSdkVersion) {
      this.minSdkVersion = minSdkVersion;
 }

 /**
 * 返回包名。
 * 
 * @return 返回的包名。
 */
 public String getPackageName() {
      return packageName;
 }

 public void setPackageName(String packageName) {
      this.packageName = packageName;
 }

 /**
 * 返回sdk平台版本。
 * 
 * @return
 */
 public String getSdkVersion() {
      return sdkVersion;
 }

 public void setSdkVersion(String sdkVersion) {
      this.sdkVersion = sdkVersion;
 }

 /**
 * 返回所建议的SDK版本。
 * 
 * @return
 */
 public String getTargetSdkVersion() {
      return targetSdkVersion;
 }

 public void setTargetSdkVersion(String targetSdkVersion) {
      this.targetSdkVersion = targetSdkVersion;
 }

 /**
 * 返回所需的用户权限。
 * 
 * @return
 */
 public List<String> getUsesPermissions() {
      return usesPermissions;
 }

 public void setUsesPermissions(List<String> usesPermission) {
      this.usesPermissions = usesPermission;
 }

 public void addToUsesPermissions(String usesPermission) {
      this.usesPermissions.add(usesPermission);
 }

 /**
 * 返回程序的名称标签。
 * 
 * @return
 */
 public String getApplicationLable() {
      return applicationLable;
 }

 public void setApplicationLable(String applicationLable) {
      this.applicationLable = applicationLable;
 }

 /**
 * 返回应用程序的图标。
 * 
 * @return
 */
 public String getApplicationIcon() {
      return applicationIcon;
 }

 public void setApplicationIcon(String applicationIcon) {
      this.applicationIcon = applicationIcon;
 }

 /**
 * 返回应用程序各个分辨率下的图标。
 * 
 * @return
 */
 public Map<String, String> getApplicationIcons() {
      return applicationIcons;
 }

 public void setApplicationIcons(Map<String, String> applicationIcons) {
      this.applicationIcons = applicationIcons;
 }

 public void addToApplicationIcons(String key, String value) {
      this.applicationIcons.put(key, value);
 }

 public void addToImpliedFeatures(ImpliedFeature impliedFeature) {
      this.impliedFeatures.add(impliedFeature);
 }

 /**
 * 返回应用程序所需的暗指的特性。
 * 
 * @return
 */
 public List<ImpliedFeature> getImpliedFeatures() {
      return impliedFeatures;
 }

 public void setImpliedFeatures(List<ImpliedFeature> impliedFeatures) {
      this.impliedFeatures = impliedFeatures;
 }

 /**
 * 返回应用程序所需的特性。
 * 
 * @return
 */
 public List<String> getFeatures() {
      return features;
 }

 public void setFeatures(List<String> features) {
      this.features = features;
 }

 public void addToFeatures(String feature) {
      this.features.add(feature);
 }

 @Override
 public String toString() {
      return "ApkInfo [versionCode=" + versionCode + ",\n versionName="
 + versionName + ",\n packageName=" + packageName
 + ",\n minSdkVersion=" + minSdkVersion + ",\n usesPermissions="
 + usesPermissions + ",\n sdkVersion=" + sdkVersion
 + ",\n targetSdkVersion=" + targetSdkVersion
 + ",\n applicationLable=" + applicationLable
 + ",\n applicationIcons=" + applicationIcons
 + ",\n applicationIcon=" + applicationIcon
 + ",\n impliedFeatures=" + impliedFeatures + ",\n features="
 + features + ",\n launchableActivity=" + launchableActivity + "\n]";
 }

 public String getLaunchableActivity() {
      return launchableActivity;
 }

 public void setLaunchableActivity(String launchableActivity) {
      this.launchableActivity = launchableActivity;
 }
}
ApkInfo
/**
 * 特性实体
 */
public class ImpliedFeature {

   /**
 * 所需设备特性名称。
 */
 private String feature;

 /**
 * 表明所需特性的内容。
 */
 private String implied;

 public ImpliedFeature() {
      super();
 }

 public ImpliedFeature(String feature, String implied) {
      super();
      this.feature = feature;
      this.implied = implied;
 }

 public String getFeature() {
      return feature;
 }

 public void setFeature(String feature) {
      this.feature = feature;
 }

 public String getImplied() {
      return implied;
 }

 public void setImplied(String implied) {
      this.implied = implied;
 }

 @Override
 public String toString() {
      return "Feature [feature=" + feature + ", implied=" + implied + "]";
 }
}
ImpliedFeature

2.3 apk的工具类

代码如下:

/**
 * apk工具类。封装了获取Apk信息的方法。
 * 包名/版本号/版本名/应用程序名/支持的android最低版本号/支持的SDK版本/建议的SDK版本/所需设备特性等
 */
public class ApkUtil {
    public static final String VERSION_CODE = "versionCode";
    public static final String VERSION_NAME = "versionName";
    public static final String SDK_VERSION = "sdkVersion";
    public static final String TARGET_SDK_VERSION = "targetSdkVersion";
    public static final String USES_PERMISSION = "uses-permission";
    public static final String APPLICATION_LABEL = "application-label";
    public static final String APPLICATION_ICON = "application-icon";
    public static final String USES_FEATURE = "uses-feature";
    public static final String USES_IMPLIED_FEATURE = "uses-implied-feature";
    public static final String SUPPORTS_SCREENS = "supports-screens";
    public static final String SUPPORTS_ANY_DENSITY = "supports-any-density";
    public static final String DENSITIES = "densities";
    public static final String PACKAGE = "package";
    public static final String APPLICATION = "application:";
    public static final String LAUNCHABLE_ACTIVITY = "launchable-activity";

    private ProcessBuilder mBuilder;
    private static final String SPLIT_REGEX = "(: )|(=')|(' )|'";
    private static final String FEATURE_SPLIT_REGEX = "(:')|(',')|'";
    /**
     * aapt所在的目录。
     */
    //windows环境下直接指向appt.exe
    //比如你可以放在src下
    private String mAaptPath = "src/main/java/aapt";
    //linux下
    //private String mAaptPath = "/usr/local/apktool/aapt";

    public ApkUtil() {
        mBuilder = new ProcessBuilder();
        mBuilder.redirectErrorStream(true);
    }

    /**
     * 返回一个apk程序的信息。
     *
     * @param apkPath apk的路径。
     * @return apkInfo 一个Apk的信息。
     */
    public ApkInfo getApkInfo(String apkPath) throws Exception {
        System.out.println("================================开始执行命令=========================");
        //通过命令调用aapt工具解析apk文件
        Process process = mBuilder.command(mAaptPath, "d", "badging", apkPath)
                .start();
        InputStream is = null;
        is = process.getInputStream();
        BufferedReader br = new BufferedReader(
                new InputStreamReader(is, "utf8"));
        String tmp = br.readLine();
        try {
            if (tmp == null || !tmp.startsWith("package")) {
                throw new Exception("参数不正确,无法正常解析APK包。输出结果为:\n" + tmp + "...");
            }
            ApkInfo apkInfo = new ApkInfo();
            do {
                setApkInfoProperty(apkInfo, tmp);
            } while ((tmp = br.readLine()) != null);
            return apkInfo;
        } catch (Exception e) {
            throw e;
        } finally {
            process.destroy();
            closeIO(is);
            closeIO(br);
        }
    }

    /**
     * 设置APK的属性信息。
     *
     * @param apkInfo
     * @param source
     */
    private void setApkInfoProperty(ApkInfo apkInfo, String source) {
        if (source.startsWith(PACKAGE)) {
            splitPackageInfo(apkInfo, source);
        } else if (source.startsWith(LAUNCHABLE_ACTIVITY)) {
            apkInfo.setLaunchableActivity(getPropertyInQuote(source));
        } else if (source.startsWith(SDK_VERSION)) {
            apkInfo.setSdkVersion(getPropertyInQuote(source));
        } else if (source.startsWith(TARGET_SDK_VERSION)) {
            apkInfo.setTargetSdkVersion(getPropertyInQuote(source));
        } else if (source.startsWith(USES_PERMISSION)) {
            apkInfo.addToUsesPermissions(getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION_LABEL)) {
            //windows下获取应用名称
            apkInfo.setApplicationLable(getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION_ICON)) {
            apkInfo.addToApplicationIcons(getKeyBeforeColon(source),
                    getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION)) {
            String[] rs = source.split("( icon=')|'");
            apkInfo.setApplicationIcon(rs[rs.length - 1]);
            //linux下获取应用名称
            apkInfo.setApplicationLable(rs[1]);
        } else if (source.startsWith(USES_FEATURE)) {
            apkInfo.addToFeatures(getPropertyInQuote(source));
        } else if (source.startsWith(USES_IMPLIED_FEATURE)) {
            apkInfo.addToImpliedFeatures(getFeature(source));
        } else {
//       System.out.println(source);
        }
    }

    private ImpliedFeature getFeature(String source) {
        String[] result = source.split(FEATURE_SPLIT_REGEX);
        ImpliedFeature impliedFeature = new ImpliedFeature(result[1], result[2]);
        return impliedFeature;
    }

    /**
     * 返回出格式为name: 'value'中的value内容。
     *
     * @param source
     * @return
     */
    private String getPropertyInQuote(String source) {
        int index = source.indexOf("'") + 1;
        return source.substring(index, source.indexOf('\'', index));
    }

    /**
     * 返回冒号前的属性名称
     *
     * @param source
     * @return
     */
    private String getKeyBeforeColon(String source) {
        return source.substring(0, source.indexOf(':'));
    }

    /**
     * 分离出包名、版本等信息。
     *
     * @param apkInfo
     * @param packageSource
     */
    private void splitPackageInfo(ApkInfo apkInfo, String packageSource) {
        String[] packageInfo = packageSource.split(SPLIT_REGEX);
        apkInfo.setPackageName(packageInfo[2]);
        apkInfo.setVersionCode(packageInfo[4]);
        apkInfo.setVersionName(packageInfo[6]);
    }

    /**
     * 释放资源。
     *
     * @param c 将关闭的资源
     */
    private final void closeIO(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try {
            String demo = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\150211100441.apk";
            ApkInfo apkInfo = new ApkUtil().getApkInfo(demo);
            System.out.println(apkInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getmAaptPath() {
        return mAaptPath;
    }

    public void setmAaptPath(String mAaptPath) {
        this.mAaptPath = mAaptPath;
    }
}
ApkUtil

如上所示,我做了个测试,我使用的是神庙逃亡的apk,然后解析到如下信息:

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

/**
 * 通过ApkInfo 里的applicationIcon从APK里解压出icon图片并存放到磁盘上
 */
public class ApkIconUtil {

    /**
     * 从指定的apk文件里获取指定file的流
     *
     * @param apkPath
     * @param fileName
     * @return
     */
    public static InputStream extractFileFromApk(String apkPath, String fileName) {
        try {
            ZipFile zFile = new ZipFile(apkPath);
            ZipEntry entry = zFile.getEntry(fileName);
            entry.getComment();
            entry.getCompressedSize();
            entry.getCrc();
            entry.isDirectory();
            entry.getSize();
            entry.getMethod();
            InputStream stream = zFile.getInputStream(entry);
            return stream;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从指定的apk文件里解压出icon图片并存放到指定的磁盘上
     *
     * @param apkPath    apk文件路径
     * @param fileName   apk的icon
     * @param outputPath 指定的磁盘路径
     * @throws Exception
     */
    public static void extractFileFromApk(String apkPath, String fileName, String outputPath) throws Exception {
        InputStream is = extractFileFromApk(apkPath, fileName);

        File file = new File(outputPath);
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), 1024);
        byte[] b = new byte[1024];
        BufferedInputStream bis = new BufferedInputStream(is, 1024);
        while (bis.read(b) != -1) {
            bos.write(b);
        }
        bos.flush();
        is.close();
        bis.close();
        bos.close();
    }

    /**
     * demo 获取apk文件的icon并写入磁盘指定位置
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            String apkPath = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\shenmiaotaowang_966.apk";
           /* if (args.length > 0) {
                apkPath = args[0];
            }*/
            ApkInfo apkInfo = new ApkUtil().getApkInfo(apkPath);
            System.out.println(apkInfo);
            long now = System.currentTimeMillis();
            extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), "D:\\image\\apkIcon" + now + ".png");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
ApkIconUtil


经测试,此方案可行,所以如果不需要解析图标的话,ApkUtil就够用了;如果需要解析图标到本地的话,就需要用ApkIconUtil。

这是我解析到的图标

apk的解析到这里就可以告一段落了!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3 解析ipa

解析ipa基本属性需要用到dd-plist-1.16.jar,有了这个jar包,我们解析ipa基本属性就相对来说简单一点了,可以通过NSDictionary根据相应的字段就可以解析到一些基本信息,比如包名之类的;

而对于icon的解析,首先我们先来看一下plist的结构:

我们可以层层递进来解析图标,先解析CFBundleIcons,然后CFBundlePrimaryIcon,再CFBundleIconFiles。

3.1 ipa工具类

完整代码如下:

/**
 * 解析ipa的工具类
 * 包名/版本名/版本号/应用名称/应用展示名称/所需IOS最低版本
 */
public class IpaUtil {
    /**
     *
     * @param ipaURL 安装包的绝对路径
     * @param path 指定图标的存放位置
     * @return
     */
    public static Map<String, Object> readIPA(String ipaURL,String path) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            File file = new File(ipaURL);
            InputStream is = new FileInputStream(file);
            InputStream is2 = new FileInputStream(file);

            ZipInputStream zipIns = new ZipInputStream(is);
            ZipInputStream zipIns2 = new ZipInputStream(is2);
            ZipEntry ze;
            ZipEntry ze2;
            InputStream infoIs = null;
            NSDictionary rootDict = null;
            String icon = null;
            while ((ze = zipIns.getNextEntry()) != null) {
                if (!ze.isDirectory()) {
                    String name = ze.getName();
                    if (null != name && name.toLowerCase().contains("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());
                        rootDict = (NSDictionary) PropertyListParser.parse(infoIs);

                        NSDictionary iconDict = (NSDictionary) rootDict.get("CFBundleIcons");

                        //获取图标名称
                        while (null != iconDict) {
                            if (iconDict.containsKey("CFBundlePrimaryIcon")) {
                                NSDictionary CFBundlePrimaryIcon = (NSDictionary) iconDict.get("CFBundlePrimaryIcon");
                                if (CFBundlePrimaryIcon.containsKey("CFBundleIconFiles")) {
                                    NSArray CFBundleIconFiles = (NSArray) CFBundlePrimaryIcon.get("CFBundleIconFiles");
                                    icon = CFBundleIconFiles.getArray()[0].toString();
                                    if (icon.contains(".png")) {
                                        icon = icon.replace(".png", "");
                                    }
                                    System.out.println("获取icon名称:" + icon);
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
            }

            //根据图标名称下载图标文件到指定位置
            while ((ze2 = zipIns2.getNextEntry()) != null) {
                if (!ze2.isDirectory()) {
                    String name = ze2.getName();
                    if (icon!=null){
                        if (name.contains(icon.trim())) {
                            //图片下载到指定的地方
                            FileOutputStream fos = new FileOutputStream(new File(path));
                            int chunk = 0;
                            byte[] data = new byte[1024];
                            while (-1 != (chunk = zipIns2.read(data))) {
                                fos.write(data, 0, chunk);
                            }
                            fos.close();
                            System.out.println("=================下载图片成功");
                            break;
                        }
                    }
                }
            }


            //如果想要查看有哪些key ,可以把下面注释放开
//       for (String string : dictionary.allKeys()) {
//          System.out.println(string + ":" + dictionary.get(string).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());
            //应用名称
            parameters = (NSString) rootDict.objectForKey("CFBundleName");
            map.put("name", parameters.toString());
            //应用展示的名称
            parameters = (NSString) rootDict.objectForKey("CFBundleDisplayName");
            map.put("displayName", parameters.toString());
            //应用所需IOS最低版本
            //parameters = (NSString) rootDict.objectForKey("MinimumOSVersion");
            //map.put("minIOSVersion", parameters.toString());

            infoIs.close();
            is.close();
            zipIns.close();

        } catch (Exception e) {
            e.printStackTrace();
            map.put("code", "fail");
            map.put("error", "读取ipa文件失败");
        }
        return map;
    }

    public static void main(String[] args) {
        String ipaUrl = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\com.baosight.securityCloudHD.ipa";
        String imgPath = "D:\\image\\ipaIcon" + System.currentTimeMillis() + ".png";
        Map<String, Object> mapIpa = IpaUtil.readIPA(ipaUrl,imgPath);
        for (String key : mapIpa.keySet()) {
            System.out.println(key + ":" + mapIpa.get(key));
        }
    }
}
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展示

这个是我直接在网上下载的,并且做出了一点修改:

#---
# iPIN - iPhone PNG Images Normalizer v1.0
# Copyright (C) 2007
#
# Author:
# Axel E. Brzostowski
# http://www.axelbrz.com.ar/
# axelbrz@gmail.com
# 
# References:
# http://iphone.fiveforty.net/wiki/index.php/PNG_Images
# http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# 
#---
from struct import *
from zlib import *
import stat
import sys
import os
import shutil
import glob
def getNormalizedPNG(filename):
pngheader = "\x89PNG\r\n\x1a\n"

file = open(filename, "rb")
oldPNG = file.read()
file.close()
if oldPNG[:8] != pngheader:
return None

newPNG = oldPNG[:8]

chunkPos = len(newPNG)

idatAcc = ""
breakLoop = False

# For each chunk in the PNG file 
while chunkPos < len(oldPNG):
skip = False

# Reading chunk
chunkLength = oldPNG[chunkPos:chunkPos+4]
chunkLength = unpack(">L", chunkLength)[0]
chunkType = oldPNG[chunkPos+4 : chunkPos+8]
chunkData = oldPNG[chunkPos+8:chunkPos+8+chunkLength]
chunkCRC = oldPNG[chunkPos+chunkLength+8:chunkPos+chunkLength+12]
chunkCRC = unpack(">L", chunkCRC)[0]
chunkPos += chunkLength + 12
# Parsing the header chunk
if chunkType == "IHDR":
width = unpack(">L", chunkData[0:4])[0]
height = unpack(">L", chunkData[4:8])[0]
# Parsing the image chunk
if chunkType == "IDAT":
# Store the chunk data for later decompression
idatAcc += chunkData
skip = True
# Removing CgBI chunk 
if chunkType == "CgBI":
skip = True
# Add all accumulated IDATA chunks
if chunkType == "IEND":
try:
# Uncompressing the image chunk
bufSize = width * height * 4 + height
chunkData = decompress( idatAcc, -15, bufSize)

except Exception, e:
# The PNG image is normalized
print e
return None
chunkType = "IDAT"
# Swapping red & blue bytes for each pixel
newdata = ""
for y in xrange(height):
i = len(newdata)
newdata += chunkData[i]
for x in xrange(width):
i = len(newdata)
newdata += chunkData[i+2]
newdata += chunkData[i+1]
newdata += chunkData[i+0]
newdata += chunkData[i+3]
# Compressing the image chunk
chunkData = newdata
chunkData = compress( chunkData )
chunkLength = len( chunkData )
chunkCRC = crc32(chunkType)
chunkCRC = crc32(chunkData, chunkCRC)
chunkCRC = (chunkCRC + 0x100000000) % 0x100000000
breakLoop = True
if not skip:
newPNG += pack(">L", chunkLength)
newPNG += chunkType
if chunkLength > 0:
newPNG += chunkData
newPNG += pack(">L", chunkCRC)
if breakLoop:
break

return newPNG
def updatePNG(filename):
data = getNormalizedPNG(filename)
if data != None:
file = open(filename, "wb")
file.write(data)
file.close()
return True
return data
def getFiles(base):
global _dirs
global _pngs
if base == ".":
_dirs = []
_pngs = []

if base in _dirs:
return
files = os.listdir(base)
for file in files:
filepath = os.path.join(base, file)
try:
st = os.lstat(filepath)
except os.error:
continue

if stat.S_ISDIR(st.st_mode):
if not filepath in _dirs:
getFiles(filepath)
_dirs.append( filepath )

elif file[-4:].lower() == ".png":
if not filepath in _pngs:
_pngs.append( filepath )

if base == ".":
return _dirs, _pngs
print "-----------------------------------"
print " iPhone PNG Images Normalizer v1.0"
print "-----------------------------------"
print " "
print "[+] Searching PNG files...",
dirs, pngs = getFiles(".")
print "ok"
if len(pngs) == 0:
print " "
print "[!] Alert: There are no PNG files found. Move this python file to the folder that contains the PNG files to normalize."
exit()

print " "
print " - %d PNG files were found at this folder (and subfolders)." % len(pngs)
print " "
while True:
normalize = "y"
if len(normalize) > 0 and (normalize[0] == "y" or normalize[0] == "n"):
break
normalized = 0
if normalize[0] == "y":
for ipng in xrange(len(pngs)):
perc = (float(ipng) / len(pngs)) * 100.0
print "%.2f%% %s" % (perc, pngs[ipng])
if updatePNG(pngs[ipng]):
normalized += 1
print " "
print "[+] %d PNG files were normalized." % normalized
for filename in glob.glob(r'/F:/image/*.png'):
shutil.move(filename,"/F:/image2")
print filename
ipin.py


最后三行代码是自己加上的,意思是把反序列的正常图片全部移到另一个文件夹里,这样做的好处是如果我们上传解析的很多ipa文件,那么也会生成很多图标,如果都放在反序列化的文件夹,而不移走的话,那么,程序每次都会去查找有哪些图片需要反序列化,这些都是耗时的;还有一个好处就是及时将反序列好的图片移到另一个文件夹,防止再次反序列化损坏图片。

3.4 反序列化图标

将ipin.fy文件放在image目录下,并且没有将ipa图标进行反序列化是这样的:

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

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

但是,这边得注意的是:

1.windows环境下如果得到的ipa图标是黑色的,输入一次命令后,会得到正常的,这时候不能再输入上述的命令了,不然会损坏文件!!!

2.mac环境下不需要进行这些操作

ipa的解析到这里也可以告一段落了。

4.工具类的调用方法

4.1 apk工具类的调用

  1. 首先传入一个apkPath(apk安装包的绝对路径:可以存放在项目根目录,然后通过这个相对路径获取绝对路径,再加上安装包名即可),这边分享一个处理上传文件的工具类UploadUtil

    public class UploadUtil {
       /**
        * 处理文件上传
        * 
        * @param file
        * @param basePath
        *            存放文件的目录的绝对路径 servletContext.getRealPath("/upload")
        * @return
        */
       public static String upload(MultipartFile file, String basePath) {
          String orgFileName = file.getOriginalFilename();
          String fileName = System.currentTimeMillis() + "." + FilenameUtils.getExtension(orgFileName);
          try {
             File targetFile = new File(basePath, fileName);
             FileUtils.writeByteArrayToFile(targetFile, file.getBytes());
          } catch (IOException e) {
             e.printStackTrace();
          }
          return fileName;
       }
    }
    UploadUtil
  2. 然后通过这个apkPath调用ApkUtil的getApkInfo方法就可以得到apkInfo,这个apkInfo里面有包名,版本名,版本号,图标等信息

  3. 最后调用ApkIconUtil的extractFileFromApk方法就可以将apk的图标存放到指定的位置
  4. 伪代码示例:

    @Autowired
    private ServletContext context;
     
    public void do(MultipartFile file){
        //根据项目根路径得到这个根路径的绝对路径
        String absolutePath = context.getRealPath("");
        //项目根路径的绝对路径+安装包名就是这个安装包名的绝对路径
        String apkPath = absolutePath + "/" + UploadUtil.upload(file , absolutePath);
        String imaPath = System.currentTimeMillis() + ".png";
        //得到apkInfo
        ApkInfo apkInfo = new ApkUtil.getApkInfo(apkPath);
        //将解析出来的图标存放到项目根路径下
        ApkIconUtil.extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), absolutePath + "/" + imaPath);
        //包名、版本名、版本号、应用名称
        String packageName = apkInfo.getPackageName();
        String versionName = apkInfo.getVersionName();
        String versionCode = apkInfo.getVersionCode();
        String displayName = apkInfo.getApplicationLable();
    }
    View Code

4.2 ipa工具类的调用

  1. 调用IpaUtil的readIpa方法,这个方法有两个参数,第一个参数是安装包的绝对路径,第二个参数是指定解析出来的图标存放位置,得到的是一个map
  2. 然后就可以从这个map中取出包名、版本名、版本号、应用名称
  3. 伪代码示例

    //接着上面伪代码使用的do方法来写,实际上可以根据apkPath包含".apk"或".ipa"来判断是apk或者ipa
    public void do(MultipartFile file){
        Map<String, Object> map = IpaUtil.readIPA(apkPath, absolutePath + "/" + imaPath);
        String packageName = (String) map.get("package");
        String versionName = (String) map.get("versionName");
        String versionCode = (String) map.get("versionCode");
        String displayName = (String) map.get("displayName");
    }
    View Code
posted @ 2018-05-03 10:34  丶Pursue  阅读(11910)  评论(2编辑  收藏  举报