The author

Hello everyone, my name is Xiao Xin, you can also call me crayon xiao Xin 😊;

I graduated from Sun Yat-sen University in 2017 and joined the 37 mobile Game Android team in July 2018. I once worked in Jubang Digital as an Android development engineer.

Currently, I am the overseas head of android team of 37 mobile games, responsible for relevant business development; Also take care of some infrastructure related work.

background

In the process of plug-in, the host needs to use the resources of the plug-in, which involves loading the resources of the plug-in.

Because plug-ins exist as APKS, the ID of the plug-in and the ID of the host can cause duplication;

To solve this problem, the resource ID of the plug-in needs to be rearranged before being loaded and used by the host.

Resource to load

Resources in Android projects are routinely indexed by R files. Aapt maps project resource names and ids into R files when packaging. Resources are obtained through Resources, as in:

resources.getDrawable(xxxid)
Copy the code

So how do you load resources in plug-ins? Constructor (Resources);

public Resources(AssetManager assetManager, DisplayMetrics metrics, Configuration config) {}Copy the code

Initialization of Resources depends on: AssetManager, DisplayMetrics, and Configuration.

There’s a method in AssetManager

public int addAssetPath(String path)
Copy the code

This method can be used to add external resources to AssetManager, but it is not public in AssetManager and needs to be called by reflection. The code looks like this:

val method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
method.isAccessible = true
method.invoke(hostAssetManager, apk)
Copy the code

Resource conflict scheme

After the above processing, the plug-in’s resources and the host’s resources are mixed together because all resource indexes are generated sequentially in 0x7FAabbbb format without processing. Therefore, there will be a conflict between the plug-in resource ID and the host resource ID (note: the resource index has four bytes and 32 bits, the first byte representing PackgeID, the second byte representing TypeID, and the last two bytes representing the resource value).

How to resolve conflicts? There are several options:

Solution 1: Resource isolation

Resource isolation means that the host and the plug-in use different Resources objects, so that the resource files are used differently and there is no conflict. Code adjustments are as follows:

// Create a new instance of AssetManager by reflection
val am = AssetManager.class.newInstance()
// Add resources
val method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
method.isAccessible = true
method.invoke(am, apk)
// Generate a new Resources object
val pluginResources = Resources(am, hostResources.displayMetrics, hostResources.configuration)
Copy the code

Plug-ins use this PluginResources. But there are some downsides:

1. The resources of the host and the plug-in cannot be shared, making it inconvenient to use

PluginResouces do not contain system resources and may cause errors in certain scenarios where system resources are used. For example, some system resources are used when loading front-end pages and using select tags

3. In some business scenarios, the Resources of the plug-in and the Resources of the host need to be the same. For example: game release SDK, SDK-related interface is not an activity, the Resources used is the game developer activity (in the host).

Solution 2: Change the resource ID

To change the resource ID, the following methods are commonly used:

Option 1: The Android resource ID is generated by AAPT. Modify AAPT so that the resource ID in the plug-in does not start from 0x7f, for example, 0x6f

Solution 2: After the plug-in APK is generated, modify the resources. Arsc file in the plug-in APK. The resources.arsc file is a fixed format file

You can modify the resource index value by parsing this file. However, there are certain defects:

1, modify difficult, need to understand resources. Arsc format

2. It is not enough to modify resources.arsc because the code uses class R, and you also need to modify the values in the code.

Note: Although it is difficult to change, but there are still big god to fix, specific stamp here >>>

Option 3: Decompilate to change the index value in the public. XML file in the plug-in and change the R class value in smali

Where did the public. XML file come from?

This file is generated when apkTool decommounts APK from the resources.arsc file in the APK package. Haven’t seen the resource. Arsc? (Drag apK to IDE yourself)

What does public.xml do

Publc.xml is used by AAPT to package resources with fixed resource IDS. If the resource has a corresponding ID in public. XML, it is packaged with the existing ID.

The format of the ID in public.xml

The first byte represents PackgeID, the second byte represents TypeID, and the last two bytes represent the resource value

The usual system resource PackageID is 01, while our own resource PackageID is 7F

TypeID, for example attr is 01 and string is 02. But it’s not fixed, it doesn’t have to be attR is 01. But in public.xml, the byte of the same type must be the same, otherwise the backcompile will fail.

R class

The value of the R class generated by the Library module is not constant and does not have final values. The value of class R generated by the APP module is a constant value. Constant values are optimized during Java compilation, and the resulting code outputs constant values instead of r.I.D.xx. Library’s, since they are variables, will not be optimized and will keep r.i.D.xx in the code

Relationship between R class and public.xml

In essence, it doesn’t really matter. But since in the code we use R.ID to find the resource, it’s relevant. If you use getIdentifier to obtain the id first, you can delete the R class.

The public.xml package corresponds to the values in Resources.arsc, and the resource values generate Java classes, which are called R classes. Arsc = resources. Arsc = resources. Arsc = resources.

The modification procedure is as follows:

1. Decompile the plug-in APK

2. Modify public. XML to increase the value of AA in 0x7faabbbb, such as 0x7F010001 → 0x7F510001, because the second byte is TypeID, which is added in order, and there is not much of it. If you change it to a larger value, it won’t conflict. (See the PublicXmlBean class implementation in the provided code for more details.)

3. Modify the value of R$x.mali. After decompiling the R class is some SMali codes, scan the SMALI codes and increase the values in them according to the same rules. So class R and resources.arsc are still corresponding, and the R class in the code is fine. However, it is important to note that the application module’s R class is treated as a constant because it is a final constant value. In this case, modifying class R doesn’t really work. Library does. Therefore, code and resource files involving R need to be sunk as Library module dependencies.

4, back compile, get modified resource index after the plug-in

Code stamp here >>>

Introduction to source code Project

The profile

Program entrance

The plugin generates tips

Plugin source code project plugin apK structure (plugin.apk) :

The actual code sinks down to resModule as library module, app module has no actual function, just reference library as application module, so we can output our plugin plugin.apk. The whole point of doing this is to keep our code from being constant in our app module.

The effect

The value of r.leyout. activity_main is 2131296284, which is converted to hexadecimal 0x7F09001C

The value of r.leyout. activity_main is 2136539164 (0x7F59001C)

conclusion

This article introduces a variety of options for plug-in resource loading, which can be selected according to your own usage scenarios and your team’s technical capabilities

Welcome to communicate

Students who have problems or need to communicate in the process can scan the TWO-DIMENSIONAL code to add friends, and then enter the group for problems and technical exchanges, etc.