Skip to content

Android dynamic load resources

galen edited this page Jan 1, 2016 · 6 revisions

Android resources are loaded by AssetManager.

While android application launched, it creates an AssetManager instance and add resources search paths by addAssetPath.

By default, it adds:

  1. "/framework/base.apk" - Android base resources (base)
  2. "/data/app/*.apk" - The launching apk resources (host)

As we know, android resources are visited by an unique index with format 0xPPTTNNNN.

  • PP - the resource package id (00-02: system, 7f: application, 03-7e: reserved)
  • TT - the resource type id (types are 'attr', 'layout', 'string' and etc.)
  • NNNN - the resource entry id

To avoid id conflicts, the resources id in host and plugins should be partitioned.

There are some ways to do this:

  1. Create a self-contained asset manager for each plugin.
  • ❌Plugins can not visit to each other.
  1. Add all plugins' asset path to one asset manager (the host one).
  2. Arrange the entry ids (NNNN) by public.xml - ⚠️ High cost of maintenance.
  3. Arrange the type ids (TT) by public.xml - ❌Plugins can not visit to each other.
  4. Arrange the package ids (PP) 1. Modify aapt source code
    • ⚠️ High cost of maintenance. 2. Modify the products of aapt (binary resources.arsc and *.xml)
    • ✅Seamless, and the ability of ultimate slicing.

Now, out final plan is to repack android asset package and reset package id of it's resources.

Repack android asset package

First of all, unzip the package file resources.ap_, get files:

AndroidManifest.xml
resources.arsc
res
  `-- layout
       `-- activity_main.xml
Repack resources.arsc
chunk type note
Table header RES_TABLE_TYPE = 0x002
Resource strings RES_STRING_POOL_TYPE = 0x001 e.g. 'Hello World!'
Package header RES_TABLE_PACKAGE_TYPE = 0x200 Rewrite entry: package id
Type strings RES_STRING_POOL_TYPE = 0x001 e.g. 'attr', 'layout', etc.
Key strings RES_STRING_POOL_TYPE = 0x001 e.g. 'activity_main', etc.
DynamicRefTable RES_TABLE_LIBRARY_TYPE = 0x0203 Insert entry for Android 5.0+
Type spec RES_TABLE_TYPE_SPEC_TYPE = 0x0202
Type info RES_TABLE_TYPE_TYPE = 0x0201 Rewrite entry: resource entry value

By the way, in Android 5.0+ needs to set the dynamicRefTable for lookup bag parent.

What's the bag and bag parent?

For example, we made a plugin with package id 0x34, and defined themes in it's styles.xml:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">..</style>
<style name="AppTheme.NoActionBar">..</style>

And then after aapt executed, we got a bag tree as following: (run aapt d --values resources *.apk to see more)

Theme.AppCompat.Light.DarkActionBar
    `-- AppTheme <bag> (id: 0x34050001)
          `-- AppTheme.NoActionBar <bag> (id: 0x34050000)

So AppTheme.NoActionBar's parent is AppTheme, but in ResourcesType.cpp, If has not pre-define dynamicRefTable (declare what 0x_34_ is), while looking up the parent of AppTheme.NoActionBar (0x_34_050000) it would abort and never find AppTheme and Theme.AppCompat.Light.DarkActionBar.

The result is that, if your activity is an instance of AppCompatActivity but cannot apply the AppCompat Theme, then a crash raised.

Let's see the codes:

ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
        uint32_t* outTypeSpecFlags) const
{
    ...
        bag_entry* cur = entries+curEntry;

        cur->stringBlock = entry.package->header->index;
        cur->map.name.ident = newName;
        cur->map.value.copyFrom_dtoh(map->value);
        status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value);
        if (err != NO_ERROR) {
            ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data);
            return UNKNOWN_ERROR;
        }
    ...
}
status_t DynamicRefTable::lookupResourceValue(Res_value* value) const {
    ...
    status_t err = lookupResourceId(&value->data);
    if (err != NO_ERROR) {
        return err;
    }
    ...
}
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
    uint32_t res = *resId;
    size_t packageId = Res_GETPACKAGE(res) + 1;

    if (packageId == APP_PACKAGE_ID) {
        // No lookup needs to be done, app package IDs are absolute.
        return NO_ERROR;
    }

    if (packageId == 0) {
        // The package ID is 0x00. That means that a shared library is accessing
        // its own local resource, so we fix up the resource with the calling
        // package ID.
        *resId |= ((uint32_t) mAssignedPackageId) << 24;
        return NO_ERROR;
    }

    // Do a proper lookup.
    uint8_t translatedId = mLookupTable[packageId];
    if (translatedId == 0) {
        //--------------------------------------------------------
        // Lookup Code Block
        // 0x52 goes here
        //--------------------------------------------------------
        ALOGV("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
                (uint8_t)mAssignedPackageId, (uint8_t)packageId);
        for (size_t i = 0; i < 256; i++) {
            if (mLookupTable[i] != 0) {
                ALOGV("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
            }
        }
        return UNKNOWN_ERROR;
    }

    *resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24);
    return NO_ERROR;
}
Repack *.xml
chunk type note
Xml header RES_XML_TYPE = 0x0003
Attr ids RES_XML_RESOURCE_MAP_TYPE = 0x180 Rewrite entry: each attr id
Xml node RES_XML_START_ELEMENT_TYPE = 0x102 Rewrite entry: node attribute value