Android - Overriding APK resources with external ones

Hello again, Android developers. In this new entry I will show you how to override your own app resources with external ones. This can allow for theming or directly allowing the user more customization.

The idea is basically as follows:
  1. The resources we want to replace are used in a standard way in our Android code, that is, directly defined embedded into XMLs -e.g. @drawable/my_icon_name-, or loaded through Context#getResources().
  2.  The new resources will be stored on an external storage (not inside the APK package). We will have a folder with the app package name and inside go the resources. These resources' file names will be the same as the Android ID.
  3. We will extend Android's Resources class and make our own wrapper to hook the calls and do our replacing at runtime.
  4. We will override Activity's getResources() to provide our own Resources class instead of Android's. The best way to do this is to create a new class that extends Activity and let all our Activities extend this new class instead.
So now that the procedure is clear, let's check an example implementation to override drawables for our ExternalResources class that extends Resources:

public class ExternalResources extends Resources {

    private static final String TAG = ExternalResources.class.getName();

    public static final String EXTERNAL_RESOURCES_ROOT = "/system/config/res/";
    private static final String EXTERNAL_DRAWABLE_FORMAT = EXTERNAL_RESOURCES_ROOT + "%s/drawable/%s.png";

    private final String externalDrawablePath;

    public ExternalResources(final Context context, final Resources resources) {
        super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
        this.externalDrawablePath = String.format(EXTERNAL_DRAWABLE_FORMAT, context.getPackageName(), "%s");
    }

    @SuppressWarnings("deprecation")
    @Override     
    public Drawable getDrawable(final int id) throws NotFoundException {
        final String drawableName = getResourceEntryName(id);
        Log.d(TAG, "getDrawable >> " + drawableName);
        if (drawableName == null) {
            return super.getDrawable(id);
        }

        final String bitmapPath = String.format(this.externalDrawablePath, drawableName);
        Log.d(TAG, "getDrawable >> " + bitmapPath);
        final Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath);
        if (bitmap == null) {
            return super.getDrawable(id);
        }

        // This constructor is deprecated because the documentation says: 
        // "Use BitmapDrawable(Resources, Bitmap) to ensure that the drawable has correctly set its target density"
        // This is not a solution for us because it will probably have an infinite recursion 
        // and currently external bitmaps are unique, not density-dependent. 
        return new BitmapDrawable(bitmap);
    }
}

As you can see, we just fall back to the APK drawable if no suitable external drawable is found.

Now the new base Activity class would be as follows:

public class MyActivity extends Activity {

    private static final String TAG = MyActivity.class.getName();

    /** Custom resources */ 
    private Resources resources = null;

    @Override 
    public synchronized Resources getResources() {
        if (this.resources == null) {
            this.resources = new ExternalResources(this, super.getResources());
        }
        return this.resources;
    }
}

So that is pretty much it. Now any class that extends MyActivity can have a folder with overriding resources. Any resources with the given name will be replaced at runtime by the matching resource on the folder or if this resources doesn't exist on the external storage it will fall back to the resource inside the APK.

Have fun extending this for sounds, strings and other resources!

Comments

  1. Interesting !
    I was searching for awhile to get something like that !

    ReplyDelete
    Replies
    1. Android - Overriding Apk Resources With External Ones >>>>> Download Now

      >>>>> Download Full

      Android - Overriding Apk Resources With External Ones >>>>> Download LINK

      >>>>> Download Now

      Android - Overriding Apk Resources With External Ones >>>>> Download Full

      >>>>> Download LINK oX

      Delete
  2. I gave it a try, but unfortunately, it doesn't seems to work when an Activity is using layout with setContentView()

    ReplyDelete
  3. I've figure out that it is impossible with Inflaters since TypedArray.getDrawable is bypassing any ContextWrapper and derived Resources.
    https://code.google.com/p/android/issues/detail?id=72810
    I hope Android team will fix that ASAP, but issue is opened since more than 2 years

    ReplyDelete
    Replies
    1. Then it is unlikely they will fix it. They will just mark it as "Obsolete" like this one: https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=12122)

      Delete

Post a Comment

Comment, motherf*cker

Popular Posts