您的位置:首页 > 移动开发 > Android开发

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

2017-07-11 10:42 966 查看

前言:

场景:在不安装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文件以及系统层面的源码分析,基本上就到这了,仔细看看代码,然后自己动手写一写。如果以后有这方面的需求,相信也难道不到你了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐