About the author: Ren Yugang, Didi Chuxing Android technology expert, author of “Android Development Art Exploration”, the initiator of the plug-in framework dynamic-load-APK, CSDN mobile development blog expert, was selected as CSDN’s top ten Blog stars in 2014 and 2015. Love technology, love open source, love to get to the bottom of everything, long-term active in CSDN and GitHub. Currently, I am working in the App architecture team of Didi Chuxing, engaged in the development of hot repair and plug-in. Blog: blog.csdn.net/singwhatiwa… ; GitHub:github.com/singwhatiwa… . This article is the first CSDN, the road of technology, common progress. Technical contributions are welcome. Please send email to [email protected] for correction.
One, foreword
With the rapid development of Android plug-in technology today, whether it is easy or difficult to develop and implement a plug-in framework is a question that different people will have different answers. But I believe that it is not difficult to complete a plug-in framework Demo, but it is not easy to develop a perfect plug-in framework, especially in China, the major ROM manufacturers have made a certain degree of customization of the Android system, which further aggravates the fragmentation of Android itself.
Didi Chuxing started late in exploring plug-ins. Due to the rapid development of the business, iterations took up a lot of time, which made us start to study this technology in 2016. After half a year of development, testing, adaptation and online verification, today, we officially launched a relatively complete plug-in framework — VirtualAPK. The reason for the launch now is that VirtualAPK has been well verified internally, and we have reached a very stable situation to support the dynamic release requirements of some or all of Didi’s businesses through continuous adaptation and detailed feature support during the iterative process. At present, the latest version of Didi Chuxing (V5.0.4), the minibus and airport pickup services are plug-ins, you can experience.
Second, the status quo of plug-in
So far, there are many excellent open source projects in the industry, such as DynamicLoadApk based on the static agent idea in the early days, DynamicApk and Small based on the pit idea later, and the DroidPlugin for 360 mobile assistant. Both of them are excellent open source projects, which greatly promote the development of plug-in technology in China.
Despite all the great frameworks out there, compatibility is still one of the biggest obstacles to plug-in development. A plug-in framework may work perfectly on a single phone, but it’s prone to compatibility issues on tens of millions of devices. I’m sure any engineer who has worked with plug-ins will know this. Why does Didi need to develop a new plug-in framework? VirtualAPK was born because we needed a plug-in framework that was fully functional, compatible and suitable for Didi’s business. Currently, open source on the market could not meet our needs, so we had to reinvent the wheel.
The birth of VirtualAPK
VirtualAPK is an excellent plug-in framework developed by Didi Chuxing. It has the following features.
1. Complete function
-
Support for almost all Android features;
-
Four components: None of the four components need to be pre-registered in the host MANIFEST, and each component has a full life cycle.
-
Activity: supports display and implicit invocation, supports Activity theme and LaunchMode, and supports transparent themes;
-
Service: support explicit and implicit calls, support the start, stop, bind and unbind of services, and support cross-process bind plug-in Service;
-
Receiver: Supports static registration and dynamic registration.
-
ContentProvider: Supports all operations of the provider, including CRUD and Call methods, and supports cross-process access to the provider in the plug-in.
-
Custom View: support custom View, support custom attributes and style, support animation;
-
PendingIntent: Supports PendingIntent and its associated Alarm, Notification, and AppWidget.
-
Support meta-data in the plug-in manifest and Application;
-
Support for SO in plug-ins.
2. Excellent compatibility
-
It is compatible with almost all Android phones on the market, which has been proven in Didi Chuxing client.
-
In terms of resources, it ADAPTS xiaomi, Vivo, Nubia, etc., and adopts adaptive adaptation scheme for unknown models;
-
Few Binder hooks. Currently, only two Binder hooks are hooked: AMS and IContentProvider. The Hook process is fully compatible and adaptable.
-
The plug-in runtime logic is isolated from the host to ensure that any problems with the framework do not affect the normal operation of the host.
3. Very low invasion
-
Plug-in development is the same as native development, the four components do not need to inherit a specific base class;
-
A compact plug-in package that can depend on or not depend on code and resources in the host;
-
The process of building plug-ins is simple. Gradle plug-ins are used to build plug-ins. The whole process is transparent to developers.
The working process of VirtualAPK
VirtualAPK has no additional constraints on plug-ins, and native APK can be used as plug-ins. After the plug-in project is compiled and generated, the APK can be loaded through the host App. After each plug-in APK is loaded, a separate LoadedPlugin object will be created in the host. With these LoadedPlugin objects, as shown below, VirtualAPK can manage plug-ins and give them new meaning, making them behave like apps installed on your phone.
1. VirtualAPK operating mode
We plan to give VirtualAPK two working modes, a coupled mode and a standalone mode. VirtualAPK already has good support for coupled forms, and standalone forms are planned for the next step.
-
Coupling pattern: Plug-ins may or may not have code or resource dependencies on the host. In this mode, classes in the plug-in cannot duplicate the host, and resource ids cannot conflict with the host. This is the default form for VirtualAPK and is the one that works for most businesses.
-
Standalone: The plug-in has no code or resource dependencies on the host. In this mode, the plug-in has no relationship with the host, so classes and resources in the plug-in can be duplicated with the host. The main purpose of this form is to run some third-party APKs.
2. How to use it
Step 1: Initialize the plug-in engine
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}Copy the code
Step 2: Load the plug-in
public class PluginManager {
public void loadPlugin(final File apk);
public void loadPlugin(final String moduleCode);
}Copy the code
We have encapsulated the loading process so that a plug-in can be loaded asynchronously.
/ / example: start the Activity of plug-in DownloadManager DownloadManager = DownloadManager. GetInstance (this); downloadManager.loadModule("com.ryg.test", true, this, new ILoadListener() { @Override public void onLoadEnd(int resultCode) { if (resultCode == ILoadListener.LOAD_SUCCESS) { Intent intent = new Intent(); intent.setClassName("com.ryg.test", "com.ryg.test.MainActivity"); startActivity(intent); } else { // todo load plugin failed } } });Copy the code
When the plug-in entry is called, the subsequent logic of the plug-in does not need the intervention of the host, and all follow the native Android process. For example, inside the plug-in, the following code will execute correctly:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
LinearLayout holder = (LinearLayout)findViewById(R.id.holder);
TextView imei = (TextView)findViewById(R.id.imei);
imei.setText(IDUtil.getUUID(this));
// bind service in plugin
Intent service = new Intent(this, BookManagerService.class);
bindService(service, mConnection, Context.BIND_AUTO_CREATE);
// start activity in plugin
Intent intent = new Intent(this, TCPClientActivity.class);
startActivity(intent);
}Copy the code
Five, explore the principle
1. Fundamentals
-
Merge the host and plug-in classloaders: It is important to note that the classes in the plug-in cannot duplicate the host;
-
Merge plug-in and host resources: reset the packageId of the plug-in resource to merge the plug-in resource and host resource.
-
Dereferent plugin package references to the host: Build time removes references to the host code and resources through the Gradle plugin.
2. Implementation principles of the four components
-
Activity: uses a pit trap in the host manifest to bypass system validation and then loads the actual Activity;
-
Service: Dynamic proxy AMS intercepts service-related requests and forwards them to a virtual space (Matrix) for processing. Matrix takes over all operations of the system.
-
Receiver: Register the statically registered Receiver in the plug-in again;
-
ContentProvider: Dynamic proxy IContentProvider intercepts provider-related requests and forwards them to a virtual space (Matrix), which takes over all operations of the system.
Below is the overall structure of VirtualAPK.
The way to fill the pit
In practice, we have encountered many problems, such as model adaptation, API version adaptation, stability assurance of Binder Hook and so on. Here we take a typical resource adaptation problem to illustrate.
In fact, this is a very helpless problem, because the domestic major ROM manufacturers like deep customization of the Android system, so there is this adaptation problem.
Normally we create the plugin’s Resources object with code like this:
Resources newResources = new Resources(assetManager,
hostResources.getDisplayMetrics(), hostResources.getConfiguration());Copy the code
However, on Vivo’s phone, the following type conversion error appears, which looks like Vivo has subclassed Resources itself.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sdu.didi.psnger/com.didi.virtualapk.core.A$1}: java.lang.ClassCastException: android.content.res.Resources cannot be cast to android.content.res.VivoResources at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2196) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:140) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1202) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5143) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.ClassCastException: android.content.res.Resources cannot be cast to android.content.res.VivoResources at android.app.ResourcesManager.getTopLevelResources(ResourcesManager.java:236) at android.app.ContextImpl.<init>(ContextImpl.java:2057) at android.app.ContextImpl.createActivityContext(ContextImpl.java:2008) at android.app.ActivityThread.createBaseContextForActivity(ActivityThread.java:2207) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140) ... 11 moreCopy the code
I decompiled the Vivo Framework code, and sure enough, I did the type conversion in the following code, so I got an error when loading the plugin resources.
@VivoHook(hookType = VivoHookType.NEW_METHOD) public Resources getTopLevelResources(String pkgName, String resDir, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { Resources resources = getTopLevelResources(resDir, displayId, overrideConfiguration, compatInfo, token); if (resources ! = null) { ((VivoResources) resources).init(pkgName); } return resources; }Copy the code
To solve this problem, we analyzed the code implementation of VivoResources and then used the following code when creating the plug-in resources.
private static final class VivoResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.VivoResources");
Resources newResources = (Resources)ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
return newResources;
}
}Copy the code
In addition to Vivo, MIUI, Nubia and other unnamed models have similar problems. And on Vivo phones, besides the typecasting error, there are other crappy issues.
There were a lot of other potholes that we dealt with that we can’t cover here, so it was a technical challenge to figure out how to make the plugin stable.
7. Some features that are not currently supported
For a variety of reasons, VirtualAPK does not currently support all Android features.
-
Some Activity properties, such as Process and configChanges, are not supported.
-
Temporary does not support overridePendingTransition (int enterAnim, int exitAnim) this form of animated transitions;
-
Plugins are notified of being hit and resources such as images cannot be used.
8. Open source plan
Our goal is to create a fully functional plug-in framework that allows all lines of business to be integrated as plug-ins to achieve hot update capabilities for Android Apps.
There are still some features that VirtualAPK needs to improve, and after that, it will open source. We expect VirtualAPK to be open source so that other apps can seamlessly integrate and easily have hot update capabilities without having to worry about implementation details or compatibility issues.
For the latest mobile development related information and technology, please follow the mobileHub wechat account (ID: MobileHub).