最近在更新一个文件搜索器时需要做一个解码apk中xml文件,研究了一下发现通过AssetManager类可以获取apk包的xml解析器,但无法直接保存成文件,只好解析后再生成string,最后封装成了一个类,可解析如AndroidManifest.xml文件等,也可解析其它xml,比如res/*/*.xml,但请注意经过加密的apk可能没有res这个路径,需要借助ZipFile类去解析获取apk文件列表,解析zip这里就不谈了。
下面是一个示例app,用来获取并解码微信的清单文件。
/**
* Created by yuanfang235 on 2021/8/12.
*
* APK资源文件解析类,用于反编译XML文件
*/
public class ApkResourceParser {
private static final String TAG = ApkResourceParser.class.getSimpleName();
/* 着色 */
private static final int COLOR_TAG = Color.parseColor("#000080");
private static final int COLOR_ATTR_PREFIX = Color.parseColor("#660E7A");
private static final int COLOR_ATTR_NAME = Color.parseColor("#0000ff");
private static final int COLOR_ATTR_VALUE = Color.parseColor("#008000");
private static final int COLOR_COMMENT = Color.parseColor("#808080");
/* 命名空间 */
private static final String NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
private static final String NAMESPACE_TOOLS = "http://schemas.android.com/tools";
private static final String NAMESPACE_APP = "http://schemas.android.com/apk/res-auto";
private static final Map<String, String> mNamespaces = new HashMap<>();
private boolean lineSpace = false; //行间距
private boolean markColor = false; //着色
private Context mContext;
public ApkResourceParser(Context mContext) {
this.mContext = mContext;
init();
}
private void init() {
mNamespaces.put(NAMESPACE_ANDROID, "android");
mNamespaces.put(NAMESPACE_TOOLS, "tools");
mNamespaces.put(NAMESPACE_APP, "app");
}
/**
* 解析apk文件
*
* @param apkFile
* @param xmlFile
* @return
*/
public CharSequence parseApkFile(String apkFile, String xmlFile) {
XmlResourceParser mParser = null;
try {
//反射获取文件cookie
AssetManager am = mContext.getAssets();
Method addAssetPath = am.getClass().getMethod("addAssetPath", String.class);
addAssetPath.setAccessible(true);
//获取文件cookie
int cookie = (int) addAssetPath.invoke(am, apkFile);
if (cookie == 0) {
return null;
}
//保存输出的xml文本
StringBuilder builder = new StringBuilder();
//生成着色字符位置列表
List<SpanText> spanTexts = new ArrayList<>();
//第一行
//builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
//创建一个节点树
Map<String, Node> nodes = new HashMap<>();
Node lastNode = null;
int level = 0;
//打开解析资源
mParser = am.openXmlResourceParser(cookie, xmlFile);
int event = mParser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
switch (event) {
case XmlPullParser.START_DOCUMENT:
Log.i(TAG, "START_DOCUMENT");
break;
case XmlPullParser.START_TAG:
if (lastNode != null && lastNode.isTagOpen) {
//关闭上一个标签开头
spanTexts.add(new SpanText(builder.length(), builder.length() + 1, COLOR_TAG));
builder.append(">\n");
lastNode.hasSubTag = true;
}
level++;
//保存节点
Node node = new Node(mParser.getName(),
mParser.getDepth(),
mParser.isEmptyElementTag(),
mParser.getAttributeCount());
lastNode = node;
nodes.put(mParser.getName() + '\0' + mParser.getDepth(), node);
builder.append('\n')
.append(makeIndent(level));
spanTexts.add(new SpanText(builder.length(), builder.length() + 1 + node.name.length(), COLOR_TAG));
builder.append('<')
.append(node.name);
int attrCount = node.attrCount;
if (attrCount > 0) {
//解析属性
if (attrCount == 1) {
builder.append(' ');
} else {
builder.append('\n');
}
for (int i = 0; i < attrCount; i++) {
String name = mParser.getAttributeName(i); //属性名称
String value = mParser.getAttributeValue(i); //属性值
String namespace = mParser.getAttributeNamespace(i); //命名空间
if (attrCount > 1) {
builder.append(makeIndent(level + 1));
}
String prefix = mNamespaces.get(namespace); //命名空间前缀
if (prefix == null) {
spanTexts.add(new SpanText(builder.length(), builder.length() + name.length() + 1, COLOR_ATTR_NAME));
builder.append(name).append('=');
} else {
spanTexts.add(new SpanText(builder.length(), builder.length() + prefix.length() + 1, COLOR_ATTR_PREFIX));
builder.append(prefix + ":");
spanTexts.add(new SpanText(builder.length(), builder.length() + name.length() + 1, COLOR_ATTR_NAME));
builder.append(name).append('=');
}
spanTexts.add(new SpanText(builder.length(), builder.length() + ("\"" + value + "\"").length(), COLOR_ATTR_VALUE));
builder.append('\"').append(value).append('\"');
if (attrCount == 1) {
//builder.append(' ');
} else if (i != attrCount - 1) {
builder.append('\n');
}
}
}
if (node.isEmptyTag) {
//标签没有元素,关闭
spanTexts.add(new SpanText(builder.length(), builder.length() + 2, COLOR_TAG));
builder.append("/>");
node.isTagOpen = false;
node.hasSubTag = false;
if (lineSpace) builder.append('\n');
}
break;
case XmlPullParser.TEXT:
if (lastNode != null) {
spanTexts.add(new SpanText(builder.length(), builder.length() + 1, COLOR_TAG));
builder.append('>');
if (!mParser.isWhitespace()) {
builder.append(mParser.getText());
}
if (lastNode.attrCount > 1) builder.append('\n');
lastNode.hasText = true;
}
break;
case XmlPullParser.END_TAG:
Node mNode = nodes.get(mParser.getName() + '\0' + mParser.getDepth()); //获取标签开始
if (mNode.isTagOpen) {
if (mNode.hasSubTag || mNode.hasText) {
//关闭双标记
builder.append('\n')
.append(makeIndent(level));
spanTexts.add(new SpanText(builder.length(), builder.length() + 3 + mParser.getName().length(), COLOR_TAG));
builder.append("</")
.append(mParser.getName())
.append(">");
} else {
//关闭单标记
spanTexts.add(new SpanText(builder.length() + 1, builder.length() + 3, COLOR_TAG));
builder.append(" />");
}
if (lineSpace) builder.append('\n');
mNode.isTagOpen = false;
}
level--;
break;
case XmlPullParser.COMMENT:
//注释
builder.append('\n')
.append(makeIndent(level + 1));
spanTexts.add(new SpanText(builder.length(), builder.length() + 9 + mParser.getText().length(), COLOR_COMMENT));
builder.append("<!-- ")
.append(mParser.getText())
.append(" -->");
break;
case XmlPullParser.CDSECT:
Log.i(TAG, "CDATA: " + mParser.getText());
break;
default:
Log.i(TAG, "event: " + event);
break;
}
event = mParser.nextToken();
}
//代码上色
if (markColor) {
SpannableString string = new SpannableString(builder);
for (SpanText spanText : spanTexts) {
string.setSpan(new ForegroundColorSpan(spanText.color), spanText.start, spanText.end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
return string;
} else {
return builder;
}
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, e.toString());
} finally {
if (mParser != null) {
mParser.close(); //关闭流,最终都会执行
}
}
return null;
}
/**
* 解析应用
*
* @param packageName
* @param xmlFile
* @return
*/
public CharSequence parseApplication(String packageName, String xmlFile) {
String apkFile = getApkFileFromPackageName(packageName);
if (apkFile != null) {
return parseApkFile(apkFile, xmlFile);
} else {
return null;
}
}
/**
* 生成缩进
*
* @param level
* @return
*/
private String makeIndent(int level) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < (level - 1) * 4; i++) {
b.append(' ');
}
return b.toString();
}
/**
* 自定义节点类
*/
private class Node {
String name; //名称
int depth; //深度
boolean isEmptyTag; //是单标签
int attrCount; //属性个数
boolean isTagOpen = true;
boolean hasSubTag;
boolean hasText;
// int row; //行
// int col; //列
public Node(String name, int depth, boolean isEmptyTag, int attrCount) {
this.name = name;
this.depth = depth;
this.isEmptyTag = isEmptyTag;
this.attrCount = attrCount;
}
}
/**
* 设置是否显示行间距
* @param lineSpace
* @return
*/
public ApkResourceParser setLineSpace(boolean lineSpace) {
this.lineSpace = lineSpace;
return this;
}
/**
* 设置是否标记颜色
* @param markColor
* @return
*/
public ApkResourceParser setMarkColor(boolean markColor) {
this.markColor = markColor;
return this;
}
/**
* 根据包名获取apk文件
*
* @param packageName
* @return
*/
private String getApkFileFromPackageName(String packageName) {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo info = null;
try {
info = pm.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (info != null) {
return info.sourceDir;
} else {
return null;
}
}
/**
* 应用是否安装
* @param packageName
* @return
*/
public boolean isAppInstalled(String packageName) {
try {
mContext.getPackageManager().getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* 此类保存上色文本信息
*/
private class SpanText {
int start;
int end;
int color;
public SpanText(int start, int end, int color) {
this.start = start;
this.end = end;
this.color = color;
}
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String WX_APPID = "com.tencent.mm";
private EditText et_xml;
private CheckBox cb_mark_color;
private ApkResourceParser mParser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bn_test1).setOnClickListener(this);
cb_mark_color = (CheckBox) findViewById(R.id.cb_mark_color);
et_xml = (EditText) findViewById(R.id.et_xml);
}
@Override
public void onClick(final View v) {
switch (v.getId()) {
case R.id.bn_test1:
mParser = new ApkResourceParser(this);
//mParser.setLineSpace(true);
mParser.setMarkColor(cb_mark_color.isChecked());
if (!mParser.isAppInstalled(WX_APPID)) {
Toast.makeText(this, "没有安装微信", Toast.LENGTH_LONG).show();
return;
}
et_xml.setText(null);
long startTime = SystemClock.elapsedRealtime();
CharSequence xmlData = mParser.parseApplication(WX_APPID, "AndroidManifest.xml");
long parseUseTime = SystemClock.elapsedRealtime() - startTime;
Log.i(TAG, "用时" + parseUseTime + "毫秒");
if (xmlData != null) {
et_xml.setText(xmlData); //ui更新很慢,由于是直接全部显示出来的,实际情况最好输出到文件中
} else {
Toast.makeText(this, "解析失败,请查看日志", Toast.LENGTH_LONG).show();
}
break;
}
}
}
demo下载地址:https://download.csdn.net/download/zzmzzff/21048893