Java通过解析文件获取apk信息

参考:https://github.com/bihe0832/Android-GetAPKInfo

ApkInfo

@Data
@NoArgsConstructor
public class ApkInfo {
    public String versionCode = "";
    public String versionName = "";
    public String packageName = "";
    public String signature = "";
    public String minSdkVersion = "";
    public String targetSdkVersion = "";
    public boolean isV1SignatureOK = false;
    public boolean isV2Signature = false;
    public boolean isV2SignatureOK = false;
    public String v2CheckErrorInfo = "";
    public ArrayList<String> permissions = new ArrayList<String>();

    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("  包名: " + packageName + "\n");
        sb.append("  版本名: " + versionName + "\n");
        sb.append("  版本号: " + versionCode + "\n");
        sb.append("  签名文件MD5: " + signature + "\n");
        sb.append("  SDK版本:\n");
        sb.append("      minSdkVersion: " + minSdkVersion + "\n");
        sb.append("      targetSdkVersion: " + targetSdkVersion + "\n");
        sb.append("  V1签名验证通过: " + isV1SignatureOK + "\n");
        sb.append("  使用V2签名: " + isV2Signature + "\n");
        sb.append("  V2签名验证通过: " + isV2SignatureOK + "\n");
        if(!isV1SignatureOK || (isV2Signature && !isV2SignatureOK)){
            sb.append("  签名验证失败原因: " + v2CheckErrorInfo + "\n");
        }
//        sb.append("  使用权限列表:\n");
//        for (String string : permissions) {
//            sb.append("      "+ string +"\n");
//        }
        return sb.toString();
    }
}

ApkUtil

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import me.yezhou.model.ApkInfo;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;

import java.util.Iterator;
import java.util.List;

public class ApkUtil {

    private static final Namespace NS = Namespace.getNamespace("http://schemas.android.com/apk/res/android");

    public static void getApkInfo(String apkPath, ApkInfo info, boolean showException) {
        SAXBuilder builder = new SAXBuilder();
        Document document = null;
        try {
            InputStream stream = new ByteArrayInputStream(AXMLPrinter.getManifestXMLFromAPK(apkPath).getBytes(StandardCharsets.UTF_8));
            document = builder.build(stream);
        } catch (Exception e) {
            if (showException) {
                e.printStackTrace();
            }
        }
        Element root = document.getRootElement();
        info.versionCode = root.getAttributeValue("versionCode", NS);
        info.versionName = root.getAttributeValue("versionName", NS);
        String s = root.getAttributes().toString();
        String c[] = s.split(",");
        for (String a : c) {
            if (a.contains("package")) {
                info.packageName = a.substring(a.indexOf("package=\"") + 9, a.lastIndexOf("\""));
            }
        }

        List booklist = root.getChildren("uses-sdk");
        Element book = (Element) booklist.get(0);
        info.minSdkVersion = book.getAttributeValue("minSdkVersion", NS);
        info.targetSdkVersion = book.getAttributeValue("targetSdkVersion", NS);

        booklist = root.getChildren("uses-permission");
        for (Iterator iter = booklist.iterator(); iter.hasNext(); ) {
            Element tempBook = (Element) iter.next();
            info.permissions.add(tempBook.getAttributeValue("name", NS));
        }
    }

}

AXMLPrinter

import java.io.File;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.xmlpull.v1.XmlPullParser;

import android.content.res.AXmlResourceParser;
import android.util.TypedValue;

/**
 * This is example usage of AXMLParser class.
 * <p>
 * Prints xml document from Android's binary xml file.
 */
public class AXMLPrinter {
    private static final String DEFAULT_XML = "AndroidManifest.xml";

    public static String getManifestXMLFromAPK(String apkPath) {
        ZipFile file = null;
        StringBuilder xmlSb = new StringBuilder(100);
        try {
            File apkFile = new File(apkPath);
            file = new ZipFile(apkFile, ZipFile.OPEN_READ);
            ZipEntry entry = file.getEntry(DEFAULT_XML);

            AXmlResourceParser parser = new AXmlResourceParser();
            parser.open(file.getInputStream(entry));

            StringBuilder sb = new StringBuilder(10);
            final String indentStep = "    ";

            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                switch (type) {
                    case XmlPullParser.START_DOCUMENT: {
                        log(xmlSb, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
                        break;
                    }
                    case XmlPullParser.START_TAG: {
                        log(false, xmlSb, "%s<%s%s", sb,
                                getNamespacePrefix(parser.getPrefix()), parser.getName());
                        sb.append(indentStep);

                        int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1);
                        int namespaceCount = parser.getNamespaceCount(parser.getDepth());

                        for (int i = namespaceCountBefore; i != namespaceCount; ++i) {
                            log(xmlSb, "%sxmlns:%s=\"%s\"",
                                    i == namespaceCountBefore ? "  " : sb,
                                    parser.getNamespacePrefix(i),
                                    parser.getNamespaceUri(i));
                        }

                        for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) {
                            log(false, xmlSb, "%s%s%s=\"%s\"", " ",
                                    getNamespacePrefix(parser.getAttributePrefix(i)),
                                    parser.getAttributeName(i),
                                    getAttributeValue(parser, i));
                        }
//                        log("%s>",sb);
                        log(xmlSb, ">");
                        break;
                    }
                    case XmlPullParser.END_TAG: {
                        sb.setLength(sb.length() - indentStep.length());
                        log(xmlSb, "%s</%s%s>", sb,
                                getNamespacePrefix(parser.getPrefix()),
                                parser.getName());
                        break;
                    }
                    case XmlPullParser.TEXT: {
                        log(xmlSb, "%s%s", sb, parser.getText());
                        break;
                    }
                }
            }
            parser.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
//        System.out.println(xmlSb.toString());
        return xmlSb.toString();
    }

    private static String getNamespacePrefix(String prefix) {
        if (prefix == null || prefix.length() == 0) {
            return "";
        }
        return prefix + ":";
    }

    private static String getAttributeValue(AXmlResourceParser parser, int index) {
        int type = parser.getAttributeValueType(index);
        int data = parser.getAttributeValueData(index);
        if (type == TypedValue.TYPE_STRING) {
            return parser.getAttributeValue(index);
        }
        if (type == TypedValue.TYPE_ATTRIBUTE) {
            return String.format("?%s%08X", getPackage(data), data);
        }
        if (type == TypedValue.TYPE_REFERENCE) {
            return String.format("@%s%08X", getPackage(data), data);
        }
        if (type == TypedValue.TYPE_FLOAT) {
            return String.valueOf(Float.intBitsToFloat(data));
        }
        if (type == TypedValue.TYPE_INT_HEX) {
            return String.format("0x%08X", data);
        }
        if (type == TypedValue.TYPE_INT_BOOLEAN) {
            return data != 0 ? "true" : "false";
        }
        if (type == TypedValue.TYPE_DIMENSION) {
            return Float.toString(complexToFloat(data)) +
                    DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
        }
        if (type == TypedValue.TYPE_FRACTION) {
            return Float.toString(complexToFloat(data)) +
                    FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
        }
        if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
            return String.format("#%08X", data);
        }
        if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) {
            return String.valueOf(data);
        }
        return String.format("<0x%X, type 0x%02X>", data, type);
    }

    private static String getPackage(int id) {
        if (id >>> 24 == 1) {
            return "android:";
        }
        return "";
    }

    private static void log(StringBuilder xmlSb, String format, Object... arguments) {
        log(true, xmlSb, format, arguments);
    }

    private static void log(boolean newLine, StringBuilder xmlSb, String format, Object... arguments) {
//        System.out.printf(format,arguments);
//        if(newLine) System.out.println();
        xmlSb.append(String.format(format, arguments));
        if (newLine) xmlSb.append("\n");
    }

    /////////////////////////////////// ILLEGAL STUFF, DONT LOOK :)

    public static float complexToFloat(int complex) {
        return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
    }

    private static final float RADIX_MULTS[] = {
            0.00390625F, 3.051758E-005F, 1.192093E-007F, 4.656613E-010F
    };
    private static final String DIMENSION_UNITS[] = {
            "px", "dip", "sp", "pt", "in", "mm", "", ""
    };
    private static final String FRACTION_UNITS[] = {
            "%", "%p", "", "", "", "", "", ""
    };
}

GetSignature

import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class GetSignature {

    public GetSignature() {
    }

    public static String getApkSignInfo(String apkFilePath, boolean showException) {
        byte[] readBuffer = new byte[8192];
        Certificate[] certs = null;

        try {
            JarFile e = new JarFile(apkFilePath);
            Enumeration entries = e.entries();

            while (entries.hasMoreElements()) {
                JarEntry je = (JarEntry) entries.nextElement();
                if (!je.isDirectory() && !je.getName().startsWith("META-INF/")) {
                    Certificate[] localCerts = loadCertificates(e, je, readBuffer);
                    boolean found = false;
                    if (certs == null) {
                        certs = localCerts;
                    } else {
                        for (int i = 0; i < certs.length; ++i) {
                            for (int j = 0; j < localCerts.length; ++j) {
                                if (certs[i] != null && certs[i].equals(localCerts[j])) {
                                    found = true;
                                    break;
                                }
                            }

                            if (certs.length != localCerts.length) {
                                e.close();
                                return null;
                            }
                        }
                    }

                    if (found) {
                        break;
                    }
                }
            }

            e.close();
            return getSignValidString(certs[0].getEncoded());
        } catch (Exception var10) {
            if (showException) {
                var10.printStackTrace();
            }
            return "get signInfo failed, please use --debug get more info";
        }
    }

    private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) {
        try {
            InputStream e = jarFile.getInputStream(je);

            while (e.read(readBuffer, 0, readBuffer.length) != -1) {
                ;
            }

            e.close();
            return je != null ? je.getCertificates() : null;
        } catch (Exception var4) {
            var4.printStackTrace();
            System.err.println("Exception reading " + je.getName() + " in " + jarFile.getName() + ": " + var4);
            return null;
        }
    }

    public static String toHexString(byte[] keyData) {
        if (keyData == null) {
            return null;
        } else {
            int expectedStringLen = keyData.length * 2;
            StringBuilder sb = new StringBuilder(expectedStringLen);

            for (int i = 0; i < keyData.length; ++i) {
                String hexStr = Integer.toString(keyData[i] & 255, 16);
                if (hexStr.length() == 1) {
                    hexStr = "0" + hexStr;
                }

                sb.append(hexStr);
            }

            return sb.toString();
        }
    }

    private static String getSignValidString(byte[] sign) throws NoSuchAlgorithmException {
        MessageDigest alga = null;
        alga = MessageDigest.getInstance("MD5");
        alga.update(sign);
        return toHexString(alga.digest());
    }
}

ApkMain

public class ApkMain {
    private static boolean sShowDebug = false;
    private static final int RET_GET_INFO_BAD = -1;

    public static void main(String[] params) throws Exception {
        getApkInfo("E:\\AppBlog\\AndroidX-1.0.apk");
    }

    private static void getApkInfo(String filePath) {
        ApkInfo info = new ApkInfo();
        try {
            ApkUtil.getApkInfo(filePath, info, sShowDebug);
        } catch (Exception e) {
            showFailedCheckResult(RET_GET_INFO_BAD, "get apkinfo failed, throw an Exception ;please use --debug get more info");
            return;
        }
        String v2Signature = ApkSignerTool.verify(filePath, sShowDebug);
        try {
            JSONObject jsonobject = JSON.parseObject(v2Signature);
            info.isV1SignatureOK = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V1_OK);
            info.isV2Signature = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V2);
            info.isV2SignatureOK = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V2_OK);
            info.v2CheckErrorInfo = jsonobject.getString(ApkSignerTool.KEY_RESULT_MSG);
            info.signature = GetSignature.getApkSignInfo(filePath, sShowDebug);
            info.v2CheckErrorInfo = jsonobject.getString(ApkSignerTool.KEY_RESULT_MSG);
        } catch (Exception e) {
            showFailedCheckResult(RET_GET_INFO_BAD, "get apk info failed, throw an Exception;please use --debug get more info");
            return;
        }
        showSuccssedCheckResult(info);
    }

    private static void showSuccssedCheckResult(ApkInfo info) {
        System.out.println("执行结果: 成功");
        System.out.println("应用信息: \n" + info.toString());
    }

    private static void showFailedCheckResult(int ret, String Msg) {
        System.out.println("执行结果: 失败(" + ret + ")");
        System.out.println("错误信息:" + Msg);
    }
}

运行输出

执行结果: 成功
应用信息: 
  包名: cn.appblog.androidx
  版本名: 1.0
  版本号: 1
  签名文件MD5: a11e91a81004207c93fd57ee52cfcc3c
  SDK版本:
      minSdkVersion: 16
      targetSdkVersion: 29
  V1签名验证通过: true
  使用V2签名: true
  V2签名验证通过: true

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/19/java-obtains-apk-information-by-parsing-files/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Java通过解析文件获取apk信息
参考:https://github.com/bihe0832/Android-GetAPKInfo ApkInfo @Data @NoArgsConstructor public class ApkInfo { public String versionCode = "……
<<上一篇
下一篇>>
文章目录
关闭
目 录