Java tutorial
/******************************************************************************* * Copyright (C) 2018 hankai * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ******************************************************************************/ package ren.hankai.cordwood.mobile; import com.dd.plist.NSArray; import com.dd.plist.NSDictionary; import com.dd.plist.PropertyListParser; import net.dongliu.apk.parser.ApkParser; import net.dongliu.apk.parser.bean.ApkMeta; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * ??? * * @author hankai * @version 1.0 * @since May 16, 2016 11:44:24 AM */ public class MobileAppScanner { private static final Logger logger = LoggerFactory.getLogger(MobileAppScanner.class); /** * ?? Android .apk ? * * @param filePath apk * @return ? * @author hankai * @since May 20, 2016 3:46:18 PM */ public static MobileAppInfo scanAndroidApk(String filePath) { final MobileAppInfo appInfo = new MobileAppInfo(); ApkParser parser = null; ZipFile zipFile = null; InputStream inputStream = null; try { parser = new ApkParser(filePath); final ApkMeta meta = parser.getApkMeta(); appInfo.setName(meta.getName()); appInfo.setVersion(meta.getVersionName() + "#" + meta.getVersionCode()); appInfo.setBundleId(meta.getPackageName()); final String iconPath = meta.getIcon(); appInfo.setIconName(iconPath); if (!StringUtils.isEmpty(iconPath)) { zipFile = new ZipFile(filePath, Charset.forName("UTF-8")); final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { final ZipEntry entry = entries.nextElement(); if (entry.getName().contains(iconPath)) { inputStream = zipFile.getInputStream(entry); appInfo.setIcon(IOUtils.toByteArray(inputStream)); break; } } } } catch (final Exception ex) { logger.error(String.format("Failed to parse apk file at %s", filePath)); } finally { try { if (parser != null) { parser.close(); } if (zipFile != null) { zipFile.close(); } if (inputStream != null) { inputStream.close(); } } catch (final Exception ex) { logger.trace("Failed to close stream.", ex); } } return appInfo; } /** * ? ipa info.plist * * @param appInfo app? * @param entry ipa ? * @param zipFile * @author hankai * @since Nov 23, 2016 2:32:08 PM */ private static void parseIosInfoPlist(MobileAppInfo appInfo, ZipEntry entry, ZipFile zipFile) { if (entry.getName().matches("Payload/.*\\.app/Info.plist")) { InputStream stream = null; try { stream = zipFile.getInputStream(entry); final NSDictionary dict = (NSDictionary) PropertyListParser.parse(stream); appInfo.setName(dict.get("CFBundleName").toString()); appInfo.setVersion(dict.get("CFBundleShortVersionString").toString() + "#" + dict.get("CFBundleVersion").toString()); appInfo.setBundleId(dict.get("CFBundleIdentifier").toString()); final NSDictionary icons = (NSDictionary) dict.get("CFBundleIcons"); final NSDictionary primaryIcons = (NSDictionary) icons.get("CFBundlePrimaryIcon"); final NSArray iconFiles = (NSArray) primaryIcons.get("CFBundleIconFiles"); if (iconFiles.count() > 0) { appInfo.setIconName(iconFiles.lastObject().toString()); } } catch (final Exception ex) { logger.error(String.format("Failed to parse entry %s", entry.getName())); } finally { IOUtils.closeQuietly(stream); } } } /** * ? ipa * * @param appInfo ? * @param zipFile * @param images * @author hankai * @since Nov 23, 2016 2:32:45 PM */ private static void parseIosAppIcon(MobileAppInfo appInfo, ZipFile zipFile, Map<String, ZipEntry> images) { if (!StringUtils.isEmpty(appInfo.getIconName())) { InputStream stream = null; for (final String key : images.keySet()) { if (key.contains(appInfo.getIconName())) { try { stream = zipFile.getInputStream(images.get(key)); appInfo.setIcon(IOUtils.toByteArray(stream)); } catch (final Exception ex) { logger.error("Failed to read app icon from package.", ex); } finally { IOUtils.closeQuietly(stream); } break; } } } } /** * ?? iOS .ipa ? * * @param filePath ipa * @return ? * @author hankai * @since May 20, 2016 3:46:18 PM */ public static MobileAppInfo scanIosIpa(String filePath) { final MobileAppInfo appInfo = new MobileAppInfo(); ZipFile zipFile = null; try { zipFile = new ZipFile(filePath, Charset.forName("UTF-8")); final Enumeration<? extends ZipEntry> entries = zipFile.entries(); final Map<String, ZipEntry> images = new HashMap<>(); while (entries.hasMoreElements()) { final ZipEntry entry = entries.nextElement(); parseIosInfoPlist(appInfo, entry, zipFile); if (entry.getName().toLowerCase().matches("payload/.*\\.app/.*\\.png")) { images.put(entry.getName(), entry); } } parseIosAppIcon(appInfo, zipFile, images); } catch (final Exception ex) { logger.error(String.format("Failed to parse ipa file at %s", filePath)); } finally { try { if (zipFile != null) { zipFile.close(); } } catch (final Exception ex) { logger.trace("Failed to close stream.", ex); } } return appInfo; } }