Virtual apk plug-in Architecture Analysis, virtualapk
The basic principle is to hook the system's activity service and other key components. When you need to start some components in the plug-in and use the custom logic, when you start the components in the local apk, native logic.
1. system problems.
Before analyzing the Didi plug-in framework, you need to understand the app startup process in the android system and the communication scheduling of the system.
There are several key classes to learn about: ActivityThread. javaApplicationThread. java ActivityManagerService. java Instrumentation. java
The startup process is as follows:
When starting a new app in laucher, refer to Lao Luo's article:
The entire application startup process requires many steps, but the entire process is divided into the following five phases:
1. Step 1-Step 11: Launcher notifies ActivityManagerService through the Binder inter-process communication mechanism. It wants to start an Activity;
2. Step 12-Step 16: ActivityManagerService notifies Launcher to enter the Paused state through the Binder inter-process communication mechanism;
III. step 17-Step 24: Launcher notifies ActivityManagerService through the Binder inter-process communication mechanism. It is ready to enter the Paused state, so ActivityManagerService creates a new process to start an ActivityThread instance, the Activity to be started is run in the ActivityThread instance;
4. Step 25-Step 27: ActivityThread transmits an ApplicationThread-Type Binder object to ActivityManagerService through the Binder inter-process communication mechanism, so that ActivityManagerService can communicate with it through this Binder object in the future;
5. Step 28-Step 35: ActivityManagerService notifies ActivityThread through the inter-process communication mechanism of the Binder. Now everything is ready, and it can truly start the Activity.
ActivityManagerService:
This is the core category of the system, almost in charge of the entire system startup issues.
ActivityThread:
It is the main thread of an application process and has an endless loop to distribute the work that the process needs to handle.
ApplicationThread:
For usage, refer to Step 4 in the startup process.
Instrumentation:
The method is as follows, which basically manages all interactions with the Activity. So, this class needs to be hooked
Ii. source code analysis.
If you want to start the activity or service related to the plug-in apk from the main apk, you must first load the activity and service in the plug-in. The entire loading process is as follows.
For these System Classes of hook, Instrumentation uses a subclass object (VAInstrumentation) to replace the original instrumentation. What this subclass does is simple. Rewrite newActivity () of the parent class (that is, the method called when we create the activity) and catch classnotfoundexception using try catch. Normally, we want to load the activity in the plug-in apk, however, by default, the class in the plug-in apk is not loaded in our application, so the error that the class cannot be found is reported. So, at this time, we can load the corresponding activity in the plug-in apk.
When you need to start an activity, startActivity () is generally used. Follow this method to see its call chain.
Public void startActivityForResult (Intentintent, int requestCode, @ Nullable Bundle options ){
If (mParent = null ){
// Here we can see that execStartActivity () of instrumentation is called, so we need to process this method of instrumentation. We can see that this is indeed the case in virtualapk code.
Instrumentation. ActivityResult ar =
MInstrumentation.exe cStartActivity (
This, mMainThread. getApplicationThread (), mToken, this,
Intent, requestCode, options );
............
}
Instrumentation. java
PublicActivityResult execStartActivity (
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options ){
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target! = Null target. onProvideReferrer (): null;
If (referrer! = Null ){
Intent. putExtra (Intent. EXTRA_REFERRER, referrer );
}
............
Try {
Intent. migrateExtraStreamToClipData ();
Intent. prepareToLeaveProcess ();
// ActivityManagerNative is actually called. In fact, the system calls ActivityManagerService to start the activity.
// Skip this step. Since the method of Instrumentation needs to be called to start the activity, so we need to process this method.
Int result = ActivityManagerNative. getDefault ()
. StartActivity (whoThread, who. getBasePackageName (), intent,
Intent. resolveTypeIfNeeded (who. getContentResolver ()),
Token, target! = Null target. mEmbeddedID: null,
RequestCode, 0, null, options );
CheckStartActivityResult (result, intent );
} Catch (RemoteException e ){
Throw new RuntimeException ("Failure from system", e );
}
Return null;
}
Instrumentation. java
// Method in the parent class of the rewrite operation.
PublicActivityResult execStartActivity (
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options ){
MPluginManager. getComponentsHandler (). transformIntentToExplicitAsNeeded (intent );
// Null component is an implicitly intent
If (intent. getComponent ()! = Null ){
Log. I (TAG, String. format ("execStartActivity [% s: % s]", intent. getComponent (). getPackageName (),
Intent. getComponent (). getClassName ()));
// Resolve intent with Stub Activity if needed
// Start a local preset activity, but the plugin flag, the target package name, and the target class name are added to the intent.
// Continue the normal activity startup process until newActivity ()
This. mPluginManager. getComponentsHandler (). markIntentIfNeeded (intent );
}
ActivityResult result = realExecStartActivity (who, contextThread, token, target,
Intent, requestCode, options );
Return result;
}
The entire call process is shown in the following figure:
@ Override
PublicActivity newActivity (ClassLoader cl, String className, Intent intent) throwsInstantiationException, IllegalAccessException, ClassNotFoundException {
Try {
// Load the class from the classloader of the primary apk first. Note that the class name is at this time. Because the class is not created locally, the cl error cannot be found. loadClass (className );
} Catch (ClassNotFoundException e ){
// You can see the implementation of LoadedPlugin. How does it load classes in the plug-in apk.
LoadedPlugin plugin = this. mPluginManager. getLoadedPlugin (intent );
// Find the corresponding activity from the loaded plug-in resource
String targetClassName = PluginUtil. getTargetActivity (intent );
Log. I (TAG, String. format ("newActivity [% s: % s]", className, targetClassName ));
If (targetClassName! = Null ){
// Plugin. getClassLoader () is already the classloader that has loaded the plug-in resources. A plug-in activity instance is created here.
// This is equivalent to replacing the preset with the activity in the plug-in apk.
// This operation is required because the activityNotFound error is reported if the activity is not pre-configured, that is, it is not declared in manifest.
// So the strategy is to first cheat the system to prevent activityNotFound error Activity = mBase. newActivity (plugin. getClassLoader (), targetClassName, intent );
Activity. setIntent (intent );
Try {
// For 4.1 +
// Modify mResources by reflection to the resource loaded with the plug-in resources
ReflectUtil. setField (ContextThemeWrapper. class, activity, "mResources", plugin. getResources ());
} Catch (Exception ignored ){
// Ignored.
}
Return activity;
}
}
Return mBase. newActivity (cl, className, intent );
}
Now, the startup of the activity in the plug-in apk is analyzed.
For the service, the dynamic proxy is used to hook the system.
/**
* HookSystemServices, but need to compatible with Android O in future.
*/
Private void hookSystemServices (){
Try {
Singleton DefaultSingleton = (Singleton ) ReflectUtil. getField (ActivityManagerNative. class, null, "gDefault ");
IActivityManager activityManagerProxy = ActivityManagerProxy. newInstance (this, defaultSingleton. get ());
// Hook IActivityManager from ActivityManagerNative
// Replace the internal objects of singleton with the proxy object.
ReflectUtil. setField (defaultSingleton. getClass (). getSuperclass (), defaultSingleton, "mInstance", activityManagerProxy );
If (defaultSingleton. get () = activityManagerProxy ){
This. mActivityManager = activityManagerProxy;
}
} Catch (Exception e ){
E. printStackTrace ();
}
}
Check your own dynamic proxy class.
ActivityManagerProxy. java
@ Override
PublicObject invoke (Object proxy, Method method, Object [] args) throws Throwable {
If ("startService". equals (method. getName ())){
Try {
Return startService (proxy, method, args );
} Catch (Throwable e ){
Log. e (TAG, "Start serviceerror", e );
}
} Else if ("stopService". equals (method. getName ())){
Try {
Return stopService (proxy, method, args );
} Catch (Throwable e ){
Log. e (TAG, "Stop Service error", e );
}
} Else if ("stopServiceToken". equals (method. getName ())){
Try {
Return stopServiceToken (proxy, method, args );
} Catch (Throwable e ){
Log. e (TAG, "Stop service token error", e );
}
} Else if ("bindService". equals (method. getName ())){
Try {
Return bindService (proxy, method, args );
} Catch (Throwable e ){
E. printStackTrace ();
}
} Else if ("unbindService". equals (method. getName ())){
Try {
Return unbindService (proxy, method, args );
} Catch (Throwable e ){
E. printStackTrace ();
}
} Else if ("getIntentSender". equals (method. getName ())){
Try {
GetIntentSender (method, args );
} Catch (Exception e ){
E. printStackTrace ();
}
} Else if ("overridePendingTransition". equals (method. getName ())){
Try {
OverridePendingTransition (method, args );
} Catch (Exception e ){
E. printStackTrace ();
}
}
Try {
// Sometimes system binder has problems.
Return method. invoke (this. mActivityManager, args );
} Catch (Throwable th ){
Throwable c = th. getCause ();
If (c! = Null & c instanceof DeadObjectException ){
// Retry connect to systembinder
IBinder ams = ServiceManager. getService (Context. ACTIVITY_SERVICE );
If (ams! = Null ){
IActivityManager am = ActivityManagerNative. asInterface (ams );
MActivityManager = am;
}
}
Throwable cause = th;
Do {
If (cause instanceof RemoteException ){
Throw cause;
}
} While (cause = cause. getCause ())! = Null );
Throw c! = Null c: th;
}
}
ActivityManagerProxy. java
Private Object startService (Object proxy, Methodmethod, Object [] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args [0];
Intent target = (Intent) args [1];
// Search the plug-in service set first. If no plug-in service is found, the service in the main apk is used.
ResolveInfo resolveInfo = this. mPluginManager. resolveService (target, 0 );
// Start the service in the main apk
If (null = resolveInfo | null = resolveInfo. serviceInfo ){
// Is host service
Return method. invoke (this. mActivityManager, args );
}
Return startDelegateServiceForTarget (target, resolveInfo. serviceInfo, null, RemoteService. EXTRA_COMMAND_START_SERVICE );
}
WrapperTargetIntent ():
Private Intent wrapperTargetIntent (Intenttarget, ServiceInfo serviceInfo, Bundle extras, int command ){
// Fill in service with ComponentName
Target. setComponent (new ComponentName (serviceInfo. packageName, serviceInfo. name ));
String pluginLocation = mPluginManager. getLoadedPlugin (target. getComponent (). getLocation ();
// Start delegate service to run plugin service inside
Boolean local = PluginUtil. isLocalService (serviceInfo );
Class Delegate = local LocalService. class: RemoteService. class;
Intent intent = new Intent ();
// Start the local proxy service
Intent. setClass (mPluginManager. getHostContext (), delegate );
Intent. putExtra (RemoteService. EXTRA_TARGET, target );
Intent. putExtra (RemoteService. EXTRA_COMMAND, command );
Intent. putExtra (RemoteService. EXTRA_PLUGIN_LOCATION, pluginLocation );
If (extras! = Null ){
Intent. putExtras (extras );
}
Return intent;
}
Here is an example of a service:
/*
* Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License ");
* You maynot use this file before t in compliance with the License.
* You mayobtain a copy of the License
*
* Http://www.apache.org/licenses/LICENSE-2.0
*
* Unlessrequired by applicable law or agreed to in writing, software
* Distributed under the License is distributed on an "as is" BASIS,
* Withoutwarranties or conditions of any kind, either express or implied.
* See theLicense for the specific language governing permissions and
* Limitations under the License.
*/
Package com. didi. virtualapk. delegate;
Import android. app. ActivityThread;
Import android. app. Application;
Import android. app. IActivityManager;
Import android. app. IApplicationThread;
Import android. app. IServiceConnection;
Import android. app. Service;
Import android. content. ComponentName;
Import android. content. Context;
Import android. content. Intent;
Import android. OS. Binder;
Import android. OS. Build;
Import android. OS. IBinder;
Import android. util. Log;
Import com. didi. virtualapk. PluginManager;
Importcom. didi. virtualapk. internal. LoadedPlugin;
Import com. didi. virtualapk. utils. PluginUtil;
Import com. didi. virtualapk. utils. ReflectUtil;
Import java. lang. reflect. Method;
/**
* @ Authorjohnsonlee
*/
Public class LocalService extends Service {
Private static final String TAG = "LocalService ";
/**
* Thetarget service, usually it's a plugin service intent
*/
Publicstatic final String EXTRA_TARGET = "target ";
Publicstatic final String EXTRA_COMMAND = "command ";
Publicstatic final String EXTRA_PLUGIN_LOCATION = "plugin_location ";
Publicstatic final int EXTRA_COMMAND_START_SERVICE = 1;
Publicstatic final int EXTRA_COMMAND_STOP_SERVICE = 2;
Publicstatic final int EXTRA_COMMAND_BIND_SERVICE = 3;
Publicstatic final int EXTRA_COMMAND_UNBIND_SERVICE = 4;
Private PluginManager mPluginManager;
@ Override
PublicIBinder onBind (Intent intent ){
Return new Binder ();
}
@ Override
Publicvoid onCreate (){
Super. onCreate ();
MPluginManager = PluginManager. getInstance (this );
}
@ Override
Publicint onStartCommand (Intent intent, int flags, int startId ){
If (null = intent |! Intent. hasExtra (EXTRA_TARGET) |! Intent. hasExtra (EXTRA_COMMAND )){
Return START_STICKY;
}
// Obtain the target service
Intent target = intent. getParcelableExtra (EXTRA_TARGET );
Int command = intent. getIntExtra (EXTRA_COMMAND, 0 );
If (null = target | command <= 0 ){
Return START_STICKY;
}
ComponentName component = target. getComponent ();
LoadedPlugin plugin = mPluginManager. getLoadedPlugin (component );
// ClassNotFoundException when unreceivalling in Android 5.1
Target. setExtrasClassLoader (plugin. getClassLoader ());
Switch (command ){
Case EXTRA_COMMAND_START_SERVICE :{
ActivityThread mainThread = (ActivityThread) ReflectUtil. getActivityThread (getBaseContext ());
IApplicationThread appThread = mainThread. getApplicationThread ();
Service service;
If (this. mPluginManager. getComponentsHandler (). isServiceAvailable (component )){
Service = this. mPluginManager. getComponentsHandler (). getService (component );
} Else {
Try {
Service = (Service) plugin. getClassLoader (). loadClass (component. getClassName (). newInstance ();
Application app = plugin. getApplication ();
IBinder token = appThread. asBinder ();
// Reflect the attach () that calls the service ()
Method attach = service. getClass (). getMethod ("attach", Context. class, ActivityThread. class, String. class, IBinder. class, Application. class, Object. class );
IActivityManager am = mPluginManager. getActivityManager ();
Attach. invoke (service, plugin. getPluginContext (), mainThread, component. getClassName (), token, app, am );
Service. onCreate ();
This. mPluginManager. getComponentsHandler (). rememberService (component, service );
} Catch (Throwable t ){
Return START_STICKY;
}
}
Service. onStartCommand (target, 0, this. mPluginManager. getComponentsHandler (). getServiceCounter (service). getAndIncrement ());
Break;
}
............
}