Source: github.com/hqweb/Plugi…

The module is packaged into an APK, which is called a plug-in, and the host dynamically loads the plug-in. While plugins are old technology, the techniques used in plugins are worth learning for everyone on Android.

Implementing a simple, runnable plug-in feature is the least of these problems

  1. The host loads the plug-in into the process and can call the plug-in’s code.
  2. An activity can be started without registering with the AndroidManifest. Because the plug-in is a separate APK, the activity of the plug-in is not registered with the host.
  3. Resource conflicts. Now, this is also the most complicated one.
  4. Conflicts occur when the view version is different. You need to understand resource conflicts, which we’ll talk about

Next, resource conflicts.

  • Resources are divided into RES and assets. Res resources are usually in conflict. The wrapper assigns all the resources in the RES to a Resource ID and generates the Resource index table Resource. Arsc, Resource index R.Java. As you can see, Resource. Arsc and R. Java are related. If the plug-in uses the host’s Resource. Arsc, its own R.java, there will be a conflict. Or the plugin can have a conflict with the host’s R.java and its own Resource. Arsc. There will be no conflicts if both host and plug-in are used.

So let’s talk about these two kinds of conflicts

  1. The plugin uses the host’s Resource. Arsc and its own R.java. If you start the plug-in’s activity without doing anything special, this conflict occurs when the plug-in accesses resources. Because R.Java is already generated when it is packaged, but Resource. Arsc is loaded at runtime, and the system starts the host APK, so it is the host’s Resource.
  2. The plugin conflicts with the host’s R.java, its own Resource. Arsc. By constructing the AssetManager object, the plug-in can use its own Resource. Arsc. However, it is possible for plug-ins to use the host’s R.java. Like custom properties,TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CustomView);R.styleable.CustomView is a module that is used by plug-ins and hosts, such as system-provided views. But r.styleable.CustomView has only one copy in memory and is loaded host by default, so there is also a conflict. For them, the host application sets them by default, and their value is also an index in r.Java.

Conflicting views with different versions

  • For example, the plugin uses version 1 view and the host uses version 2 view. Although they have the same package name and class name, they use different resources. The plugin’s Resource. Because LayoutInflater’s sConstructorMap variable caches the View by the class name, it is possible that the plugin will load the version 2 view used by the host, and a conflict will occur.

How to solve the above four problems?

  1. The host loads the plug-in into the process. Apk can be loaded through a classLoader, and the classLoader that builds the plug-in can load the code for the plug-in, or change the dexElements in the host classLoader to place the classes to be loaded in front of the array. My code uses the former method.

  2. An activity can be started without registering with the AndroidManifest. There are two solutions. The first hook starts the activity’s key function and tricks the AMW. The second proxy method is to register the ProxyActivity with the host, and then expose the PluginActivity as a normal class new for the ProxyActivity to reference. In this way, ProxyActivity calls the PluginActivity function of the same name, indirectly realizing the invocation of PluginActivit. I used the first hook method. Now I will talk about the first method in detail.

Let’s start with the activity startup process

Graph TD startActivity --> AWM proxy AWP startActivity --> AWM process --> ActivityThread process --> Instrumentation newActivity --> start

Check whether the activity is registered in AWM process, but AWM can not hook, we can hook AMP, and then fake a registered StubActivity to deceive AWM, and then hook newActivity, Restore StubActivity back to PluginActivity.

  1. There are two types of resource conflicts
  • A Resource. Arsc conflict occurred. The activity loads Resources through the AssetManager in Resources. We can construct a Resources and AssetManager and override the activity’s getResources method. The plugin will then use its own Resource. Arsc.
  • R.java conflicts because the class is essentially only one copy in memory and is loaded by default to the host. You can change the parent delegate mechanism of the classLoader to allow plug-ins to load classes in their own packages. If you are not familiar with this mechanism, do a web search. So we just need to change the parent of the host classLoader to the plugin classLoader. The problem with this is that loading the class takes precedence over loading the plugin. If my host package and the plugin have the same class, then the host loading the class will load the plugin’s class, and the host will have a conflict. The solution is to start the plugin in a different process so that the plugin changes the classLoader without affecting the host package.
  • Because the index for them is already an integer passed to the plugin, changing the classLoader will not work. You can set them manually in the plugin activity.setTheme(R.style.Theme_AppCompat);
  1. Conflicts occur when the view version is different. Since the plug-in starts in another process, there is no conflict.

reference

  • Github.com/Tencent/Sha…
  • Book.douban.com/subject/303…