Java解析apk、ipa图标,包名,应用名称,版本号

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/moyanxuan_1993_2_24/article/details/53612001

这篇文章主要针对apk、ipa解析图标,当然也会顺带解析其他一些基本信息,
比如:包名、版本号、版本名、应用名称。

之前google了好多文章,没有完整的可以解决图标的博文。今天我就为大家带来一篇。
首先给大家吃一颗定心丸:这篇文章绝对可以帮助大家解析出来图标的。只要你的apk和ipa文件不是
损坏,是正常的,绝对是百发百中,弹无虚发,屡试不爽。

如果,大家不是为了解析图标而来,那么就不需要看这篇文章了,
请移步到:
完整版java读取apk、ipa包名、版本名、版本号等信息 (附源码)

大家需要做的只是耐心看完这篇文章。

首先大家看一下效果图:
这里写图片描述


接下来步入正题。。。。。。

1、首先看一下用到什么工具

环境:linux

解析apk
(1). aapt工具

解析ipa
(1). python2.6

2、解析apk

对于apk的解析,我是一万个不想用到aapt这个工具,感觉太烦人,如果能通过只需要引用几个jar包,
用代码读取出来多好。

事实上,读取一般信息,是可以的,对于,apk的名称,是不好获取的。我们知道安卓名称都是
android:label=”@string/app_name” 这种方式,定义一个变量,然后就增加了我们获取的难度,
还有另一个难点就是对于图标的获取。我知道可以通过以下方式获取,前提是你要知道图标的名称。

//上面代码省略。。。。。。。
zipFile = new ZipFile(apkUrl);
            Enumeration<?> enumeration = zipFile.entries();
            ZipEntry zipEntry = null;
            while (enumeration.hasMoreElements()) {
                zipEntry = (ZipEntry) enumeration.nextElement();
                //我知道图片的图标名称就叫appicon_logo,所以可以这样获取
                if (zipEntry.getName().contains("appicon_logo")) {

                    int length = 0;
                    byte b[] = new byte [1024];
                    OutputStream outputStream = new FileOutputStream(
                            new File("E:\\python\\img\\apkicon.png"));
                    InputStream inputStream = zipFile.getInputStream(zipEntry); 
                    while (-1 != (length = inputStream.read(b))){
                       outputStream.write(b, 0, length);
                    }
                    outputStream.close();
                    break;
                }
            }
//下面代码省略。。。。。。

但是,一般我们要解析的,是我们不知道的apk,所以,我们就要获取图标的名称,然而,无论我怎么通过代码解析,就是获取不到,哎,不得已,只能用到aapt工具了。

##2.1. linux下安装aapt

请移步到这篇博客
linux 64位 安装aapt

window环境下,只需要下载aapt.exe就ok了

首先新建一个实体类

/**
 * @author ZSL
 * @since 2016年12月7日
 * @desc [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;
        }
    }

还需要一个实体

/**
 * @author ZSL
 * @since 2016年12月7日
 * @desc [特性实体]
 */
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 + "]";
    }
}

以上两个实体,大家不需要特别关心。

接下来一个解析apk的工具类

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
    //比如你可以放在lib下
    //private String mAaptPath = "lib/aapt";
    //下面是linux下
    private String mAaptPath = "/usr/local/python/img/aapt";

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

    /**
     * 返回一个apk程序的信息。
     * 
     * @param apkPath
     *            apk的路径。
     * @return apkInfo 一个Apk的信息。
     */
    public ApkInfo getApkInfo(String apkPath) throws Exception {

        //通过命令调用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 {
                System.out.println("==============apkInfo");
                System.out.println(tmp);
                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)) {
            //window下获取应用名称
            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 = "src/test.apk.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;
    }
}

下面这个是获取apk图标的工具类

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

    /**
     * 从指定的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;
    }

    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:\\DefaultApkTempSaveFolder\\3G安卓市场\\com.jiubang.market.apk";
            if (args.length > 0) {
                apkpath = args[0];
            }
            ApkInfo apkInfo = new ApkUtil().getApkInfo(apkpath);
            System.out.println(apkInfo);
            extractFileFromApk(apkpath, apkInfo.getApplicationIcon(), "D:\\DefaultApkTempSaveFolder\\3G安卓市场\\crawler\\icon.png");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

apk就解析结束!


3、解析ipa

##3.1. 解析图片

解析ipa基本属性需要用到dd-plist-1.16.jar

下载地址:dd-plist-1.16.jar
对于获取图标,大家可以先看我另一篇博客:
完整版java读取apk、ipa包名、版本名、版本号等信息

plist结构如下:

这里写图片描述

我们可以通过如下方式获取

这里写图片描述

然后根据名称去下载
这里写图片描述

最终可以获取到图片并下载到本地

这里写图片描述

很多人都会遇到这个问题:有的图标是正常的,而有的图标却是黑色的,打不开。

根据网上搜索的资料,大家或者找到了办法,就是通过ipin.py (python脚本)来反序列化恢复正常的图片。

我这边也下载了那个文件试了下,大家看效果:

这里写图片描述


大家一定注意到了吧,那张图片本来是黑色的,执行了一次之后,就恢复正常了,
然而再次执行之后,就变得更糟了,变成一张损坏的图片,完全打不开。。。。。

我们可以先生成一张黑色的图片,然后执行ipin.py恢复成好的图片,然后把这张图片移动另一个目录。
哎呦 ,这貌似是一个不错的想法,但是,如果,你解析的ipa图片,本身就是一张好的图片,这种情况不是不可能的,不是所有的图标本身就是黑色的。比如:爱奇艺的ipa,图片本身就是正常的,这时候你去ipin.py一下,那么这就尴尬了。

解决办法:
根据之前的ipin.py文件,修改了一下,现在的功能是遇到好的图片就跳过,不去反序列它,遇到黑色图片才去做操作。

请看效果:

这里写图片描述

没错,程序会报错,直接跳过这张图片。继续执行。

##3.1. python 反序列图片

安装大家可以到这里:
Python环境搭建

对于linux环境,一般python都是安装好的,大家可以直接输入python命令
如果版本太高下面执行会有问题,我的python版本是2.7的

下面最重要的来了,就是那个改进过的ipin.py文件。跳过正常图片。

#---
# iPIN - iPhone PNG Images Normalizer v1.0
# Copyright (C) 2007
#
# Author:
#  Axel E. Brzostowski
#  http://www.axelbrz.com.ar/
#  [email protected]
# 
# 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'/E:/python/img2/*.png'):
    shutil.move(filename,"/E:/python/img")
    print filename

大家需要注意的地方有一点,最后三行代码是我加上的,意思是把发序列的正常图片,
全部移到另一个文件夹里,这样做的好处是什么呢?

1、如果我们上传解析很多ipa文件,
那么理所当然的也就生成很多图标,如果,都放在反序列化的文件夹,而不移走的话,
那么, 程序,每次都会去查找有哪些图片需要反序列,这都是耗时的。

2、那些之前是黑色的,被反序列成正常图片之后是没问题的,如果再次反序列还是
会照样变成损坏的图片。上面改进过的ipin.py文件虽然改进过了,但是它只是针对
源文件是正常图片,而不是本身是黑色图片,这时你转成正常图片,再转一次,还是
会有问题。

其实两者是不矛盾的。

下面需要做的就是写个shell脚本,然后用程序去调用了。

shell脚本

#!/bin/sh
cd /usr/local/python/img/
python ipin.py

下面是后台代码:

/**
     * @author ZSL
     * @param ipaURL
     * @return
     * @desc [解析ipa文件]
     */
    public static Map<String,Object> getIpaInfo(String ipaURL , HttpServletRequest request){
        Map<String,Object> map = new HashMap<String,Object>();
        String newIconName = "";
        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();
                    System.out.println("=================name:" + name);
                    if(name.contains(icon.trim())){
                        newIconName = createDateSuffix()+ ".png";
                        FileOutputStream fos = new FileOutputStream(new File(pythonTempImagePath + File.separator + newIconName ));
                           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.get("CFBundleDisplayName");
            if(null == parameters){
                parameters = (NSString) rootDict.get("CFBundleName");
            }
            map.put("appName", parameters.toString());
//          parameters = (NSString) rootDict.get("CFBundlePrimaryIcon");
//          map.put("icon", parameters.toString());
            /////////////////////////////////////////////////
            infoIs.close();
            is.close();
            zipIns.close();

        } catch (Exception e) {
            e.printStackTrace();
            map.put("code", "fail");
            map.put("error","读取ipa文件失败");
        }
//      finally {
//          if (null != infoIs) {
//              infoIs.close();
//          }
//          if (null != is) {
//              is.close();
//          }
//          if (null != zipIns) {
//              zipIns.close();
//          }
//      }

        System.out.println("================================执行命令获取icon开始=========================");
        Process process;
        try {
            process = Runtime.getRuntime().exec("sh /usr/local/python/img/ipin.sh");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
            process.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("================================执行命令获取icon结束=========================");
        System.out.println("================================图标名称:"+newIconName+"=========================");

        map.put("icon", newIconName);
        map.put("code", "success");
        return map;
    }

大功告成! ipa文件解析成功 !

再来看一下效果:上传了两个比较大的,100M多

这里写图片描述







我这里是在linux下操作的,不过windows下也是可以的,只需要把shell脚本改成bat文件,然后
调用系统命令执行bat文件。

下面提供一份源码
Java解析apk、ipa图标,包名,应用名称,版本号源码

希望对大家有所帮助!

猜你喜欢

转载自blog.csdn.net/moyanxuan_1993_2_24/article/details/53612001