Fun with VectorDrawable’s (or: how to make them work everywhere)

We recently started trying to use VectorDrawable’s – this is as part of an effort to reduce and minimise app size. VectorDrawable’s are a vector image format, based on SVG. Reliably using them across multiple Android versions, and a diverse medley of devices, has proven somewhat tricky – this blog post is a compilation of what we’ve done to get them rendering acceptably and consistently.

What’s a VectorDrawable

VectorDrawable‘s are an xml vector graphics format, with some similarities to SVG. They’re much smaller than the equivalent png or webp. They are scaleable, so you only need to ship one file, as opposed to 3+ when using raster images (i.e. png or webp).

When should you use them

On supported devices (we’ve tested Android 4+), for all images up to 200 x 200 dp (Google recommend not using them for larger images to avoid performance and memory-consumption issues), so long as you are using support library 23.2 or newer OR you only support Android 5 and newer.

If your app is extremely small there’s little advantage to using VectorDrawable’s, so it’s probably best to just stick to raster images to avoid the various issues detailed below.

Where can you use them

Pretty much anywhere: on Android 5 and newer VectorDrawable’s are supported everywhere by default. On older devices you’ll still be able to get them working, as long as you’re willing to get your hands dirty.

For 4.4 and older you need support library 23.2 or newer. Officially, as detailed in the release notes, VectorDrawable’s are only supported in some components: this includes AppCompat components. We’ve had no issues using them in design support library components, including NavigationView.

But it’s also possible to load VectorDrawable’s directly – which I’ll describe below. (Note: there was a temporarily supported feature for overriding all drawable loading when VectorDrawable’s were placed within a Drawable container, but that was disabled again, apparently due to issues in handling Configuration changes, and increased memory consumption.)

Build system changes

If you’re building for Android 5+, no changes should be needed. If you’re building for lower versions with use of the support library, and using gradle, follow the release note instructions. If you happen to have your own build system that invokes aapt directly (but who would do that?), you’ll need to add “–no-version-vectors” to your aapt invocation.

VectorDrawable loading with 23.4 (and possibly 23.2 – 24.1)

Instead of loading Drawable’s via context.getResources(), you can use AppCompatDrawableManager:

-    final Drawable d = context.getResources().getDrawable(drawableID); // This only loads system-supported resources
+    final Drawable d = AppCompatDrawableManager.get().getDrawable(context, drawableID); // This loads <vector> too!

This doesn’t seem to be documented anywhere, and is therefore essentially unsupported. However it uses the exact same Drawable loading code as the rest of AppCompat, and leads to the same code paths as the (newer/official) 24.2 code. This is what we use for most of our Drawable loading.

VectorDrawable loading with 24.2 and newer

24.2 finally introduces official VectorDrawable support, see the release notes and API docs:

-    final Drawable d = context.getResources().getDrawable(drawableID); // This only loads system-supported resources
+    final Drawable d = AppCompatResources.getDrawable(context, drawableID); // This loads <vector> too!

Version specific bugs

Android 4: corrupted drawable’s (Proguard)

Screenshot of VectorDrawable's exhibiting corruption on Android 4

Plenty of corrupted drawables. You’ll never get the same corruption twice!

This can be fixed with some proguard tweaks:

-keepclassmembers class android.support.graphics.drawable.VectorDrawableCompat$* {
   void set*(***);
   *** get*();
}

-keepattributes LocalVariableTable

This issue has been filed in the Android bug tracker, it doesn’t look like a fix has been landed yet.

Android 4 and 6: missing drawable’s on 4, artifacts on 6 (Arc curves)

On Android 4, drawable’s with arc curves might randomly disappear (this is easy to test if you have a navigationview that you can hide and reopen repeatedly, e.g. in a BottomSheet context menu). On Android 6, such drawable’s can display artifacts (this is a different kind of corruption to what we saw on Android 4: on Android 6, arc curves seem to result in some points in the image being misplaced, as opposed to true corruption).

Image of icons being rendered with artifacts visible.

The bottom 3 icons all exhibit some rendering issues on Android 6

Screenshot of some VectorDrawable's not being rendered on Android 4

Some icons will occasionally disappear on Android 4 (ignore the blurry pin icon, that’s just an excessively scaled png that we’ve since replaced with another VectorDrawable)

After some experimenting, I discovered this only happens for Drawable’s with arc curves. You can check if these are present by looking for the letter “a” or “A” being present in the “android:pathData” part of your VectorDrawable. Or within the <path> if you’re looking at the source SVG.

screenshot of an editor, pointing to where an arc curve is defined

You can spot an arc curve wherever ‘a’ or ‘A’ is used in a vector path

One simple way of getting rid of these is:

  1. Import the source SVG into an SVG editor
  2. Perform an “ungroup”
  3. Export again.

Boxy SVG is fairly lightweight option for doing this. If you’re processing a large number of drawable’s, scripting inkscape might be a more efficient choice. (Make sure to double check the output, there’s no guarantee that any edits will result in purging of arc curves.)

A warning for the road

Our QA team has done some good testing, which meant that we discovered the corruption issues described above early during development. Our release audience is significantly more diverse than what we can test ourselves – and we’ve only been using VectorDrawable’s for an as-of-yet unreleased feature – so we can’t actually guarantee that there won’t be more issues in the wild. The usual culprits have all been tested (every Android version, x86 devices, Asus 4.X devices, some other odd tablets), which makes me more confident that we’ve ironed out most of the kinks.

Our first universally-visible VectorDrawable landed recently, which should let us verify that VectorDrawable’s really are truly ready to ship everywhere.

Other size reduction tips

For larger images (larger than 200 x 200dp), or even just as a quicker fix (getting SVG versions of images is laborious), I recommend looking into webp conversion. Webp provides somewhat better compression than png (although you need to be careful to not compromise image quality), with 30% size savings commonly reported. Android 4.0 only supports non-transparent images (i.e. no alpha-channel), you need Android 4.3 for full support – nevertheless we converted a fair number of images recently. I’ve written a small wiki page detailing some png optimisation commands, and webp conversion tips.

 

Tagged with: , ,
Posted in Firefox for Android