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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27540131/article/details/74938234

前言:

场景:在不安装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("?%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", "", "", "", "", "", "" };

}

上面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文件以及系统层面的源码分析,基本上就到这了,仔细看看代码,然后自己动手写一写。如果以后有这方面的需求,相信也难道不到你了!





展开阅读全文

没有更多推荐了,返回首页