The four components in the VirtualApk support plug-in were analyzed previously. This article discusses how to resolve the conflict between plug-in resource ids and host resource ids. This article covers compilation and packaging of Andoird resources. Therefore, the knowledge of this respect had better have a certain understanding. See Luo’s article on compiling and packaging Andoird resources.

Why the conflict? Why resolve resource ID conflicts?

First, the host APK and the plug-in APK are two different APKs, and both generate their own resources.arsc at compile time. That is, they are two separate compilation processes. The resource IDS in their resources.arSC must be the same. This can be a problem:

As we have seen before, when a host loads a plugin’s Resources, it actually creates a new resource that contains both the host and the plugin’s Resources. A duplicate resource id is present in a resource, and an error is reported using the resource ID to retrieve the resource at runtime.

How do you solve it?

There are two ideas:

  1. Modify AAPT source code, customize AAPT tools, modify PP section during compilation. (The PP field is the first byte of the resource ID and represents the package space)

DynamicAPK does this by customizingaAPT, replacing Google’s original AAPT, and passing in parameters during compilation to modify the PP segment: for example, the PP segment of a compiled resource passed 0x05 is 0x05. How to change the compiled resource ID value in Android

  1. The product of modifying AAPT, that is, rearrange the resources of plug-in Apk and arrange the IDS after compilation.

VirtualApk takes this approach. This article will take a look at the implementation of this scheme.

VirtualApk’s solution

After compiling apK resources, reset the resource ID in the resources. Arsc file of the plugin and update the R.Java file

For example, when you compile the plugin APk, you set:

Com. Didi. Virtualapk. Apply the plugin: 'plugin' virtualapk {packageId ID = 0 x6f / / plugin resources of PP field targetHost = '.. /VirtualApk/app' // Host directory applyHostMapping = true}Copy the code

After running the task of compiling the plug-in APK, the PP field of the resource ID of the generated plug-in is 0x6F.

VirtualApkhook ProcessAndroidResourcestask. This task is used to compile Android resources. VirtualApk takes the output of this task and does the following:

  1. Collect all the resources in the plug-in from the compiled R.txt file

  2. Collect all resources in the host APK based on the r.txt file generated by the compilation

  3. Filter plugin resources: Filters out resources that already exist in the host

  4. Reset the resource ID of the plug-in resource

  5. Delete the previously filtered resources from the plug-in resources directory

  6. Rearrange the plug-in resource ID in the plug-in resources.arsc file to the newly set resource ID

  7. Regenerate the R.Java file

So let’s look at the code. The water is deep. So the following code is just pseudo code to look at, our main goal is to understand the general implementation idea.

Take a quick look at the implementation code

According to theR.txtFile collects all the resources in the plug-in

R.t xt file is produced during the process of compiling resource resource ID of the record files, in the build/intermediates/symbols/xx/xx/R.t xt can find the problem, its format is as follows:

int anim abc_fade_in 0x7f010000  
int anim abc_fade_out 0x7f010001
.....
Copy the code

Take a look at the code:

private void parseResEntries(File RSymbolFile, ListMultimap allResources, List styleableList) { RSymbolFile.eachLine { line -> /** * Line Content: * Common Res: int string abc_action_bar_home_description 0x7f090000 * Styleable: int[] styleable TagLayout { 0x010100af, 0x7f0102b5, 0x7f0102b6 } * or int styleable TagLayout_android_gravity 0 */ if (! line.empty) { def tokenizer = new StringTokenizer(line) def valueType = tokenizer.nextToken() // value type (int or int[]) def resType = tokenizer.nextToken() // resource type (attr/string/color etc.) def resName = tokenizer.nextToken()  def resId = tokenizer.nextToken('\r\n').trim() if (resType == 'styleable') { styleableList.add(new StyleableEntry(resName, resId, valueType)) } else { allResources.put(resType, new ResourceEntry(resType, resName, Integer.decode(resId))) } } } }Copy the code

Collect all resources, such as resource names, resource ids, and resource types. Then save in the collection :allResources and styleableList

Generated by compilationR.txtThe file collects all the resources in the host APK

Same as step 1

Filter plugin resources: Filters out resources that already exist in the host

Private void filterPluginResources() {allresources.values ().each {// allResources = def index Hostresources.get (it.resourceType).indexof (it) if(index >= 0){// The plugin's resource exists in the host Hostresources.get (it.resourceType).get(index).resourceid // Set the id of this same plug-in resource to the host ID hostResources.get(it.resourceType).set(index, } else {pluginResources.put(it. ResourceType, it) } } allStyleables.each { def index = hostStyleables.indexOf(it) if(index >= 0) { it.value = hostStyleables.get(index).value hostStyleables.set(index, it) } else { pluginStyleables.add(it) } } }Copy the code

After the above action, pluginResources contains only the resources of the plug-in. This resource has no intersection with the host’s resource set, that is, no identical resource.

Reset the resource ID of the plug-in

This step is the core, the logic is simple, based on the value of the custom PP field, change the resource ID of the resource in pluginResources that has been collected above:

Private void reassignPluginResourceId() {def resourceIdList = [] pluginResources.keyset ().each  { String resType -> List<ResourceEntry> entryList = pluginResources.get(resType) resourceIdList.add([resType: resType, typeId: entryList.empty ? -100 : parseTypeIdFromResId(entryList.first().resourceId)]) } resourceIdList.sort { t1, TypeId - t2.typeId} // Reset the resource ID of the plug-in int lastType = 1 resourceidList. each {if (it.typeid < 0) {return} def typeId = 0 def entryId = 0 typeId = lastType++ pluginResources.get(it.resType).each { it.setNewResourceId(virtualApk.packageId, typeId, EntryId++) // virtualapk. packageId is the packageId we define in gradle}} List<ResourceEntry> attrEntries = allResources.get('attr') pluginStyleables.findAll { it.valueType == 'int[]'}.each { StyleableEntry styleableEntry-> List<String> values = styleableEntry.valueAsList values.eachWithIndex { hexResId, idx -> ResourceEntry resEntry = attrEntries.find { it.hexResourceId == hexResId } if (resEntry ! = null) { values[idx] = resEntry.hexNewResourceId } } styleableEntry.value = values } }Copy the code

Ok, after the above processing, the resource ids of the resources in pluginResources are the new resource ids that have been reset.

Delete the previously filtered resources from the plug-in resources directory

We may have deleted some resource ids from the plugin, but the corresponding file is not deleted, so we need to delete the file as well:

void filterResources(final List<? > retainedTypes, final Set<String> outFilteredResources) { def resDir = new File(assetDir, Each {typeDir -> def type = retainedtypes.find {typedir.name.startswith (it.name); } if (type == null) {if (type == null) { Each {outfilteredResources.add ("res/$typeDir.name/$it. Name ")} TypeDir.deleteDir () return } def entryFiles = typeDir.listFiles() def retainedEntryCount = entryfiles.size () entryfiles.each { EntryFile - > def entry = type. Entries. Find {entryFile. Name. StartsWith (" ${it. The name}. ")} the if (entry = = null) {/ / the above logic outFilteredResources.add("res/$typeDir.name/$entryFile.name") entryFile.delete() retainedEntryCount-- } } if (retainedEntryCount == 0) { typeDir.deleteDir() } } }Copy the code

Rearrange plug-insresources.arscThe plug-in resource ID in the file is the newly configured resource ID

The VirtualApk source code: arsceditor.slice ()

Regenerate the R.Java file

public static void generateRJava(File dest, String pkg, ListMultimap<String, ResourceEntry> resources, List<StyleableEntry> styleables) { if (! dest.parentFile.exists()) { dest.parentFile.mkdirs() } if (! dest.exists()) { dest.createNewFile() } dest.withPrintWriter { pw -> pw.println "package ${pkg};" pw.println "public final class R {" resources.keySet().each { type -> pw.println " public static final class ${type} {" resources.get(type).each { entry -> pw.println " public static final int ${entry.resourceName} = ${entry.hexNewResourceId};" } pw.println " }" } pw.println " public static final class styleable {" styleables.each { styleable -> pw.println " public static final ${styleable.valueType} ${styleable.name} = ${styleable.value};" } pw.println " }" pw.println "}" } }Copy the code

Welcome to Star my Android advanced plan, see more dry goods.