Android实现反编译xml资源文件

最近在更新一个文件搜索器时需要做一个解码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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值