滴滴开源插件框架地址: https://github.com/didi/VirtualAPK
大神鸿洋的框架分析: http://blog.youkuaiyun.com/lmj623565791/article/details/75000580
框架接入: http://www.jianshu.com/p/013510c19391
先看看从git上clone下来的工程的项目结构:

主要是四个module
1、AndroidStub
声明了一些和framework同名的类,用来在编译的时候通过检查,在实际运行的时候会被rom中的同名类替换掉。
里面的类所有方法都是抛出一个异常。该工程被CoreLibrary引用,因为在核心工程里面会用到一些引用了framework类的地方。
throw new RuntimeException("Stub!");
2、CoreLibray
核心库,里面插件框架的主要逻辑。被宿主工程引用
3、PluginDemo
一个独立的demo工程,可以单独使用,但是作为插件apk的时候,不能通过AS run的方式生成apk,必须通过gradle编译,因为在编译的过程中会去掉一些和宿主工程一样的引用类库,比如说宿主工程应用了v7包,插件工程在开发的时候也引用了v7包,那么在编译的时候不去掉这个v7包,在加载插件的时候就会出现这个问题:
-
07-18 21:09:23.640 4747-4747/com.didi.virtualapk I/VAInstrumentation: newActivity[com.didi.virtualapk.core.A$1 : com.didi.virtualapk.demo.aidl.BookManagerActivity] -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Class resolved by unexpected DEX: Lcom/didi/virtualapk/demo/aidl/BookManagerActivity;(0x4193c570):0x5acfc000 ref [Landroid/support/v7/app/AppCompatActivity;] Landroid/support/v7/app/AppCompatActivity;(0x4193c570):0x5aad5000 -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: (Lcom/didi/virtualapk/demo/aidl/BookManagerActivity; had used a different Landroid/support/v7/app/AppCompatActivity; during pre-verification) -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Unable to resolve superclass of Lcom/didi/virtualapk/demo/aidl/BookManagerActivity; (1537) -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Link of class 'Lcom/didi/virtualapk/demo/aidl/BookManagerActivity;' failed -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk D/AndroidRuntime: Shutting down VM -
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x40aea2a0) -
07-18 21:09:23.656 4747-4747/com.didi.virtualapk E/AndroidRuntime: FATAL EXCEPTION: main -
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation -
at dalvik.system.DexFile.defineClass(Native Method) -
at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211) -
at dalvik.system.DexPathList.findClass(DexPathList.java:315) -
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58) -
at java.lang.ClassLoader.loadClass(ClassLoader.java:501) -
at java.lang.ClassLoader.loadClass(ClassLoader.java:495) -
at java.lang.ClassLoader.loadClass(ClassLoader.java:461) -
at android.app.Instrumentation.newActivity(Instrumentation.java:1053) -
at com.didi.virtualapk.internal.VAInstrumentation.newActivity(VAInstrumentation.java:101) -
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1984) -
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2094) -
at android.app.ActivityThread.access$600(ActivityThread.java:134) -
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1202) -
at android.os.Handler.dispatchMessage(Handler.java:99) -
at android.os.Looper.loop(Looper.java:137) -
at android.app.ActivityThread.main(ActivityThread.java:4767) -
at java.lang.reflect.Method.invokeNative(Native Method) -
at java.lang.reflect.Method.invoke(Method.java:511) -
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:800) -
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:567) -
at dalvik.system.NativeStart.main(Native Method) -
07-18 21:09:29.898 4747-4747/com.didi.virtualapk I/Process: Sending signal. PID: 4747 SIG: 9
had used a different Landroid/support/v7/app/AppCompatActivity; during pre-verification)
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
重复的类型。
gradle编译插件,看github上的wiki。
4、app
这个是宿主工程的主module,里面只有一个Activity和Application。
在application创建的时候,通过单例模式初始化了插件管理类PluginManager对象,同时通过宿主的context拿到当前宿主进程的activityThread对象,并通过这个对象,进而拿到instrumentation(一个进程里面只有一个instrumentation,activity中的instrumentation就是通过构造contextImpl对象的时候传进去的,该对象负责接管AMS来执行activity的生命周期)和AMS在客户端的代理对象ActivityManagerProxy(IActivityManager类型)。(后面在使用contentProvider的时候,也会去hook一个IContentProvider对象,为什么不在这里开始的时候就hook?因为IContentProvider对象是在ContentResolver执行query等几个方法的时候,去ActivityThread中查询是否有这个IContentProvider对象,没有才去告诉AMS去创建一个IContentProvider对象,所以还没到使用contentProvider的时候)
看PluginManager的构造:
-
private PluginManager(Context context) { -
Context app = context.getApplicationContext(); -
if (app == null) { -
this.mContext = context; -
} else { -
this.mContext = ((Application)app).getBaseContext(); -
} -
prepare(); -
}
拿到了宿主的context保存起来,执行prepare,去hookinstrumentation和AMS的代理
-
private void prepare() { -
Systems.sHostContext = getHostContext(); -
this.hookInstrumentationAndHandler(); -
this.hookSystemServices(); -
}
通过反射的方式,拿到进程的activityThread对象,并根据这个对象获取ActivityThread中的instrumentation变量,通过静态代理的方式,创建一个VAInstrumentation对象,该对象里面包含了原生instrumentation的引用。把该对象重新赋值给activityThread中的instrumentation变量。这样以后宿主执行activity的创建和生命周期都由这个新对象来完成。
-
private void hookInstrumentationAndHandler() { -
try { -
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext); -
if (baseInstrumentation.getClass().getName().contains("lbe")) { -
// reject executing in paralell space, for example, lbe. -
System.exit(0); -
} -
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation); -
Object activityThread = ReflectUtil.getActivityThread(this.mContext); -
ReflectUtil.setInstrumentation(activityThread, instrumentation); -
ReflectUtil.setHandlerCallback(this.mContext, instrumentation); -
this.mInstrumentation = instrumentation; -
} catch (Exception e) { -
e.printStackTrace(); -
} -
}
看看这个VAInstrumentation里复写了什么方法:
-
public ActivityResult 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 -
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); -
} -
ActivityResult result = realExecStartActivity(who, contextThread, token, target, -
intent, requestCode, options); -
return result; -
}
这个方法是在什么时候调用的呢?
先回到主工程,在这个宿主工程的activity中的onCreate方法中,执行了加载插件apk的逻辑(通过DexClassLoader加载外部apk),并保存在一个LoadedPlugin对象中。
在这个activity中有个button,点击该button回去执行一个启动activity的代码:
-
Intent intent = new Intent(); -
intent.setClassName(pkg, "com.didi.virtualapk.demo.aidl.BookManagerActivity"); -
startActivity(intent);
这里我们看到要启动一个插件里的activity。那么来看看startActivity的流程:
startActivity ---> startActivityForResult ---> mInstrumentation.execStartActivity。
由于我们activity的mInstrumentation引用的是ActivityThread里面的(具体是在activityThread创建activity的时候,通过activity的attach方法传进来的),而activityThread中的instrumentation已经被hook成VAInstrumentation对象了。所以必然会执行到VAInstrumentation对象的execStartActivity方法中,也就是上面的代码。
在上面的execStartActivity中,首先根据intent去插件的包里面,找到对应的插件activity的信息,包括在menifest中设置的启动模式,风格等。拿到这些信息后,根据启动模式和风格,去找到相应的占坑的activity(在核心库中的xml中注册的)。重新组装一个inent,调用realExecStartActivity方法:
-
private ActivityResult realExecStartActivity( -
Context who, IBinder contextThread, IBinder token, Activity target, -
Intent intent, int requestCode, Bundle options) { -
ActivityResult result = null; -
try { -
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, -
int.class, Bundle.class}; -
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase, -
"execStartActivity", parameterTypes, -
who, contextThread, token, target, intent, requestCode, options); -
} catch (Exception e) { -
e.printStackTrace(); -
} -
return result; -
}
看,直接通过反射,直接调用了原生instrumentation的execStartActivity方法,该方法里面调用AMS的代理来告诉AMS我要启动一个activity
-
try { -
intent.migrateExtraStreamToClipData(); -
intent.prepareToLeaveProcess(who); -
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); -
}
这里传递给AMS的intent就是占坑的activity的信息,这个acitivy是在宿主的核心库中注册的,AMS必然能通过检测。
AMS通过客户端(ApplicationThread)在systemServer的代理ApplicationThreadProxy发送创建activity的指令,通过binder驱动,客户端也就是宿主进程里的ActivityThread的内部类ApplicationThread,执行scheduleLauncherActivity,通过ActivityThread的内部对象H发送msg,执行handleLaunchActivity -- > performLaunchActivity:
-
try { -
ClassLoader e = r.packageInfo.getClassLoader(); -
activity = this.mInstrumentation.newActivity(e, component.getClassName(), r.intent); -
StrictMode.incrementExpectedActivityCount(activity.getClass()); -
r.intent.setExtrasClassLoader(e); -
r.intent.prepareToEnterProcess(); -
if(r.state != null) { -
r.state.setClassLoader(e); -
} -
}
在performLaunchActivity这个方法里面,又调用了instrumentation的newActivity,于是我们看VAInstrumentation的该方法是做了什么内容:
-
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { -
try { -
cl.loadClass(className); -
} catch (ClassNotFoundException e) { -
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); -
String targetClassName = PluginUtil.getTargetActivity(intent); -
Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName)); -
if (targetClassName != null) { -
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); -
activity.setIntent(intent); -
try { -
// for 4.1+ -
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources()); -
} catch (Exception ignored) { -
// ignored. -
} -
return activity; -
} -
} -
return mBase.newActivity(cl, className, intent); -
}
根据intent信息,还原我们的插件activity的信息,直接通过原来的instrumentation 来new一个activity出来。之后的流程还是继续走正常的。
接下来看hook AMS(也就是hook AMS在客户端的代理ActivityManagerProxy),在PluginManager的hookSystemServices方法中,通过动态代理的方式,创建了一个代理IActivityManager,通过反射把该代理替换掉原来的AMP对象。以后所有执行该代理的方法,都是默认先走到我们自己定义的ActivityManagerProxy(继承自InvocationHanlder)的invoke方法中(这是动态代理的机制)。
-
private void hookSystemServices() { -
try { -
Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault"); -
IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get()); -
// Hook IActivityManager from ActivityManagerNative -
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy); -
if (defaultSingleton.get() == activityManagerProxy) { -
this.mActivityManager = activityManagerProxy; -
} -
} catch (Exception e) { -
e.printStackTrace(); -
} -
}
看下ActivityManagerProxy(这个类并不是真正意义上的AMS的代理了,只是实现了InvocationHandler接口,有点混淆),主要看invoke方法:
-
if ("startService".equals(method.getName())) { -
try { -
return startService(proxy, method, args); -
} catch (Throwable e) { -
Log.e(TAG, "Start service error", 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(); -
} -
}
看这个方法里面的method的name是不是很熟悉,就是我们主动调用startService的时候,由ContextImpl传递到AMS的代理执行的。
比如在一个activity中启动一个service是这样的
startService ---> 最终调用contextImpl的startServie。
-
public ComponentName startService(Intent service) { -
this.warnIfCallingFromSystemProcess(); -
return this.startServiceCommon(service, this.mUser); -
}
-
private ComponentName startServiceCommon(Intent service, UserHandle user) { -
try { -
this.validateServiceIntent(service); -
service.prepareToLeaveProcess(this); -
ComponentName e = ActivityManagerNative.getDefault().startService(this.mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(this.getContentResolver()), this.getOpPackageName(), user.getIdentifier()); -
if(e != null) { -
if(e.getPackageName().equals("!")) { -
throw new SecurityException("Not allowed to start service " + service + " without permission " + e.getClassName()); -
} -
if(e.getPackageName().equals("!!")) { -
throw new SecurityException("Unable to start service " + service + ": " + e.getClassName()); -
} -
} -
return e; -
} catch (RemoteException var4) { -
throw var4.rethrowFromSystemServer(); -
} -
}
看,最终调用了AMS的代理的startService,而该代理被我们hook了,也就会执行到上面的ActivityManagerProxy(继承自InvocationHanlder)的invoke方法中。
其他的方法,比如stop等等都会被这个invoke拦截,在invoke中通过不同的方法名调用了不同的函数,比如startService
-
private Object startService(Object proxy, Method method, Object[] args) throws Throwable { -
IApplicationThread appThread = (IApplicationThread) args[0]; -
Intent target = (Intent) args[1]; -
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); -
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); -
}
最终,如果是要执行代理的组件,就会去执行startDelegateServiceFortarget方法,看看其他的方法,比如stopService,最终都会调用这个函数。也就是说所有的AMS的操作service的命令,最终都会执行到这里。看看这个函数做了什么。
-
private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { -
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); -
return mPluginManager.getHostContext().startService(wrapperIntent); -
}
所有跟service生命周期相关的调用都被替换为执行启动宿主的service,也就是每次都会回调宿主service的onStartCommand方法,必然,会在该方法里面去创建我们插件的service的生命周期了。
最后,ContextProvider的处理,也是通过动态代理的方式,hook掉AMS的IContentProvider,所有执行query等操作都有invoke来分发,通过包裹uri,去调用占坑的contentProvider,占坑的contentProvider在根据这些uri,解析出真正要执行的插件中的contentProvider,然后启动该provider。
执行一个contentProvider的query流程是这样的:
在service、activity或application中调用getConentResolver.query,
getContentResolver由父类ContextWrapper实现,其又调用了内部对象mBase(是个ContextImpl的实例)的同名方法,那么具体看ContextImpl
-
public ContentResolver getContentResolver() { -
return this.mContentResolver; -
}
mContentResolver是ContextImpl.ApplicationContentResolver对象,在ContextImpl构造函数中创建
this.mContentResolver = new ContextImpl.ApplicationContentResolver(this, mainThread, user);
直接看这个类的query方法,这个query其实是调用的父类ContentResolver的方法:
-
public final Cursor query(@Read Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { -
SeempLog.record_uri(13, uri); -
return this.query(uri, projection, selection, selectionArgs, sortOrder, (CancellationSignal)null); -
}
在其重载方法中会去调用acquireProvider,该方法是个抽象方法,具体在applicationContentResolver中实现:
-
protected IContentProvider acquireProvider(Context context, String auth) { -
return this.mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), this.resolveUserIdFromAuthority(auth), true); -
}
看,直接调用的是activityThread的同名方法,
-
public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) { -
IContentProvider provider = this.acquireExistingProvider(c, auth, userId, stable); -
if(provider != null) { -
return provider; -
} else { -
ContentProviderHolder holder = null; -
try { -
holder = ActivityManagerNative.getDefault().getContentProvider(this.getApplicationThread(), auth, userId, stable); -
} catch (RemoteException var8) { -
throw var8.rethrowFromSystemServer(); -
} -
if(holder == null) { -
Slog.e("ActivityThread", "Failed to find provider info for " + auth); -
return null; -
} else { -
holder = this.installProvider(c, holder, holder.info, true, holder.noReleaseNeeded, stable); -
return holder.provider; -
} -
} -
}
调用了AMS的代理,获取一个IContentProvider对象存储到holder对象中。并执行了安装provider的操作,installProvider会把provider保存在activityThread的的provider的map中保存起来以便以后使用。
这是宿主启动占坑的contentProvider的主要流程,我们要hook的就是操作这个宿主占坑contentprovider对应的IContentProvider对象了,为什么这么说呢,因为插件的contentProvider都是通过宿主的contentprovider来显示的执行创建,query等操作的。
所以在操作插件contentProvider前先要启动占坑的contentProvider,这样才能拿到代理的IContentProvider对象进行hook。
所以在PluginManager的hook中,先执行了占坑contentProvider的创建过程。
-
private void hookIContentProviderAsNeeded() { -
Uri uri = Uri.parse(PluginContentResolver.getUri(mContext)); -
mContext.getContentResolver().call(uri, "wakeup", null, null); -
try { -
Field authority = null; -
Field mProvider = null; -
ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext); -
Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap"); -
Iterator iter = mProviderMap.entrySet().iterator(); -
while (iter.hasNext()) { -
Map.Entry entry = (Map.Entry) iter.next(); -
Object key = entry.getKey(); -
Object val = entry.getValue(); -
String auth; -
if (key instanceof String) { -
auth = (String) key; -
} else { -
if (authority == null) { -
authority = key.getClass().getDeclaredField("authority"); -
authority.setAccessible(true); -
} -
auth = (String) authority.get(key); -
} -
if (auth.equals(PluginContentResolver.getAuthority(mContext))) { -
if (mProvider == null) { -
mProvider = val.getClass().getDeclaredField("mProvider"); -
mProvider.setAccessible(true); -
} -
IContentProvider rawProvider = (IContentProvider) mProvider.get(val); -
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider); -
mIContentProvider = proxy; -
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider); -
break; -
} -
} -
} catch (Exception e) { -
e.printStackTrace(); -
} -
}
头两句,先获得宿主的uri,通过该uri,通过宿主的context获取到宿主的resolver对象,执行call方法,这样就回去执行宿主占坑的contentProvider的call方法,此时,操作占坑的provider的IcontentProvider对象已经存储到activityThread的map中了,
所以在该方法最后,直接从map变量中获取了IContentProvider,然后通过动态代理的方式,替换了原来的IContentProvider。
这个时候所有操作IContentProvider的所有方法都被IContentProviderProxy的invoke方法拦截,
-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { -
Log.v(TAG, method.toGenericString() + " : " + Arrays.toString(args)); -
wrapperUri(method, args); -
try { -
return method.invoke(mBase, args); -
} catch (InvocationTargetException e) { -
throw e.getTargetException(); -
} -
}
在该方法里面包着了uri,携带了启动插件provider的uri,
然后继续调用宿主 contentProvider,在占坑的provider中,各个方法里面
-
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { -
ContentProvider provider = getContentProvider(uri); -
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); -
if (provider != null) { -
return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder); -
} -
return null; -
}
getContentProvider方法拿到插件的provider,直接指向query方法。这样插件中的query方法就被调用了。就好像系统去调用了一样。
这里为什么hook宿主的provider时,要使用call方式来启动宿主provider呢
看这里: http://blog.youkuaiyun.com/zhangyongfeiyong/article/details/51860572
自我感觉,resolver执行call方法,比如导致provider来执行call,这里面不用做什么其他而我的操作,执行call方法必然就会让provider去执行onCreate方法来启动。感觉是个启动provider的好时机。
原文转载自:https://blog.youkuaiyun.com/anhenzhufeng/article/details/75411779
本文详细解析了滴滴开源的VirtualAPK框架,这是一个实现Android应用插件化的框架。它通过hook系统服务、Instrumentation以及ActivityManagerNative,实现在不修改系统和宿主应用的情况下,动态加载和运行插件APK。核心流程包括:1) 创建占坑组件,2) hook Instrumentation以接管Activity生命周期,3) hook AMS代理以控制服务启动,4) 动态代理ContentProvider。通过这些手段,VirtualAPK实现了对插件APK的无缝集成和运行。
5164

被折叠的 条评论
为什么被折叠?



