android 解析未安装apk中的AndroidManifest.xml以及系统源码分析

xiaoxiao2021-02-28  67

前言:

场景:在不安装apk的前提下,获取apk中的包名,LAUNCHER Activity等,这就需要解析我们的androidManifest.xml文件了,而我们的apk又是一个zip的压缩文件,通过压缩文件的方式--好压,rar等打开。 可以看到我们以上的目录,而我们的AndroidManifest.xml就在其中,有了这个就好办了,那我们就只需要获取这个zip包中的androidManifest.xml文件,拿出来解析就行了。不过在这之前值得一说的是,当我们把项目打包成apk的时候,androidManifest.xml文件已经被加密了,所以直接拿出来通过解析xml的三种api解析是不行的。 获取xml文件的方式有两种: 1.通过反编译获取 2.通过ZipFile来获取。这个要通过google提供的api APKParser.jar来解析这个加密的xml文件。 APKParser下载:http://code.google.com/p/xml-apk-parser 其中还有个demo,可以参考! 分析如何解析apk中的androidManifest.xml之前,先从源码的角度了解下系统是如何解析我们的apk中的androidManifest.xml文件的。

源码角度分析-如何解析androidManifest.xml

解析apk是由系统的PackageManagerService来执行的,而PackageManagerService的解析工作是在它的初始化中的。PackageManagerService的初始化: 在我们的SystemServer的initAndLoop方法中通过PackageManagerService.main()来初始化的。有些版本的源码可能是main()方法,SystemServer初始化了framework层的许多服务,这里仅仅只分析PackageManager。 深入到PackageManagerService.main()方法中 可以看到这里对PackageManagerService进行初始化操作。继续追踪下去,看下new PackageManagerService()到底进行了什么工作。 在它的初始化方法中,有这么段代码,分别是先获取到系统目录下的app等文件夹,然后通过scanDirLI()这个方法来扫描其中的apk文件。那接下来又追踪到我们的scanDirLI()方法。 可以看到,这个方法遍历整个文件夹,然后再通过scanPackageLI()方法来扫描其中的apk文件。 在scanPackageLI()方法中又通过一个parsePackage()的方法来扫描具体的apk文件。 它返回的package对象就包含了我们apk中的许多信息。那我们就继续来追踪下parsePackage()整个方法,他到底是如何解析我们的apk的。 这方法的代码有点长,分开来截图,这里有几个重点: 1.第一个红框是判断是否为apk文件,不是则返回null; 2.openXmlResourceParser方法返回的是一个Xml资源解析对象,这里解析的就是我们的androidManifest.xml文件。 3.调用他的同名函数parsePackage()方法进行具体的解析操作。如下图。 同名函数parsePackage的解析操作: 从上面的代码可以清楚的了解到,分别对xml文件中的application,permission等节点进行解析,而我们的activity,service等四大组件和一些其他的节点信息就在parseApplication()方法中进行解析得到的,同时把解析到的到四大组件以及xml文件中的其他信息封装到返回的package对象中。以上就是源码如何解析apk中androidManifest.xml文件的具体流程,当然packageManagerService的初始化工作并不仅仅是解析apk文件,他还有对dex文件进行优化以及一些其他的操作,想了解的可以深入看看源码。 了解了源码是如何解析androidManifest.xml文件之后,对于解析未安装的apk中的xml文件就更不在话下了。

解析未安装apk文件中的androidManifest.xml文件

在贴解析代码之前,来个解析结果图: 由于数量过多,所以这里仅仅截取一小部分,以上结果输出了3个字段。 分别是tag-name 代表节点信息;name代表当前节点属性的key值;value代表当前节点属性的value值。 不过解析到这并不行,还要能获取特定的信息,就比如刚开始所说的,需要包名/入口activity/这个apk的渠道信息等,这里的demo仅仅演示入口activity的获取。 package com.ljx.test; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.content.res.AXmlResourceParser; import android.util.TypedValue; public class AnalysisApk { public static void main(String[] args) { File file = null; file = new File("F:\\XXX.apk"); ArrayList<String> mActivities = new ArrayList<String>(); try { ZipFile zipFile = new ZipFile(file); Enumeration enumeration = zipFile.entries(); // 获取到apk中的AndroidManifest.xml文件 ZipEntry zipEntry = zipFile.getEntry(("AndroidManifest.xml")); AXmlResourceParser parser = new AXmlResourceParser(); parser.open(zipFile.getInputStream(zipEntry)); boolean flag = true; while (flag) { int event = parser.next(); if (event == XmlPullParser.START_TAG) { int count = parser.getAttributeCount(); 解析整个AndroidManifest.xml文件并输出 // for (int i = 0; i != parser.getAttributeCount(); ++i) { // System.out.printf("%s%s%s=\"%s\"", // new StringBuilder(10), // getNamespacePrefix(parser.getAttributePrefix(i)), // parser.getAttributeName(i), // getAttributeValue(parser, i)); // System.out.println(); // } for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) { // 解析整个AndroidManifest.xml文件并输出 // System.out.println("tag-name " + parser.getName()); // System.out.println("name " + // parser.getAttributeName(i)); // System.out.println("value " + // getAttributeValue(parser,i)); // System.out.println(""); // 获取应用入口activity if (parser.getName().endsWith("activity") && parser.getAttributeName(i).equals("name")) { mActivities.add(getAttributeValue(parser, i)); } if (parser.getAttributeValue(i).contains("MAIN")) { System.out.println(mActivities.get(mActivities.size() - 1)); return; } } } } } catch (ZipException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (XmlPullParserException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 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("?%sX", getPackage(data), data); } if (type == TypedValue.TYPE_REFERENCE) { return String.format("@%sX", getPackage(data), data); } if (type == TypedValue.TYPE_FLOAT) { return String.valueOf(Float.intBitsToFloat(data)); } if (type == TypedValue.TYPE_INT_HEX) { return String.format("0xX", 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("#X", data); } if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { return String.valueOf(data); } return String.format("<0x%X, type 0xX>", 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", "", "", "", "", "", "" }; } 上面main方法中注释的两段代码,都是对androidManifest.xml文件进行解析,只是输出样式不同,有兴趣的可以自己试着写下,然后看下输出结果是否是我们应用的入口activity。 上面代码的思路是,解析androidManifest.xml文件中的所有节点,然后找到其中的activity节点,紧接着对这个activity中的属性进行解析,看是否包含"MAIN",因为只有"MAIN"以及"LAUNCHER"同时存在的时候,这个activity就是我们应用的入口activity,不过上面的代码有一点问题的是,当androidManifest.xml中如果存在多个activity的action是"android.intent.action.MAIN"的时候,上面的判断逻辑就不正确了,严谨点的话是把MAIN替换成LAUNCHER,不过我这是事先知道自己应用只有一个activity中的action是...MAIN的,所以这里这样处理了。 可以看到通过上面的demo代码,找到了SplashActivity,然后可以打开我们项目的AndroidManifest.xml文件看下,是否是这个activity。 通过上图可以发现,我们解析是完全正确的, 如何解析androidManifest.xml文件以及系统层面的源码分析,基本上就到这了,仔细看看代码,然后自己动手写一写。如果以后有这方面的需求,相信也难道不到你了!
转载请注明原文地址: https://www.6miu.com/read-49776.html

最新回复(0)