Shadow open-source code is divided into core layer and Dynamic layer. The core layer does all the work for the plug-in framework, and the Dynamic layer makes the plug-in framework dynamic. Then the core layer itself is mainly divided into two parts, one is loader related, the other is Manager related. Loader is used to solve the core functions of the plug-in framework, such as loading the plug-in APK and starting activities that are not installed. The function of manager is to manage plug-in packages. In this article, we’ll take a look at the manager’s design for managing plug-in packages. Manager process management for launching plug-ins will be covered in another article.

InstalledApk

Let’s assume there is no Manager implementation and just use core.loader to see what parameters we need to provide when launching the plug-in. Making this scenario usable was one of our principles when designing the SDK. Therefore, there is a semi-finished sample of test-none-dynamic-host, which currently only verifies that the scenario can be compiled and started successfully. This test case will need to be enhanced in the future.

Load the plug-in entry method is com. Tencent. Shadow. Core. Loader. ShadowPluginLoader# loadPlugin, This method receives the parameter type is com.tencent.shadow.core.com mon InstalledApk. So InstalledApk is all the description given to the Loader plug-in.

InstalledApk is an immutable structure we designed that resides in the common module packaged in the host. The dynamic layer loads loader implementation and runtime implementation using this structure. We can see that InstalledApk has only four variables: apkFilePath, oDexPath, libraryPath, and parcelExtras. The first three parameters are required by DexClassLoader to load APK. After confirming these three parameters, APK can be loaded into DexClassLoader. The fourth parameter, parcelExtras, is an extended field. We serialized another Parcelable into this variable to dynamically extend the parameter. The other Parcelable is com. Tencent. Shadow. Core. Load_parameters. LoadParameters. The LoadParameters can be modified at will, just by updating the Manager and loader at the same time.

Note that the name of the InstalledApk class indicates that this is an installed plug-in. Install-free plug-ins are exempt from being installed on the system, but still need to be installed in our plug-in framework Manager. The installation of Manager plug-in is also designed to imitate the normal app installation of the system. The main job is to copy the plug-in APK to a special directory managed by the Manager, just as apK is copied to the data directory when the system is installed. Then, just like the system, extract the SO in APK and copy it. So, before constructing InstalledApk to loadPlugin, the Manager will place the plug-in APK in the host’s data directory and unpack the ABI’s SO required in the plug-in APK into the host’s data directory as well. The plug-in APK must be placed in the host’s data directory because our DexClassLoader only has permission to load files in the host’s Data directory.

LoadParameters

LoadParameters is the plug-in’s load parameter structure. This is a class that can be modified by both a dynamically implemented Manager and a dynamically implemented Loader. This structure is independent of the code in the host. Currently there are 4 parameters in LoadParameters: businessName, partKey, dependsOn and hostWhiteList.

BusinessName is the businessName. Shadow allows a Loader to load multiple plug-ins at the same time. As long as these plug-ins do not have so library conflicts, these plug-ins can be completely irrelevant to the business. Shadow ensures that Java classes are isolated between plug-ins through the ClassLoader design, but there is no way to carve out a separate memory space for Native SO libraries. In addition to the potential for code conflicts, the use of the data directory is also possible. Because Shadow works on the principle that “plug-ins are part of the host code,” all plug-ins have access to the host’s data directory. Persistent data conflicts can occur if the plug-in and host or multiple plug-ins use the same data directory logic, such as when MultiDex persists data to a fixed SharePreference. To solve this problem, the businessName is introduced. When businessName is null, Shadow assumes that the plug-in is the same business as the host, and that the plug-in uses the host’s data directory directly. When businessName is set, Shadow creates a new subdirectory in the host’s data directory with businessName as the data root directory for the plug-in. Plugins with the same businessName will use the same data directory, and the data directories of plugins with different Businessnames are isolated.

PartKey is the alias of the plug-in APK. Because the file name of a plug-in APK can change with a version number or a parameter, there is such a partKey as a constant alias for a plug-in APK. PartKey can be used to indicate that one plug-in depends on another. Shadow also uses the partKey as the Key of the APK when implementing the multi-plug-in logic. The partKey parameter on interfaces such as the Loader also refers to this partKey.

DependsOn specifies which other plug-ins the current plug-in dependsOn. To specify the dependent plug-in, fill in the plug-in partKey. If plugin A depends on plugin B, Shadow will treat plugin B’s ClassLoader as plugin A’s parent. This allows plug-in A to access plug-in B’s classes. A plug-in with a dependsOn declaration whose ClassLoader is standard parental delegation logic and does not have the ability to directly load the host class declared in a whitelist. Code in com. Tencent. Shadow. Core. Loader. Blocs. LoadApkBloc# loadPlugin tectonic PluginClassLoader in incoming specialClassLoader is null. Therefore, if plugin A depends on plugin B, the host class declared in plugin A’s hostWhiteList is invalid. HostWhiteList declarations are only valid in plugin B. This question has received feedback: github.com/Tencent/Sha… It is worth optimizing. Plug-in A dependant plug-in B, still should be able to make A Resource dependency in the plugin resources in B, it should have been in constructing plug-in A Resource object, will B as A plugin A android content. PM. ApplicationInfo# sharedLibraryFiles. Regarding cross-plug-in dependency resources, the code has not been uploaded and needs to be cleaned up. Please pay attention to the com. Tencent. Shadow. Core. Loader. Blocs. CreateResourceBloc# the create method the implementation of the change.

HostWhiteList is a parameter designed to allow plug-ins to access the host class. HostWhiteList is the package name of the Java class. A plugin that does not have a dependsOn set will have the host ClassLoader as parent, but the plugin’s ClassLoader is not the normal parent delegate logic. The plug-in ClassLoader also holds the parent of the host ClassLoader as a variable named specialClassLoader. The main path for a plug-in’s ClassLoader to load a class is to try to load it yourself and then load it by specialClassLoader. When the class to be loaded is in a hostWhiteList, the normal parent delegate is used and the parent (the host’s ClassLoader) is used to load the class. This is designed to isolate the plug-in from the host class while allowing the plug-in to reuse portions of the host class.

About SO File

So in APK can not run directly, must be decompressed to the data directory to run. This is not a limitation of the plug-in framework, and the process is the same for a normally installed App. When installing an App normally, the system will automatically determine a suitable ABI according to the current ABI of the phone during installation, and then extract the so of the specified ABI from APK to the data directory. So the plug-in framework also needs to decide which ABI to use when installing the plug-in APK. Shadow the current design, there is no automated the process, need to inherit the PluginManager subclasses Override on com. Tencent. Shadow. Core. Manager. BasePluginManager# getAbi method, Returns the ABI that needs to be unpacked.

The ABI of the plugin cannot be arbitrary like that of a normal app, because most phones are now 64-bit, and Android does not allow the mixing of 64-bit and 32-bit SO in one process. So the plugin needs to be consistent with the host on the choice of 64-bit or 32-bit. In a special case, if the host does not have any SO installed on a 64-bit phone, the system will consider it a 64-bit application. This causes the plug-in to fail to load 32-bit SO. I haven’t found a proper solution to this problem, other than having the host load a 32-bit SO first.

Decompression so method is com. Tencent. Shadow. Core. Manager. BasePluginManager# extractSo. You can see in the implementation that the so directory is determined based on the UUID, independent of the partKey. This is because we do not have the technical means to isolate SO in the same process. So the SO of multiple plug-ins loaded in the same process, without isolation from each other, is equivalent to the SO loaded by the host. Therefore, plug-in A relies on so in plug-in B without special declaration. There is A conflict between plug-in A and plug-in B, so needs to work in the same process, and the designer of SO needs to solve it by himself.

The design of config. Json

Config. json is a description of a package that can be added. Manager through the com. Tencent. Shadow. Core. Manager. Installplugin. PluginConfig# parseFromJson method converts json com. Tencent. Shadow. Core. The manag Er. Installplugin. PluginConfig object, And then through the com. Tencent. Shadow. Core. Manager. Installplugin. InstalledDao# insert method described plug-in package all the information written to persistent storage in a database. In this process, the relative path of an APK file is converted to an absolute path.

Config. json contains two parts, one is the version information of the plug-in package, the other is the plug-in APK description.

The parameters related to the version information are version, compact_version, UUID, and UUID_NickName.

Version indicates the format version of the config.json file. Compact_version indicates which formats are compatible with older versions of the current config.json file and can be used by managers that support older formats.

The UUID represents the version of the plug-in package content, and only APKs with the same UUID can work together. Apk has three types: Loader, Runtime, and Plugin. So all apKs described in the same config.json have the same UUID and therefore work together. The UUID is generated according to the general UUID generation algorithm to ensure that the plug-in version released several times will not be repeated. UUID_NickName is a common service version name that has no effect on the plug-in update logic.

It is important to note that multiple config.json can use the same UUID. Because we sometimes need to download plug-in packages in segments, download one part and start the other part first. So you can split the plug-in APK into multiple config.json files with the same UUID. Also, the Loader and Runtime only need to exist in one of the plug-in packages.

The Loader, Runtime, and Plugin descriptions have two basic information: apkName and Hash. This is designed to facilitate future implementations where the same hash may exist in config.json with a different UUID, without changing the plugin. You can then decide to reuse locally existing plug-ins across UUID based on the same hash.

For the Plugin, there are also partKey, businessName, and hostWhiteList, which function as mentioned earlier and can be set here.

The plugin package generates Gradle plug-ins

To make it easy to generate plugin packages directly without manually filling in config.json, we implemented a Gradle plugin that generates plugin packages. This is the shadow {packagePlugin {}}DSL seen in Sample. This allows you to dynamically fill in some parameters of config.json using Gradle scripts.

UUID is automatically generated when the packageDebugPlugin task is executed. If you want to reuse the previous UUID, you can specify the UUID in a UUID. TXT file in the build directory generated by the plugin package. See this code com. Tencent. Shadow. Core. Gradle. Extensions. PackagePluginExtension# toJson. It is important to note that if the UUID is fixed in the source dependent sample, the sample-plugin for updating the code will not install properly because its UUID is always the same.

The filename of the generated plug-in package zip, which can be suffixed with the PluginSuffix environment variable. See code com. Tencent. Shadow. Core. Gradle. CreatePackagePluginTaskKt# createPackagePluginTask.

As for the Gradle plug-in, the current implementation mainly meets the basic needs of our business. It seems that the design is not versatile enough and not very robust. There are several simple unit tests in projects/ SDK /core/gradle-plugin/ SRC /test. Everyone is welcome to contribute code.

Why is our plugin package a ZIP package?

In fact, a close look at all the previous designs shows that we should not have bundled config.json with all apK in a zip package. This does not make it possible to reuse APK across UUID. This is because when we were developing Shadow, the old and new plug-in frameworks ran at the same time, and there was no human effort to modify the plug-in’s distribution system. Plug-in publishing systems have always published ZIP packages. So Shadow, on top of everything covered previously, encapsulates a layer of implementation that installs plug-in packages from ZIP. Future modifications of this implementation should not affect the underlying design. Therefore, we will modify this design in the future.