滴滴开源插件框架地址:
https://github.com/didi/VirtualAPK
本人比较渣,在阅读源码后做一个比较简单的记录,方便后期查看。
先看看从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呢
自我感觉,resolver执行call方法,比如导致provider来执行call,这里面不用做什么其他而我的操作,执行call方法必然就会让provider去执行onCreate方法来启动。感觉是个启动provider的好时机。
好吧,终于理完了~~