{"id":1408,"date":"2023-03-19T11:34:29","date_gmt":"2023-03-19T03:34:29","guid":{"rendered":"https:\/\/www.appblog.cn\/?p=1408"},"modified":"2023-04-28T21:08:07","modified_gmt":"2023-04-28T13:08:07","slug":"java-obtains-apk-information-by-parsing-files","status":"publish","type":"post","link":"https:\/\/www.appblog.cn\/index.php\/2023\/03\/19\/java-obtains-apk-information-by-parsing-files\/","title":{"rendered":"Java\u901a\u8fc7\u89e3\u6790\u6587\u4ef6\u83b7\u53d6apk\u4fe1\u606f"},"content":{"rendered":"<p>\u53c2\u8003\uff1a<a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/github.com\/bihe0832\/Android-GetAPKInfo\">https:\/\/github.com\/bihe0832\/Android-GetAPKInfo<\/a><\/p>\n<h3>ApkInfo<\/h3>\n<p><!-- more --><\/p>\n<pre><code class=\"language-java\">@Data\n@NoArgsConstructor\npublic class ApkInfo {\n    public String versionCode = &quot;&quot;;\n    public String versionName = &quot;&quot;;\n    public String packageName = &quot;&quot;;\n    public String signature = &quot;&quot;;\n    public String minSdkVersion = &quot;&quot;;\n    public String targetSdkVersion = &quot;&quot;;\n    public boolean isV1SignatureOK = false;\n    public boolean isV2Signature = false;\n    public boolean isV2SignatureOK = false;\n    public String v2CheckErrorInfo = &quot;&quot;;\n    public ArrayList&lt;String&gt; permissions = new ArrayList&lt;String&gt;();\n\n    @Override\n    public String toString(){\n        StringBuilder sb = new StringBuilder();\n        sb.append(&quot;  \u5305\u540d: &quot; + packageName + &quot;\\n&quot;);\n        sb.append(&quot;  \u7248\u672c\u540d: &quot; + versionName + &quot;\\n&quot;);\n        sb.append(&quot;  \u7248\u672c\u53f7: &quot; + versionCode + &quot;\\n&quot;);\n        sb.append(&quot;  \u7b7e\u540d\u6587\u4ef6MD5: &quot; + signature + &quot;\\n&quot;);\n        sb.append(&quot;  SDK\u7248\u672c:\\n&quot;);\n        sb.append(&quot;      minSdkVersion: &quot; + minSdkVersion + &quot;\\n&quot;);\n        sb.append(&quot;      targetSdkVersion: &quot; + targetSdkVersion + &quot;\\n&quot;);\n        sb.append(&quot;  V1\u7b7e\u540d\u9a8c\u8bc1\u901a\u8fc7: &quot; + isV1SignatureOK + &quot;\\n&quot;);\n        sb.append(&quot;  \u4f7f\u7528V2\u7b7e\u540d: &quot; + isV2Signature + &quot;\\n&quot;);\n        sb.append(&quot;  V2\u7b7e\u540d\u9a8c\u8bc1\u901a\u8fc7: &quot; + isV2SignatureOK + &quot;\\n&quot;);\n        if(!isV1SignatureOK || (isV2Signature &amp;&amp; !isV2SignatureOK)){\n            sb.append(&quot;  \u7b7e\u540d\u9a8c\u8bc1\u5931\u8d25\u539f\u56e0: &quot; + v2CheckErrorInfo + &quot;\\n&quot;);\n        }\n\/\/        sb.append(&quot;  \u4f7f\u7528\u6743\u9650\u5217\u8868:\\n&quot;);\n\/\/        for (String string : permissions) {\n\/\/            sb.append(&quot;      &quot;+ string +&quot;\\n&quot;);\n\/\/        }\n        return sb.toString();\n    }\n}<\/code><\/pre>\n<h3>ApkUtil<\/h3>\n<pre><code class=\"language-java\">import java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\n\nimport me.yezhou.model.ApkInfo;\nimport org.jdom2.Document;\nimport org.jdom2.Element;\nimport org.jdom2.Namespace;\nimport org.jdom2.input.SAXBuilder;\n\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class ApkUtil {\n\n    private static final Namespace NS = Namespace.getNamespace(&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;);\n\n    public static void getApkInfo(String apkPath, ApkInfo info, boolean showException) {\n        SAXBuilder builder = new SAXBuilder();\n        Document document = null;\n        try {\n            InputStream stream = new ByteArrayInputStream(AXMLPrinter.getManifestXMLFromAPK(apkPath).getBytes(StandardCharsets.UTF_8));\n            document = builder.build(stream);\n        } catch (Exception e) {\n            if (showException) {\n                e.printStackTrace();\n            }\n        }\n        Element root = document.getRootElement();\n        info.versionCode = root.getAttributeValue(&quot;versionCode&quot;, NS);\n        info.versionName = root.getAttributeValue(&quot;versionName&quot;, NS);\n        String s = root.getAttributes().toString();\n        String c[] = s.split(&quot;,&quot;);\n        for (String a : c) {\n            if (a.contains(&quot;package&quot;)) {\n                info.packageName = a.substring(a.indexOf(&quot;package=\\&quot;&quot;) + 9, a.lastIndexOf(&quot;\\&quot;&quot;));\n            }\n        }\n\n        List booklist = root.getChildren(&quot;uses-sdk&quot;);\n        Element book = (Element) booklist.get(0);\n        info.minSdkVersion = book.getAttributeValue(&quot;minSdkVersion&quot;, NS);\n        info.targetSdkVersion = book.getAttributeValue(&quot;targetSdkVersion&quot;, NS);\n\n        booklist = root.getChildren(&quot;uses-permission&quot;);\n        for (Iterator iter = booklist.iterator(); iter.hasNext(); ) {\n            Element tempBook = (Element) iter.next();\n            info.permissions.add(tempBook.getAttributeValue(&quot;name&quot;, NS));\n        }\n    }\n\n}<\/code><\/pre>\n<h3>AXMLPrinter<\/h3>\n<pre><code class=\"language-java\">import java.io.File;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\nimport org.xmlpull.v1.XmlPullParser;\n\nimport android.content.res.AXmlResourceParser;\nimport android.util.TypedValue;\n\n\/**\n * This is example usage of AXMLParser class.\n * &lt;p&gt;\n * Prints xml document from Android&#039;s binary xml file.\n *\/\npublic class AXMLPrinter {\n    private static final String DEFAULT_XML = &quot;AndroidManifest.xml&quot;;\n\n    public static String getManifestXMLFromAPK(String apkPath) {\n        ZipFile file = null;\n        StringBuilder xmlSb = new StringBuilder(100);\n        try {\n            File apkFile = new File(apkPath);\n            file = new ZipFile(apkFile, ZipFile.OPEN_READ);\n            ZipEntry entry = file.getEntry(DEFAULT_XML);\n\n            AXmlResourceParser parser = new AXmlResourceParser();\n            parser.open(file.getInputStream(entry));\n\n            StringBuilder sb = new StringBuilder(10);\n            final String indentStep = &quot;    &quot;;\n\n            int type;\n            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {\n                switch (type) {\n                    case XmlPullParser.START_DOCUMENT: {\n                        log(xmlSb, &quot;&lt;?xml version=\\&quot;1.0\\&quot; encoding=\\&quot;utf-8\\&quot;?&gt;&quot;);\n                        break;\n                    }\n                    case XmlPullParser.START_TAG: {\n                        log(false, xmlSb, &quot;%s&lt;%s%s&quot;, sb,\n                                getNamespacePrefix(parser.getPrefix()), parser.getName());\n                        sb.append(indentStep);\n\n                        int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1);\n                        int namespaceCount = parser.getNamespaceCount(parser.getDepth());\n\n                        for (int i = namespaceCountBefore; i != namespaceCount; ++i) {\n                            log(xmlSb, &quot;%sxmlns:%s=\\&quot;%s\\&quot;&quot;,\n                                    i == namespaceCountBefore ? &quot;  &quot; : sb,\n                                    parser.getNamespacePrefix(i),\n                                    parser.getNamespaceUri(i));\n                        }\n\n                        for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) {\n                            log(false, xmlSb, &quot;%s%s%s=\\&quot;%s\\&quot;&quot;, &quot; &quot;,\n                                    getNamespacePrefix(parser.getAttributePrefix(i)),\n                                    parser.getAttributeName(i),\n                                    getAttributeValue(parser, i));\n                        }\n\/\/                        log(&quot;%s&gt;&quot;,sb);\n                        log(xmlSb, &quot;&gt;&quot;);\n                        break;\n                    }\n                    case XmlPullParser.END_TAG: {\n                        sb.setLength(sb.length() - indentStep.length());\n                        log(xmlSb, &quot;%s&lt;\/%s%s&gt;&quot;, sb,\n                                getNamespacePrefix(parser.getPrefix()),\n                                parser.getName());\n                        break;\n                    }\n                    case XmlPullParser.TEXT: {\n                        log(xmlSb, &quot;%s%s&quot;, sb, parser.getText());\n                        break;\n                    }\n                }\n            }\n            parser.close();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\/\/        System.out.println(xmlSb.toString());\n        return xmlSb.toString();\n    }\n\n    private static String getNamespacePrefix(String prefix) {\n        if (prefix == null || prefix.length() == 0) {\n            return &quot;&quot;;\n        }\n        return prefix + &quot;:&quot;;\n    }\n\n    private static String getAttributeValue(AXmlResourceParser parser, int index) {\n        int type = parser.getAttributeValueType(index);\n        int data = parser.getAttributeValueData(index);\n        if (type == TypedValue.TYPE_STRING) {\n            return parser.getAttributeValue(index);\n        }\n        if (type == TypedValue.TYPE_ATTRIBUTE) {\n            return String.format(&quot;?%s%08X&quot;, getPackage(data), data);\n        }\n        if (type == TypedValue.TYPE_REFERENCE) {\n            return String.format(&quot;@%s%08X&quot;, getPackage(data), data);\n        }\n        if (type == TypedValue.TYPE_FLOAT) {\n            return String.valueOf(Float.intBitsToFloat(data));\n        }\n        if (type == TypedValue.TYPE_INT_HEX) {\n            return String.format(&quot;0x%08X&quot;, data);\n        }\n        if (type == TypedValue.TYPE_INT_BOOLEAN) {\n            return data != 0 ? &quot;true&quot; : &quot;false&quot;;\n        }\n        if (type == TypedValue.TYPE_DIMENSION) {\n            return Float.toString(complexToFloat(data)) +\n                    DIMENSION_UNITS[data &amp; TypedValue.COMPLEX_UNIT_MASK];\n        }\n        if (type == TypedValue.TYPE_FRACTION) {\n            return Float.toString(complexToFloat(data)) +\n                    FRACTION_UNITS[data &amp; TypedValue.COMPLEX_UNIT_MASK];\n        }\n        if (type &gt;= TypedValue.TYPE_FIRST_COLOR_INT &amp;&amp; type &lt;= TypedValue.TYPE_LAST_COLOR_INT) {\n            return String.format(&quot;#%08X&quot;, data);\n        }\n        if (type &gt;= TypedValue.TYPE_FIRST_INT &amp;&amp; type &lt;= TypedValue.TYPE_LAST_INT) {\n            return String.valueOf(data);\n        }\n        return String.format(&quot;&lt;0x%X, type 0x%02X&gt;&quot;, data, type);\n    }\n\n    private static String getPackage(int id) {\n        if (id &gt;&gt;&gt; 24 == 1) {\n            return &quot;android:&quot;;\n        }\n        return &quot;&quot;;\n    }\n\n    private static void log(StringBuilder xmlSb, String format, Object... arguments) {\n        log(true, xmlSb, format, arguments);\n    }\n\n    private static void log(boolean newLine, StringBuilder xmlSb, String format, Object... arguments) {\n\/\/        System.out.printf(format,arguments);\n\/\/        if(newLine) System.out.println();\n        xmlSb.append(String.format(format, arguments));\n        if (newLine) xmlSb.append(&quot;\\n&quot;);\n    }\n\n    \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ ILLEGAL STUFF, DONT LOOK :)\n\n    public static float complexToFloat(int complex) {\n        return (float) (complex &amp; 0xFFFFFF00) * RADIX_MULTS[(complex &gt;&gt; 4) &amp; 3];\n    }\n\n    private static final float RADIX_MULTS[] = {\n            0.00390625F, 3.051758E-005F, 1.192093E-007F, 4.656613E-010F\n    };\n    private static final String DIMENSION_UNITS[] = {\n            &quot;px&quot;, &quot;dip&quot;, &quot;sp&quot;, &quot;pt&quot;, &quot;in&quot;, &quot;mm&quot;, &quot;&quot;, &quot;&quot;\n    };\n    private static final String FRACTION_UNITS[] = {\n            &quot;%&quot;, &quot;%p&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;\n    };\n}<\/code><\/pre>\n<h3>GetSignature<\/h3>\n<pre><code class=\"language-java\">import java.io.InputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.Certificate;\nimport java.util.Enumeration;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\n\npublic class GetSignature {\n\n    public GetSignature() {\n    }\n\n    public static String getApkSignInfo(String apkFilePath, boolean showException) {\n        byte[] readBuffer = new byte[8192];\n        Certificate[] certs = null;\n\n        try {\n            JarFile e = new JarFile(apkFilePath);\n            Enumeration entries = e.entries();\n\n            while (entries.hasMoreElements()) {\n                JarEntry je = (JarEntry) entries.nextElement();\n                if (!je.isDirectory() &amp;&amp; !je.getName().startsWith(&quot;META-INF\/&quot;)) {\n                    Certificate[] localCerts = loadCertificates(e, je, readBuffer);\n                    boolean found = false;\n                    if (certs == null) {\n                        certs = localCerts;\n                    } else {\n                        for (int i = 0; i &lt; certs.length; ++i) {\n                            for (int j = 0; j &lt; localCerts.length; ++j) {\n                                if (certs[i] != null &amp;&amp; certs[i].equals(localCerts[j])) {\n                                    found = true;\n                                    break;\n                                }\n                            }\n\n                            if (certs.length != localCerts.length) {\n                                e.close();\n                                return null;\n                            }\n                        }\n                    }\n\n                    if (found) {\n                        break;\n                    }\n                }\n            }\n\n            e.close();\n            return getSignValidString(certs[0].getEncoded());\n        } catch (Exception var10) {\n            if (showException) {\n                var10.printStackTrace();\n            }\n            return &quot;get signInfo failed, please use --debug get more info&quot;;\n        }\n    }\n\n    private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) {\n        try {\n            InputStream e = jarFile.getInputStream(je);\n\n            while (e.read(readBuffer, 0, readBuffer.length) != -1) {\n                ;\n            }\n\n            e.close();\n            return je != null ? je.getCertificates() : null;\n        } catch (Exception var4) {\n            var4.printStackTrace();\n            System.err.println(&quot;Exception reading &quot; + je.getName() + &quot; in &quot; + jarFile.getName() + &quot;: &quot; + var4);\n            return null;\n        }\n    }\n\n    public static String toHexString(byte[] keyData) {\n        if (keyData == null) {\n            return null;\n        } else {\n            int expectedStringLen = keyData.length * 2;\n            StringBuilder sb = new StringBuilder(expectedStringLen);\n\n            for (int i = 0; i &lt; keyData.length; ++i) {\n                String hexStr = Integer.toString(keyData[i] &amp; 255, 16);\n                if (hexStr.length() == 1) {\n                    hexStr = &quot;0&quot; + hexStr;\n                }\n\n                sb.append(hexStr);\n            }\n\n            return sb.toString();\n        }\n    }\n\n    private static String getSignValidString(byte[] sign) throws NoSuchAlgorithmException {\n        MessageDigest alga = null;\n        alga = MessageDigest.getInstance(&quot;MD5&quot;);\n        alga.update(sign);\n        return toHexString(alga.digest());\n    }\n}<\/code><\/pre>\n<h3>ApkMain<\/h3>\n<pre><code class=\"language-java\">public class ApkMain {\n    private static boolean sShowDebug = false;\n    private static final int RET_GET_INFO_BAD = -1;\n\n    public static void main(String[] params) throws Exception {\n        getApkInfo(&quot;E:\\\\AppBlog\\\\AndroidX-1.0.apk&quot;);\n    }\n\n    private static void getApkInfo(String filePath) {\n        ApkInfo info = new ApkInfo();\n        try {\n            ApkUtil.getApkInfo(filePath, info, sShowDebug);\n        } catch (Exception e) {\n            showFailedCheckResult(RET_GET_INFO_BAD, &quot;get apkinfo failed, throw an Exception ;please use --debug get more info&quot;);\n            return;\n        }\n        String v2Signature = ApkSignerTool.verify(filePath, sShowDebug);\n        try {\n            JSONObject jsonobject = JSON.parseObject(v2Signature);\n            info.isV1SignatureOK = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V1_OK);\n            info.isV2Signature = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V2);\n            info.isV2SignatureOK = jsonobject.getBoolean(ApkSignerTool.KEY_RESULT_IS_V2_OK);\n            info.v2CheckErrorInfo = jsonobject.getString(ApkSignerTool.KEY_RESULT_MSG);\n            info.signature = GetSignature.getApkSignInfo(filePath, sShowDebug);\n            info.v2CheckErrorInfo = jsonobject.getString(ApkSignerTool.KEY_RESULT_MSG);\n        } catch (Exception e) {\n            showFailedCheckResult(RET_GET_INFO_BAD, &quot;get apk info failed, throw an Exception;please use --debug get more info&quot;);\n            return;\n        }\n        showSuccssedCheckResult(info);\n    }\n\n    private static void showSuccssedCheckResult(ApkInfo info) {\n        System.out.println(&quot;\u6267\u884c\u7ed3\u679c: \u6210\u529f&quot;);\n        System.out.println(&quot;\u5e94\u7528\u4fe1\u606f: \\n&quot; + info.toString());\n    }\n\n    private static void showFailedCheckResult(int ret, String Msg) {\n        System.out.println(&quot;\u6267\u884c\u7ed3\u679c: \u5931\u8d25(&quot; + ret + &quot;)&quot;);\n        System.out.println(&quot;\u9519\u8bef\u4fe1\u606f:&quot; + Msg);\n    }\n}<\/code><\/pre>\n<h3>\u8fd0\u884c\u8f93\u51fa<\/h3>\n<pre><code>\u6267\u884c\u7ed3\u679c: \u6210\u529f\n\u5e94\u7528\u4fe1\u606f: \n  \u5305\u540d: cn.appblog.androidx\n  \u7248\u672c\u540d: 1.0\n  \u7248\u672c\u53f7: 1\n  \u7b7e\u540d\u6587\u4ef6MD5: a11e91a81004207c93fd57ee52cfcc3c\n  SDK\u7248\u672c:\n      minSdkVersion: 16\n      targetSdkVersion: 29\n  V1\u7b7e\u540d\u9a8c\u8bc1\u901a\u8fc7: true\n  \u4f7f\u7528V2\u7b7e\u540d: true\n  V2\u7b7e\u540d\u9a8c\u8bc1\u901a\u8fc7: true<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u53c2\u8003\uff1ahttps:\/\/github.com\/bihe0832\/Android-GetAPKInfo ApkIn [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43],"tags":[210],"class_list":["post-1408","post","type-post","status-publish","format-standard","hentry","category-java-basic","tag-apk"],"_links":{"self":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1408","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=1408"}],"version-history":[{"count":0,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1408\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=1408"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=1408"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=1408"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}