Plug-in framework can load a single business module to achieve the purpose of module update without reinstalling the main program. The whole loading and updating process is also unconscious to users.
Because of this, the coverage rate and speed of new requirements will be higher and faster than the traditional update method. For large development teams, each business module development team does not need to wait for the unified release version of all groups’ requirements, and the release version can be released separately for a single function within the group. These advantages have made plug-in frameworks so popular over the past year.
At present, the mainstream plug-in technology core popular on the Internet can be divided into two categories:
One is DroidPlugin, which is open source of 360 Company. It creates a pure environment on the host program, allowing ordinary APKS (plug-ins) to be used normally without installation. In addition, resources and codes before plug-ins are independent of each other without interference or access. The framework hooks a large number of system apis.
The other is from dynamic-load-APK, which combines the plug-in code and resources with the host and calls each other by reflecting a small amount of API. At present, most frameworks combine the code and resources from the bottom in the same way, but each framework has a different understanding of plug-in based on this. For their respective projects to do a lot of business packaging.
Common class loading methods are:
The common ways to load resources are:
So far, how to load code, how to load resources, how to dynamically declare and launch Android components. Most of the open source frameworks on the web have solved the problem of how to do this, and do it perfectly from zero to one. But for a large app with tens of millions of developers, solving these problems alone isn’t enough. The stability of the plug-in framework, the cost of migrating from original code to plug-in, the cost of late maintenance, and so on all need to be considered. Therefore, compatibility, migration cost and later maintenance cost are the most basic considerations in plug-in selection.
First of all, compatibility and later maintenance costs. As we all know, due to the fragmentation of Android system, apis provided by Android also have many compatibility problems. Moreover, for domestic mobile phone manufacturers with such strong innovation ability, domestic mobile phones also have many additional compatibility problems.
Specific example: The commonly used resource loading method cannot be used in some mobile phones of Vivo, because its ROM encapsulates the Resources of the system into VivoResources, which directly leads to the failure of reflection and plug-in Resources cannot be loaded. The same is true for some mobile phones of Nubia. So based on this when doing plug-in hook system API should be as little as possible, because the HOOK API uncertainty is too much, and in this part of the development process will certainly not have any documentation to provide reference, encounter problems on the dry masturbate code.
The more work it takes to deal with compatibility, the higher the later maintenance cost will be. At least if a new version of Android comes out, the first thing to see is whether the official API of previous Hook has been changed. If there is a problem, I have to seek a new implementation for the new version, which is very heavy workload. This is also an important reason why we do not choose DroidPlugin. From all the information we can find on the Internet, we do not see how much compatibility of DroidPlugin can be adapted to how many mobile phones. However, it is expected that DroidPlugin hook has a large number of APIS compared with other framework hooks, and the subsequent maintenance cost of this part is also enough to drink a pot.
Migration cost, in fact, many large projects to achieve plug-in, in the process of adjustment of the code structure, call logic and so on the modification is certainly some. One of our concerns was how to keep the changes to a minimum because changes can cause bugs. Fortunately, we started with the package script and implemented the module plug-in without changing the traditional project structure and logic calls. Let plugins run first, and after implementation, let business groups refine and encapsulate the protocols and conventions between the plug-in and the host for the proposed plugins.
Plug-in migration process:
First, take a look at our original base project structure:
At the bottom of the project is a public library that provides most of the common basic modules. Kugou serves as the application as the main program, and other listening, reading, singing and other business modules also serve as a library. Each business group is associated with the public module and the main program of Kugou, which are developed and debug-ed under their respective business modules. When it’s released, it’s unified on the packaging platform to have Cool dog associate all the business modules and then unified packaging. This is the most common project component architecture. Business modules have project-level code separation and business projects rely on a common base.
Project optimization objective
After optimization, the novel way of development completely the same, before the project structure compared to optimize ago intact, packaging after each business module is each plug-in can be loaded separately, each plug-in is only contains its own resources and the plugin code (do not include the public library), plug-in can be normal access to a host of resources and the code, As long as the host retains the resources and code required by the plug-in, the plug-in can be started no matter how the host changes.
The main problems to be solved in this process are as follows:
- A packaged plug-in retains only the code of the plug-in itself, and the plug-in can call back to the host logic without changing any calling logic.
- Plugins can only retain their own resources, and plugins can access the host resources.
- How to ensure that the old host can support the new plug-in after recompiling.
First of all, how to separate the business module that depends on the underlying project into a plug-in package and only keep the code of the business module
Let’s take the listening module as an example and compile the host program, which is the Cool Dog project and the underlying base library, all the way to the javac where the main project resources r.Java and the mapping tables are available, Then package the compiled class into common.jar and copy the Common project resources into an empty shell project, CommonRes. Then change the properties of the listener project from a library to an application, and associate it not directly with the base library, but with commonn.jar and Commonres, where common.jar provides compilation.
By doing this, the apK compiled by the listening project contains only its own code.
Next, solve the resource problem, the normal resource lookup method
Application layer id directly to get access to Resources is to use Resources, Resources according to our id to first is the name of the map to find the Resources to get the Resources of the resource name is not corresponding to the file only needs to perform conversion from a resource id to the resource name, and due to the file Resources also need to open the corresponding file according to the resource name. After reflection, resources contains multiple mapping tables in the directory. When searching, it will look up the host first and then each plug-in mapping table in order.
Resource conflict problem because after the above project structure adjustment, the plug-in and the host will have the same resource ID when the application is compiled. When the plug-in searches for resource ID, it may find the resource of the host. So you can resolve the conflict by simply changing the resources. Arsc and the r.Java ID used by the code layer.
If you want to change the resource id of the plugin, you can change the resource ID of the plugin. If you want to change the resource id of the plugin, you can change the resource ID. As long as the plugin code calls the same resource ID in r.java, change it to the host resource ID, resource lookup will be successful to the host resources. Arsc resource lookup.
Finally, delete the extra resource ids and extra resource files for the plugin resources.arsc. The result is a plug-in package that contains only the plug-in code and plug-in resources and has arbitrary access to the host resources and code.
Finally, let’s look at the overall compilation process.
Compatibility issues with wechat resource obfuscation tools
The plugin tool mainly modifies ids and removes other redundant resources at compile time. The resource obfuscation tool mainly shortens names and paths without changing ids, so there is no conflict. Just make sure that all reads and writes are written strictly in the resources.arsc format.
Then there is the last question, how to ensure that the old host can support the new plug-in after recompilation, simply put, how to do multiple programs together to make code confusion, how to maintain the host resource ID.
Multiple items mixed up:
We chose unity to do obfuscation, why can’t we first obfuscate the whole project and then obfuscate the second project using the mapping of the previous project and continue to obfuscate and compile like this?
Primarily because there is no fixed interface between the plug-in and the host common library, obfuscating methods that were previously directly associated with calls can be obfuscated and removed. Remember that the plug-in module and the Common Base module are directly related and called. Later we changed the project structure to make the plug-in separate, but this part of the call still exists.
Once they are separately obfuscated, the code associated with them is removed, and the final static variables of the host public library are obfuscated and gone, and the plug-in cannot call them.
In fact, normally, the calls between the host and the plug-in need to have a fixed interface to decouple all the calls, the host provides a complete set of APIS for the plug-in to use, and then keep their boundaries when confused. This is the most correct way to update and manage subsequent plug-in versions. Why this problem until now chat, set for each business module to the plugin and then to encapsulate interface, such as their good decoupling package to do it too long, so let’s use this way to let them don’t need to do any packaging and decoupling can, follow-up and ask them to slowly the specification of this part of the interface.
How to keep resource problems
We know that resource ids are generated randomly according to resource names. Once a resource name is added or modified, all resource ids may change. If the resource ID cannot be fixed and changed every time it is compiled, the plugin cannot be distributed to the user.
The solution is to compile ids. XML and public. XML in the /res/value directory of the host next time and keep the id unchanged, even if other resources of the host change next time. As long as all the resources used by the plugin are not changed, the new plugin can be used by the old host.
At this point, a completed plug-in package is out, and all that remains is to follow the basic loading method and load the plug-in into it.
Finally, implement plug-in management functionality in the host, which is pure basic business logic.
- Download the verification plug-in differential package. (We generate a new plug-in package last time we visit the server, the server will make a difference comparison with the original plug-in, and then generate a file-level difference file and send it to the user)
- Merge difference packs to compare plug-in versions.
- Check blacklist and whitelist before loading. (Some plug-in versions must be forced to load, others must not load)
- Load the plug-in resource mapping table at startup. (To ensure that a start can be queried to all resources, and this reflection efficiency is very high, not time-consuming, also do not consume memory speed is very fast).
- Plug-in code selects the right time to load lazily. (It takes time to load dex, including opt below 5.0 and OAT above 5.0, and the time is not short, so lazy loading should be carried out at an appropriate time.)
Finally, this article is mainly about the solutions to some problems we encountered in the plug-in process. In fact, every solution has its own trade-offs, and no one is better than the other. If you have a better solution, please leave a comment below.