一、简介
VirtualAPK是滴滴出行自研的一款优秀的插件化框架,VirtualAPK其特性和主流开源框架的对比:
本文将去分析四大组件的代码hook过程。
二、支持Activity
启动插件中的一个activity,
首先PluginManager.getInstance(this).getLoadedPlugin(pkg),先去判断有没有加载com.didi.virtualapk.demo的插件包,有的话则去跳转插件中的BookManagerActivity。那么是如何跳转的呢?
PluginManager在被创建实例的时候,会去hookCurrentProcess(),
通过activityThread去获取到Instrumentation,在这个Instrumentation的基础上新建一个VAInstrumentation的实例,并且set给activityThread,同时还hook了ActivityThread的mH类的Callback。
一些底层知识可以看下,为什么去hook Instrumentation:
《写给Android App开发人员看的Android底层知识(2)》
startActivity()方法会去执行到VAInstrumentation的execStartActivity方法中,
在接下来的markIntentIfNeeded方法中,会去将需要跳转的activity信息保存下来,在真正去跳转到下一个页面时候在去恢复回来
dispatchStubActivity方法会去根据不同的launchMode和themeObj去加载不同的stubActivity
继续回到Instrumentation的execStartActivity,
我们分析android源码以android 23为例:
通过ActivityManagerNative.getDefault()方法去获取IActivityManager的子类ActivityManagerProxy,调用startActivity方法
通过binder通信机制,
IApplicationThread app = ApplicationThreadNative.asInterface(b)是为了获取
到ApplicationThreadProxy并在后面的方法startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, options)中会用到,startActivity的具体逻辑需要在ActivityManagerService类去追踪了,
在AMS在处理完启动Activity后,会回到ActivityThread中ApplicationThread的scheduleLaunchActivity方法,最终要回到了mInstrumentation.newActivity的方法中去了
回到VAInstrumentation.newActivity方法中
从intent中获取到我们需要加载activity,通过Instrumentation.newActivity去创建新的activity,接着进入到了callActivityOnCreate中,从LoadedPlugin中取出mResources,mBase,mApplication传递给新生成的这个activity中,后面就可以完成activity的启动流程了。
三 、Broadcast Receiver的支持
对于Broadcast Receiver,比较简单,初始化LoadedPlugin插件对象的时候,直接将静态广播转为动态广播,就可以了。
四、Service的支持
service的hook采用了ActivityManagerProxy(继承了IActivityManager)的动态代理的方式处理,通过ActivityManagerProxy代理类来拦截方法去进行hook
public class ActivityManagerProxy implements InvocationHandler {
@Override
public Object 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 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);
}
}
...
}
protected 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);
}
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}
protected Intent wrapperTargetIntent(Intent target, 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<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
Intent intent = new Intent();
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;
}
}
ActivityManagerProxy在处理startService方法时,如图走入到wrapperTargetIntent方法里,判断当前是否本地service启动LocalService还是RemoteService,Service多次启动都会去调用onStartCommond方法,如果已经启动了话,只会去调用onStartCommond方法。
1、LocalService
public class LocalService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
return START_STICKY;
}
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);
if (plugin == null) {
Log.w(TAG, "Error target: " + target.toURI());
return START_STICKY;
}
// ClassNotFoundException when unmarshalling in Android 5.1
target.setExtrasClassLoader(plugin.getClassLoader());
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
ActivityThread mainThread = ActivityThread.currentActivityThread();
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();
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;
}
}
}
...
}
在LocalService中可以看到,onStartCommand中方法进行重写,该方法对目标Service新建、操作、销毁等,创建ComponentsHandler类去便于管理目标Service的记录、删除等。
2、RemoteService
- onBind返回为空,说明这个Service不能被绑定。
- onStartCommand中多了方法PluginManager.getInstance(this).loadPlugin(new File(pluginLocation)),说明它可以去加载其他的插件下的代码,也就可以调用其他Plugin下的Service
public class RemoteService extends LocalService {
private static final String TAG = "VA.RemoteService";
public RemoteService() {
}
public IBinder onBind(Intent intent) {
return null;
}
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
return super.onStartCommand(intent, flags, startId);
} else {
Intent target = (Intent)intent.getParcelableExtra("target");
if (target != null) {
String pluginLocation = intent.getStringExtra("plugin_location");
ComponentName component = target.getComponent();
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component);
if (plugin == null && pluginLocation != null) {
try {
PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
} catch (Exception var9) {
Log.w("VA.RemoteService", var9);
}
}
}
return super.onStartCommand(intent, flags, startId);
}
}
}
五、ContentProvider的支持
ContentProvider的hook也是采用代理的方式,看调用ContentProvider的测试代码:
// test ContentProvider
Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg);
bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
if (bookCursor != null) {
while (bookCursor.moveToNext()) {
int bookId = bookCursor.getInt(0);
String bookName = bookCursor.getString(1);
Log.d("ryg", "query book:" + bookId + ", " + bookName);
}
bookCursor.close();
}
由于VAInstrumentation中injectActivity的方法,mBase是被设置成了PluginContext,实际上getContentResolver()方法是走到了PluginContext的getContentResolver()方法里面,返回PluginContentResolver对象,由于getContentResolver().query(…)方法调用会走到了PluginContentResolver的acquireProvider()/acquireUnstableProvider()方法里面,通过mPluginManager.getIContentProvider()走到hookIContentProviderAsNeeded()方法中。
VAInstrumentation的injectActivity方法:
protected void injectActivity(Activity activity) {
...
Reflector.with(base).field("mResources").set(plugin.getResources());
Reflector reflector = Reflector.with(activity);
reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
reflector.field("mApplication").set(plugin.getApplication());
...
}
PluginContext的getContentResolver方法是返回PluginContentResolver对象
class PluginContext extends ContextWrapper {
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
}
PluginContentResolver的acquireProvider()方法:
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return super.acquireProvider(context, auth);
}
mPluginManager.getIContentProvider()、hookIContentProviderAsNeeded()。
方法作用是:得到占坑的RemoteContentProvider的uri,调用call()方法,call里面流程是调用acquireProvider->mMainThread.acquireProvider->
ActivityManagerNative.getDefault().getContentProvider->installProvider。
首先调用已经注册provider,得到返回的IContentProvider对象,这个IContentProvider对象是在ActivityThread.installProvider方法中加入到mProviderMap中。
之后去遍历activityThread的mProviderMap集合就是为了去拿到占坑的RemoteContentProvider对应的IContentProvider对象,并通过创建IContentProviderProxy对象进行动态代理的设置。
public synchronized IContentProvider getIContentProvider() {
if (mIContentProvider == null) {
hookIContentProviderAsNeeded();
}
return mIContentProvider;
}
protected void hookIContentProviderAsNeeded() {
//得到占坑的RemoteContentProvider的uri,调用call()方法,调用acquireProvider->mMainThread.acquireProvider->
//ActivityManagerNative.getDefault().getContentProvider->installProvider。
//首先调用已经注册provider,得到返回的IContentProvider对象,这个IContentProvider对象是在ActivityThread.installProvider方法中加入到mProviderMap中。
//最终拿到占坑的RemoteContentProvider对应的IContentProvider对象。
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
try {
Field authority = null;
Field provider = null;
ActivityThread activityThread = ActivityThread.currentActivityThread();
Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
Iterator iter = providerMap.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(RemoteContentProvider.getAuthority(mContext))) {
if (provider == null) {
provider = val.getClass().getDeclaredField("mProvider");
provider.setAccessible(true);
}
IContentProvider rawProvider = (IContentProvider) provider.get(val);
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
看IContentProviderProxy这个类,invoke()方法中的wrapperUri()主要是为了去将需要调用的uri去替换成RemoteContentProvider的uri,然后在去配置需要调用的uri到路径的参数中去
public class IContentProviderProxy implements InvocationHandler {
...
@Override
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();
}
}
private void wrapperUri(Method method, Object[] args) {
Uri uri = null;
int index = 0;
if (args != null) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Uri) {
uri = (Uri) args[i];
index = i;
break;
}
}
}
Bundle bundleInCallMethod = null;
if (method.getName().equals("call")) {
bundleInCallMethod = getBundleParameter(args);
if (bundleInCallMethod != null) {
String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI);
if (uriString != null) {
uri = Uri.parse(uriString);
}
}
}
if (uri == null) {
return;
}
PluginManager pluginManager = PluginManager.getInstance(mContext);
ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0);
if (info != null) {
String pkg = info.packageName;
LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);
Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri);
if (method.getName().equals("call")) {
bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString());
} else {
args[index] = wrapperUri;
}
}
}
}
public class RemoteContentProvider extends ContentProvider {
...
@Override
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;
}
private ContentProvider getContentProvider(final Uri uri) {
final PluginManager pluginManager = PluginManager.getInstance(getContext());
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
final String auth = pluginUri.getAuthority();
ContentProvider cachedProvider = sCachedProviders.get(auth);
if (cachedProvider != null) {
return cachedProvider;
}
synchronized (sCachedProviders) {
LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
if (plugin == null) {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
Log.w(TAG, e);
}
}
final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
if (providerInfo != null) {
RunUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
Log.w(TAG, e);
}
}
}, true);
return sCachedProviders.get(auth);
}
}
}
看RemoteContentProvider的query()方法,ContentProvider provider = getContentProvider(uri)去获取到真实需要调用的ContentProvider,并且进行query调用,这样就通过RemoteContentProvider去间接调用到了目标ContentProvider的方法。
参考链接:
深度 | 滴滴插件化方案 VirtualApk 源码解析
VirtualAPK 资源篇
Android开源框架源码鉴赏:VirtualAPK