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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM