Given byte-array of VectorDrawable, how can I create an instance of VectorDrawable from it?

被刻印的时光 ゝ 提交于 2019-12-31 05:13:09

问题


Background

My app finds APK files in the storage of the device, showing information about them and allowing to perform operations on them.

The problem

Thing is, ever since Google added new restrictions about storage, I'm trying to find solutions about how to parse APK files without storage permission (because the framework can handle only file-path). For now I use the special legacy flag (requestLegacyExternalStorage) to allow me to do it with file-path, but it's a temporary solution till the next version of Android.

What I've tried and found

Currently, using a workaround and a the library (link here), I succeeded getting the basic information and the app name, but for icons it becomes messy : wrong qualifiers, can return PNG instead of VectorDrawable, and the VectorDrawable is just a byte array...

But even if I ignore its issues, I tried to look at the various VectorDrawable functions and also its android-x ones (which actually looked very promising), to try to create an instance out of what I got. For some reason, each function I tried didn't help.

The question

Is it possible to load VectorDrawable dynamically from byte code? For now I'm planning to use this library which parses APKs on its own (because sadly Google is planning to add restrictions of reaching files, to use the terrible SAF), but sadly for VectorDrawable it just has a byte array...

Just to be clear: I'm talking about accessing the APK files (not installed ones, as getting this information still works fine) from SAF. With storage permission it worked fine (and of course with installed apps). See here. When you try to use SAF with the normal framework, you will get bad results: a string with a package name as the app-name, and the default icon as the app-icon (or worse: an exception). Also note: I know I can copy the files to my own app's storage and use just the framework, but this isn't a good solution, because if I want to show information about all APK files on the device, it would be a waste of space and time, just to get the icons and app-names... I need a more direct approach. One that is fastest for the app to handle.


回答1:


If the byte array of the vector drawable you have has a binary XML form, then you should be able to create it by passing the array to the following method I took from my previous answer:

/**
 * Create a vector drawable from a binary XML byte array.
 * @param context Any context.
 * @param binXml Byte array containing the binary XML.
 * @return The vector drawable or null it couldn't be created.
 */
public static Drawable getVectorDrawable(@NonNull Context context, @NonNull byte[] binXml) {
    try {
        // Get the binary XML parser (XmlBlock.Parser) and use it to create the drawable
        // This is the equivalent of what AssetManager#getXml() does
        @SuppressLint("PrivateApi")
        Class<?> xmlBlock = Class.forName("android.content.res.XmlBlock");
        Constructor xmlBlockConstr = xmlBlock.getConstructor(byte[].class);
        Method xmlParserNew = xmlBlock.getDeclaredMethod("newParser");
        xmlBlockConstr.setAccessible(true);
        xmlParserNew.setAccessible(true);
        XmlPullParser parser = (XmlPullParser) xmlParserNew.invoke(
                xmlBlockConstr.newInstance((Object) binXml));

        if (Build.VERSION.SDK_INT >= 24) {
            return Drawable.createFromXml(context.getResources(), parser);
        } else {
            // Before API 24, vector drawables aren't rendered correctly without compat lib
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            int type = parser.next();
            while (type != XmlPullParser.START_TAG) {
                type = parser.next();
            }
            return VectorDrawableCompat.createFromXmlInner(context.getResources(), parser, attrs, null);
        }

    } catch (Exception e) {
        Log.e(TAG, "Vector creation failed", e);
    }
    return null;
}

I haven't tested it but I don't see a reason why it shouldn't work if your byte array really is the binary XML of a vector drawable, which is what the XmlPullParser and VectorDrawable expects to parse.

EDIT: I tried using XmlPullParser to create the drawable like this:

val xml = """
|<vector xmlns:android="http://schemas.android.com/apk/res/android"
|    android:width="24dp"
|    android:height="24dp"
|    android:viewportWidth="24.0"
|    android:viewportHeight="24.0">
|    <path
|        android:pathData="M9.5,3A6.5,6.5 0,0 0,3 9.5A6.5,6.5 0,0 0,9.5 16A6.5,6.5 0,0 0,13.33 14.744L18.586,20L20,18.586L14.742,13.328A6.5,6.5 0,0 0,16 9.5A6.5,6.5 0,0 0,9.5 3zM9.5,5A4.5,4.5 0,0 1,14 9.5A4.5,4.5 0,0 1,9.5 14A4.5,4.5 0,0 1,5 9.5A4.5,4.5 0,0 1,9.5 5z"
|        android:fillColor="#000000"/>
|</vector>
""".trimMargin()

val parser = XmlPullParserFactory.newInstance().newPullParser()
parser.setInput(xml.reader())

val drawable = Drawable.createFromXml(resources, parser, theme)
iconView.setImageDrawable(drawable)

However this fails with an exception: java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser. So this is not possible after all to answer your comment.



来源:https://stackoverflow.com/questions/59022749/given-byte-array-of-vectordrawable-how-can-i-create-an-instance-of-vectordrawab

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!