Android plug-in framework Shadow principle analysis

1. Preface :

1 Introduction:

Shadow is an Android plug-in framework independently developed by Tencent, which has been tested by hundreds of millions of online users. Shadow not only shares the key code of plug-in technology with open source, but also completely shares all the designs required for online deployment.

Compared with other plug-in frameworks on the market, Shadow mainly has the following characteristics:

  • Reuse the source code of the independent installation App : The source code of the plug-in App can be installed and run normally.
  • Zero reflection and no Hack implementation of plug-in technology : In theory, it has been determined that there is no need for compatible development of any system, and there is no hidden API call, which does not conflict with Google's strategy of restricting access to non-public SDK interfaces.
  • Fully dynamic plug-in framework : It is difficult to implement a perfect plug-in framework at one time, but Shadow makes all these implementations dynamic, so that the code of the plug-in framework becomes part of the plug-in. Iteration of plugins is no longer limited by the host packaging an older version of the plugin framework.
  • The host increment is very small : Thanks to the full dynamic implementation, the amount of code that is actually integrated into the host program is extremely small (15KB, about 160 methods).
  • Kotlin implementation : core.loader, core.transform core code is completely implemented in Kotlin, the code is concise and easy to maintain.

2. Project address

Project address: GitHub - Tencent/Shadow: Zero reflection full dynamic Android plugin framework

2. Principle analysis :

Shadow claims to have no Hook points. The core principle is to use the proxy method to change the original acitivty compilation period into a proxy class to proxy all the life cycles of the host activity.

2.1 Traditional solutions

Since it is mentioned that the feature of Shadow is that there is no hook, then we naturally first briefly talk about the method of hook is to realize componentization. The traditional way is to hook the instrumentation or classLoader, which originally converted the task of starting HostActivity into the task of starting TargetActivity, thus realizing the startup of TargetActivity in the plugin.

For specific plug-in articles, please refer to my plug-in series courses:

https://blog.csdn.net/rzleilei/category_11590922.html?spm=1001.2014.3001.5482

Are there any downsides to hooks? That's natural, otherwise Shadow wouldn't focus on promoting its hook-free feature. The biggest problem with hooks is the risk. With the update of the Android version, the previous hook point is likely to change with the version change. For example, the Instrumentation is Hooked through reflection below. Such code is OK under Android 12, but if Android 14 sets mInstrumentation as a private variable, our entire plug-in solution will fail.

 private fun checkInstrumentation(context: Context) {
        if (state.isHookInstrumentation) {
            return
        }
        state.isHookInstrumentation = true
        val myInstrumentation =
            MyInstrumentation()
        //替换Acitivty中的mInstrumentation
        val classLoader = javaClass.classLoader
        val activityThreadClass = classLoader.loadClass("android.app.ActivityThread")
        val activityThreadField =
            activityThreadClass.getDeclaredField("sCurrentActivityThread")
        activityThreadField.isAccessible = true
        val activityThreadGet = activityThreadField.get(null)

        val instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation")
        instrumentationField.isAccessible = true
        instrumentationField.set(activityThreadGet, myInstrumentation)
    }

2.2 Activity startup process in Shadow:

The above is the complete startup flow chart I drew and sorted out in the afternoon, which basically covers all the processes. The following is an explanation of the whole process.

1. First click as the starting point. We click the button to start the application. At this time, the startPluginActivity method is called. The start is to start the PluginDefaultProxyActivity that we have buried in the host, and will bring in some necessary information, such as the class name of the target class, etc. Wait.

2. Since we registered the PluginDefaultProxyActivity in the mainfest, the AMS check will pass, and then the Instrumentation will be notified to create the corresponding Activity.

3. When the PluginDefaultProxyActivity is created, the constructor of the parent class will be called. Its parent class is PluginContainerActivity. In the construction method of PluginContainerActivity, the proxy class ShadowActivityDelegate object delegate is generated, and this proxy class maintains the relationship between the host and the implementation class.

3. After the instrumentation of the system is created, the onCreate method of the Activity will be called. Since there is no implementation in PluginDefaultProxyActivity, the system will call the onCreate method of its parent class PluginContainerActivity.

4. In the onCreate method of PluginContainerActivity, the targetActivity (generated according to the passed class name and other information) will be created through the previously created delegate object. The targetActivity is our target Activity, which contains our normal business logic.

5. After the object is created, the onCreate method of targetActivity will be called. In our target Activity, there will naturally be some normal usage logic. Such as setContentView() and so on.

6. Here is an example of setContentView. Calling the setContentView method in TargetActivity will actually call the setContentView method in its parent ShadowActivity.

7. Some people will naturally ask here. Isn't our normal TargetActivity inherited from Activity? Wouldn't it be very troublesome to change the parent class to ShadowActivity? Here Shadow uses the technology of bytecode instrumentation, that is, when the APK is packaged, it will automatically replace it for us.

8. In the onCreate method of ShadowActivity, the setContentView method of the real host Activity will be notified through the proxy class.

2.3 How Shadow loads the dex in the plugin

It is the normal use of DexClassLoader to load.

new DexClassLoader(apkFile.getAbsolutePath(), oDexDir.getAbsolutePath(), null, ODexBloc.class.getClassLoader());

2.4 How Shadow loads resource packs

  val packageManager = hostAppContext.packageManager
        packageArchiveInfo.applicationInfo.publicSourceDir = archiveFilePath
        packageArchiveInfo.applicationInfo.sourceDir = archiveFilePath
        packageArchiveInfo.applicationInfo.sharedLibraryFiles = hostAppContext.applicationInfo.sharedLibraryFiles
        try {
            return packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo)
        } catch (e: PackageManager.NameNotFoundException) {
            throw RuntimeException(e)
        }

Also generated via the normal packageManager. But it generates a new Resources, which is different from the Resources normally used in the host. Since there are different Resources in the plugin and the host, there is no problem of resource ID conflict.

3. Personal opinion

3.1 User experience

It has been used for a few months without any major problems, and the stability is still good.

3.2 Advantages

1. No hook point, it will be a long-term stable state.

2. There will be no conflict between resource IDs, which is especially friendly to special models such as Samsung.

3.3 Disadvantages

1. Because it is an instrumentation mode, the style attribute in the Activity is ineffective. It needs to be set in the Activity code.

2. Shadow should be componentized, so the plug-in application must be connected to the Shadow component for a certain transformation before it can be connected normally.

3. The cost of access is still high. For many functions, you need to understand their principles to use them normally, and one-click access is not yet possible.

Guess you like

Origin blog.csdn.net/AA5279AA/article/details/121103090