最近在更新一個檔案搜索器時需要做一個解碼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
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/293762.html
標籤:其他
下一篇:Android的java的報錯提示:java.lang.RuntimeException: Fail to connect to camera service
