Series index
Concurrent series: Thread locking
-
Why does CountDownlatch guarantee execution order?
-
Why can Concurrent Containers achieve efficient concurrency?
-
From ReentrientLock to see the correct use of lock posture
New series: Android11 system source code analysis
-
How to download Android source code for Mac environment?
-
Android11 Source code analysis: How does the application start?
-
Android11 source code analysis: How to start the Activity?
-
Android11 source code analysis: Service startup process analysis
-
Android11 source code analysis: Static broadcast is how to receive notifications?
-
Binder cross-process Implementation (In process of creation)
-
How do plug-in activities start?
-
Android11 source code analysis: why in the end will BE stuck UI?
-
How does SurfaceFlinger distribute vsync signals? (In process of creation)
Classic series: Android10 system startup process
-
Source code download and compilation
-
Overview of the Android system startup process
-
Init process source parsing
-
Zygote process source code analysis
-
SystemServer source code parsing
preface
After analyzing the source code of the four components of Android, we will talk about a very relevant topic: plug-in!
The complexity of plug-ins is that if a complete APK wants to be loaded into another APK as a plug-in, three core requirements need to be met
- Load the code in the plug-in, the class class
- Load the resource (resource) in the plug-in
- Implement support for calls to the four major components
Today we are going to analyze these three core requirements
How do I load plug-in classes?
Loading of plug-in classes
To implement the plug-in class load, we first need to know how our normal application class load
Context.getclassloader (); ContextImpl; LoadedApk; getClassloader(); Obtain the corresponding classLoader based on the ApplicationInfo generated after the ActivityThread is created. That is, load the corresponding dex based on the APK path
We need to understand that when we can call the classes we created in the process, as well as the class objects in the system, we can only use the JVM to map the statically compiled classes into memory by using the classLoader, and find the classes we compiled in the dex by using the classloader. That’s what we’re going to do now
The loading mechanism of the Classloader is to search for the parent class. If it cannot find the parent class, it will search for the current Classloader
Loading plug-in classes can be implemented in two ways:
- Isolation method
To implement isolation, we can use context.getClassLoader().getSuperClassLoader () to get the top-level ClassLoader. When we create a customized DexClassLoader and pass it into the dex directory of the plug-in, And pass in the SuperClassLoader so that when we create multiple plug-in classLoaders, they are isolated from each other and cannot access each other
- Mixed way
The hybrid approach is to use the context.getClassLoader() object — the host classloader– as the parent loader and pass it in as the parent parameter when creating our plug-in’s DexClassLoader. This way, when we use the plug-in’s classloader, we can call both the current plug-in’s classes and the classes in the host
The two methods have their own advantages and disadvantages. You can implement your own loading mechanism according to your business needs
In my experience with plug-in development, the second hybrid mode is used because the business scenario is not complex and there is only one plug-in
summary
Loading the plug-in class is not complicated, we just need to get the dex generated during compilation (or apK generated by compilation can also be used) file, and use the DexclassLoader created by ourselves to load
Another problem to deal with is the relationship with the host class ClassLoader. If mixed mode is used, the host classLoader can be used as the parent, and if isolation is needed, the host classLoader’s top-level parent (i.e. Bootclassloader) can be used as the parent. In this way, each plug-in and host has its own separate Classloader to load
How to load plug-in resources?
Loading plug-in resources
For the loading of plug-in Resources, the main core classes involved are AssetsMananger and Recources. When using it, we can call getResources() function of the host context to get the Resources object
On Android5.0 and above you can get the AssetManager object from Resources by calling getAssets(). So AssetsMananger and Recources are combinatorial relations () by holding AssetsMananger objects in Recources
There are also two options for managing plug-in resources
- Mixed way
As mentioned, the host context can be used to retrieve the reources and AssetsMananger, at which point we can call addAssetPath() passed to our plug-in apk’s path to load the plug-in’s recoures resource
Pay attention to the adaptation for each mobile phone manufacturer. The addAssetPath() function of Resources may fail to be called because each vendor has its own subclass implementation of Resources, such as MiuiResources
Here, different Resources objects need to be created for different vendors, and the AssetsMananger obtained from the host side is passed to Resources as a parameter to realize the combination of host Resources and plug-in Resources
When we need to get Resources, we just need to get the host and plug-in Resources through our newly merged Resources object
- Plug-in resource independence
If you use a plug-in resource-independent scheme, you simply create the AssetsMananger by reflection, call addAssetPath() to load in Resources from the APK directory, and create a Resources object that holds a reference to that AssetsMananger
Handle resource ID conflicts
When loading resources in a hybrid manner, the problem of conflicting resource ids can arise because plug-in compilation and host compilation are compiled separately
The tool for compiling resources is AAPT, so in the earliest scheme, we use the method of modifying the source code of AAPT to generate the plug-in Resourse with different ID fields during compilation (the default compilation ID is 0x7fXXXX, only need to ensure that the previous ID segment is not repeated).
Now you can customize the resource ID section by using Gradle plugins. You don’t need to modify aAPT source code. You just need to add some code to Gradle files
aaptOptions {
additionalParameters "--allow-reserved-package-id", "--package-id"," 0x50"
}
Copy the code
summary
Loading Resources mainly involves Resources and AssetsMananger. You can obtain the corresponding AssetsMananger by using context.getAssets()
We merge the plug-in and host resources by calling addAssetPath() on the same AssetsMananger object to add the plug-in resources. Another thing to pay attention to is the adaptation of each manufacturer
At this point, class loading and resource loading schemes for plug-ins are introduced
How to launch a Activitiy plugin?
Once the core functions are done — classes and resources are loaded — we want to be able to use the four major components in the plug-in just like normal application development
How do you start an activity for a plug-in
The process is briefly
When we want to start a normal activity, we first register it in the manifest file, then call startActivity() in the context, pass an Intent object, and set the class object for the activity we want to start. This class object will eventually be instantiated by the Instrumentation object in the ActivityThread via reflection
The only way to start an activity is to register it in a manifest file. There is obviously no way for an activity in a plug-in to be registered in a manifest file, and the host necessarily does not know which activity objects are implemented in the plug-in
The solution
The solution is actually two words: occupy pit!
When we start the plug-in activity, we must pass in the intent object of the plug-in activity. At this time, AMS will definitely throw an exception when it finds that it is not registered in the manifest file.
Therefore, we need to replace the activity in the intent object with an activity that occupies a hole, and store the contents of the original intent (i.e. the registration and class name corresponding to the plug-in activity) in the intent, and mark the current intent as the plug-in’s intent
After AMS executes a series of processes, the LAUNCH_ACTIVITY message branch of mH in ActivityThread is called by binder for processing, and the activity object is created by Instrumentation reflection
In this process, we need to replace the activity that hogs with the activity object that we really need to start.
To implement this, we can retrieve the ActivityThreadmH by reflection and set mCallback to the mH (the callback will be processed first by the handler message). Then we can get the code for AMS to execute to the ActivityThread through the binder call. That is, through mH to process the LAUNCH_ACTIVITY message, replace the intent content at this time with the content of the plug-in activity we need to start, and then create an activity object through Instrumentation reflection, that is, create our plug-in activity!
summary
As described above, a picture can be summarized:
Afterword.
The need for plug-ins for the four major components is the icing on the cake
In simple business scenarios, we usually only need to apply for an activity, and write our business logic in this activity. We can completely avoid the use of the activity, and develop by Dialog to get popWindow
The benefit is that it simplifies the plug-in solution and makes our plug-in development lighter. Through the previous four components of the source analysis, I believe you must have found that the four components are large, because they are very heavy, need to handle their startup and life cycle logic through the system services
The downside is that there may be some tedious development and learning costs
How to make a choice depends on your real business scenario requirements
Your praise is the biggest motivation for my creation, please support more praise oh!
Refer to the link
- weishu.me
- VirtualApk