深入理解VirtualApk插件化
什么是插件化?
插件化包括宿主和插件2部分。把需要实现的模块或功能独立的提取出来,比如相册,每个模块相当于一个独立的apk,这个apk就是一个插件。有一个加载相册插件apk的App叫做宿主,宿主是一个普通的App,但包含了加载插件apk的功能,使得插件apk正常运行。插件化可以减少宿主App的规模,可以把插件apk放到服务器上,当需要使用到相应的功能时再去加载相应的插件apk。宿主和插件都有各自的包名和版本号,包名可以区别宿主和各个插件,版本号可用于宿主和插件的升级。
简单介绍下VirtualAPK
VirtualAPK对插件没有额外的约束,原生的apk即可作为插件。插件工程编译生成apk后,即可通过宿主App加载,每个插件apk被加载后,都会在宿主中创建一个单独的LoadedPlugin对象。如下图所示,通过这些LoadedPlugin对象,VirtualAPK就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。
一、从代码入口分析
代码分析基于0.9.8版本
compile 'com.didi.virtualapk:core:0.9.8'
VirtualApk初始化插件引擎需要在Application的attachBaseContext进行
public class VAApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); PluginManager.getInstance(base).init(); } }
getInstalce方法用单例设计模式创建PluginManager对象,使用了synchronized关键字同步锁
public static PluginManager getInstance(Context base) { if (sInstance == null) { synchronized (PluginManager.class) { if (sInstance == null) { sInstance = createInstance(base); } } } return sInstance; }
创建PluginManager对象后,接着会调用hookCurrentProcess方法
protected void hookCurrentProcess() { hookInstrumentationAndHandler(); hookSystemServices(); hookDataBindingUtil(); }
到这里可以知道,hook了Instrumentation、Handler、SystemServices、DataBindingUtil。下面逐个分析下这四个hook流程,这里分析流程的目的是要了解hook是怎么回事,hook了是要干嘛呢。
二、hookInstrumentationAndHandler
先看看hookInstrumentationAndHandler的代码
protected void hookInstrumentationAndHandler() { try { ActivityThread activityThread = ActivityThread.currentActivityThread(); Instrumentation baseInstrumentation = activityThread.getInstrumentation(); final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); Reflector.with(activityThread).field("mInstrumentation").set(instrumentation); Handler mainHandler = Reflector.with(activityThread).method("getHandler").call(); Reflector.with(mainHandler).field("mCallback").set(instrumentation); this.mInstrumentation = instrumentation; Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation); } catch (Exception e) { Log.w(TAG, e); } }
第一行ActivityThread activityThread = ActivityThread.currentActivityThread();这里会不会让你想起什么来,ActivityThread是一个@hide类,为什么可以直接使用@hide类呢?可以按点击去试试看,会跳到AndroidStub模块下的ActivityThread。AndroidStub定义了许多路径一样的类但是里面都是实现抛出RuntimeException.为了尽量避免使用反射浪费性能,使用了AndroidStub模块来欺骗编译器。欺骗编译器需要查看Android framework层源码,定义和原码中一摸一样的方法,实现抛出RuntimeException。CoreLibrary使用provided依赖AndroidStub,provided依赖是不打包依赖包,而是运行时提供,所以成功欺骗了编辑器,用来提高了性能。
public final class ActivityThread { public static ActivityThread currentActivityThread() { throw new RuntimeException("Stub!"); } ... }
CoreLibrary使用provided依赖AndroidStub
final String projectAndroidStub = ':AndroidStub' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) provided project(projectAndroidStub) }
现在回到hook instrumentation上,使用了Reflector反射器直接把framework层的ActivityThread类下mInstrumentation变量变成了VAInstrumentation,使得VAInstrumentation起到了代理作用。
final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
代理的目的就是先让VAInstrumentation处理自己的逻辑,处理完后再给framework层的Instrumentation处理,以实现达到欺骗系统的作用,校验的是宿主占坑Activity,启动插件中的Activity。
public class VAInstrumentation extends Instrumentation implements Handler.Callback { ... protected Instrumentation mBase; @Override public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { injectIntent(intent); return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options); } protected void injectIntent(Intent intent) { 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); } } ... }
现在来看看hook Handler,
Handler mainHandler = Reflector.with(activityThread).method("getHandler").call(); Reflector.with(mainHandler).field("mCallback").set(instrumentation);
看看ActivityThread源码下getHandler()是什么?原来是H类。
public final class ActivityThread extends ClientTransactionHandler { ... final H mH = new H(); final Handler getHandler() { return mH; } ... }
那为什么可以直接把H类的mCallback直接替换成功VAInstrumentation实现的Handler.Callback不会引起其他问题,导致无法执行H类的handleMessage呢?看看Handler源码就知道了。new H()的时候mCallback为null,使用代理VAInstrumentation后mCallback.handleMessage(msg)会一直返回false,会继续执行handleMessage方法。起到了代理H类的效果,先执行VAInstrumentation的handleMessage,再执行H类的handleMessage。
public class Handler { ... public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { ... mCallback = callback; mAsynchronous = async; } public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } ... }
三、hookSystemServices
hook SystemServices的时候,先从ActivityManager.class或ActivityManagerNative.class中反射获取Singleton
对象,再使用ActivityManagerProxy动态代理动态代理获取一个 IActivityManager.
protected void hookSystemServices() { try { SingletondefaultSingleton; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get(); } else { defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get(); } IActivityManager origin = defaultSingleton.get(); IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class }, createActivityManagerProxy(origin)); // Hook IActivityManager from ActivityManagerNative Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); if (defaultSingleton.get() == activityManagerProxy) { this.mActivityManager = activityManagerProxy; Log.d(TAG, "hookSystemServices succeed : " + mActivityManager); } } catch (Exception e) { Log.w(TAG, e); } }
ActivityManager.class下的IActivityManagerSingleton
public class ActivityManager { ... private static final SingletonIActivityManagerSingleton = new Singleton () { @Override protected IActivityManager create() { final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE); final IActivityManager am = IActivityManager.Stub.asInterface(b); return am; } }; } ... }
Reflector.with(defaultSingleton).field(“mInstance”).set(activityManagerProxy);这里的mInstance其实是Singleton
类的mInstance变量。
public abstract class Singleton{ private T mInstance; protected abstract T create(); public final T get() { synchronized (this) { if (mInstance == null) { mInstance = create(); } return mInstance; } } }
三、hook DataBindingUtil
分析完上面2个原理,到这里应该比较清晰hook是什么回事了,就是在执行framework层代码之前,先执行Proxy代理的代码,来实现一些意想不到的效果。hook DataBindingUtil可以自己看看代码分析下。
protected void hookDataBindingUtil() { Reflector.QuietReflector reflector = Reflector.QuietReflector.on("android.databinding.DataBindingUtil").field("sMapper"); Object old = reflector.get(); if (old != null) { try { Callback callback = Reflector.on("android.databinding.DataBinderMapperProxy").constructor().newInstance(); reflector.set(callback); addCallback(callback); Log.d(TAG, "hookDataBindingUtil succeed : " + callback); } catch (Reflector.ReflectedException e) { Log.w(TAG, e); } } }
四、支持插件中的Activity
这里需要了解Activity的启动流程,如果你还没有了解可以点击这里。前面我们了解了hook Instrumentation,看看VAInstrumentation到底做了什么。启动Activity时会执行execStartActivity,而在执行execStartActivity之前做了injectIntent,是为了绕过系统校验是否在宿主的AndroidManifest.xml中注册过插件中的Activity。也就是要达到插件中的Activity不用在宿主中注册就可以启动。
@Override public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { injectIntent(intent); return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options); } protected void injectIntent(Intent intent) { 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); } }
transformIntentToExplicitAsNeeded作用是把隐式启动的Activity转化为显式启动。下图可以知道把Intent { act=com.didi.virtualapk.plugin.BookManagerActivity }转化为显式Intent { act=com.didi.virtualapk.plugin.BookManagerActivity cmp=com.didi.virtualapk.demo/.aidl.BookManagerActivity },ComponentName由null变成包含BookManagerActivity数据的ComponentName。
markIntentIfNeeded其实就是记录了下插件的信息包括isPlugin、插件package、要启动的插件Activity类,记录的目的是绕过系统校验后,再把这些信息取出来,启动真正要启动的插件Activity。
public void markIntentIfNeeded(Intent intent) { if (intent.getComponent() == null) { return; } String targetPackageName = intent.getComponent().getPackageName(); String targetClassName = intent.getComponent().getClassName(); // search map and return specific launchmode stub activity if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) { intent.putExtra(Constants.KEY_IS_PLUGIN, true); intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName); intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName); dispatchStubActivity(intent); } }
这里要看看dispatchStubActivity把targetActivity替换成stubActivity的过程。
图中可以看到stubActivity是com.didi.virtualapk.core.A$1,这是CoreLibrary/src/main/AndroidManifest.xml下提前注册占坑Activity。包含了四种启动模式,不同的启动模式取不同的占坑Activity,达到支持插件Activity的四种启动模式。
如果想了解如何取对应设计模式的占坑,可以查看StubActivityInfo类,如取值stubActivity为com.didi.virtualapk.core.A$1
public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d"; stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
injectIntent绕过校验后,会执行newActivity,在classloader加载占坑类com.didi.virtualapk.core.A$1时,由于只是占坑,不存在这个类,会走ClassNotFoundException异常逻辑。
@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); Log.i(TAG, String.format("newActivity[%s]", className)); } catch (ClassNotFoundException e) { ComponentName component = PluginUtil.getComponent(intent); if (component == null) { return newActivity(mBase.newActivity(cl, className, intent)); } String targetClassName = component.getClassName(); Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName)); LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); if (plugin == null) { // Not found then goto stub activity. boolean debuggable = false; try { Context context = this.mPluginManager.getHostContext(); debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } catch (Throwable ex) { } if (debuggable) { throw new ActivityNotFoundException("error intent: " + intent.toURI()); } Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class); return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent)); } Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); // for 4.1+ Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); return newActivity(activity); } return newActivity(mBase.newActivity(cl, className, intent)); }
通过this.mPluginManager.getLoadedPlugin(component)获取已经加载的插件,并重新设置了恢复了要启动的插件Activity。就这样callActivityOnCreate的时候也是调用要启动的插件Activity。
@Override public void callActivityOnCreate(Activity activity, Bundle icicle) { injectActivity(activity); mBase.callActivityOnCreate(activity, icicle); }
五、支持插件中的Service
前面已经了解了hookSystemServices的过程,是使用了动态代理生成代理类。
IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class }, createActivityManagerProxy(origin));
再看看createActivityManagerProxy做了什么
protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception { return new ActivityManagerProxy(this, origin); }
既然用了动态代理,那就看看ActivityManagerProxy的invoke方法
@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); } } ... }
在执行Service生命周期等关键方法时都做了相应的代理处理,看看startService
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); }
resolveService判断是不是宿主工程的Service,宿主Service走原来的逻辑,插件Service就走startDelegateServiceForTarget
public ResolveInfo resolveService(Intent intent, int flags) { for (LoadedPlugin plugin : this.mPlugins.values()) { ResolveInfo resolveInfo = plugin.resolveService(intent, flags); if (null != resolveInfo) { return resolveInfo; } } return null; }
startDelegateServiceForTarget里面执行wrapperTargetIntent,
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); return mPluginManager.getHostContext().startService(wrapperIntent); }
关键点local ? LocalService.class : RemoteService.class,明确了需要哪个Service做代理类。
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; }
RemoteService继承了LocalService,RemoteService作用是loadPlugin,其他工作交给LocalService。这样做也合理,如果是插件的交给RemoteService来loadPlugin,省下的逻辑都是相同的交给LocalService处理就可以了。
public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { return super.onStartCommand(intent, flags, startId); } Intent target = intent.getParcelableExtra(EXTRA_TARGET); if (target != null) { String pluginLocation = intent.getStringExtra(EXTRA_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 e) { Log.w(TAG, e); } } } return super.onStartCommand(intent, flags, startId); }
LocalService按照service的启动流程,loadClass先加载service,反射调用attach,在调用onCreate方法,rememberService记录attach后的service,再调用service.onStartCommand执行命令。如果是启动了的直接调用onStartCommand。
@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; } ... } return START_STICKY; }
TODO:分析动态代理思想
六、支持插件中的BroadcastReceiver
思路是动态注册广播,将静态注册的广播转变为动态注册,将插件中静态注册的receiver使用mHostContext重新注册一遍。具体代码可以在LoadedPlugin构造方法查看。
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { ... // Register broadcast receivers dynamically Mapreceivers = new HashMap (); for (PackageParser.Activity receiver : this.mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); for (PackageParser.ActivityIntentInfo aii : receiver.intents) { this.mHostContext.registerReceiver(br, aii); } } this.mReceiverInfos = Collections.unmodifiableMap(receivers); this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]); // try to invoke plugin's application invokeApplication(); }
再看看查询所有的receivers,先对比ComponentName是否相同,component为空时再用intent等去匹配。
public ListqueryBroadcastReceivers(Intent intent, int flags) { ComponentName component = intent.getComponent(); List resolveInfos = new ArrayList (); ContentResolver resolver = this.mPluginContext.getContentResolver(); for (PackageParser.Activity receiver : this.mPackage.receivers) { if (receiver.getComponentName().equals(component)) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = receiver.info; resolveInfos.add(resolveInfo); } else if (component == null) { // only match implicit intent for (PackageParser.ActivityIntentInfo intentInfo : receiver.intents) { if (intentInfo.match(resolver, intent, true, TAG) >= 0) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = receiver.info; resolveInfos.add(resolveInfo); break; } } } } return resolveInfos; }
七、支持插件中的ContentProvider
先看一下插件化是如何使用ContentProvider的,获取插件LoadedPlugin后,通过PluginContentResolver的wrapperUri转化Uri为后续支持读取的Uri。
// 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(); }
是用wrapperUri转化下Uri后就可以正常读取ContentProvider的数据了。这里使用到了RemoteContentProvider,这个转化其实就是后面会使用到RemoteContentProvider。
public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) { String pkg = loadedPlugin.getPackageName(); String pluginUriString = Uri.encode(pluginUri.toString()); StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext())); builder.append("/?plugin=" + loadedPlugin.getLocation()); builder.append("&pkg=" + pkg); builder.append("&uri=" + pluginUriString); Uri wrapperUri = Uri.parse(builder.toString()); return wrapperUri; }
wrapperUri会拼接成
content://com.libill.virtualapk.VirtualAPK.Provider/?plugin=/storage/emulated/0/Test.apk&pkg=com.didi.virtualapk.demo&uri=content%3A%2F%2Fcom.didi.virtualapk.demo.book.provider%2Fbook
我们先看看pluginManager.loadPlugin(apk)时做了一些解释数据,并缓存起来。
// Cache providers Mapproviders = new HashMap (); Map providerInfos = new HashMap (); for (PackageParser.Provider provider : this.mPackage.providers) { providers.put(provider.info.authority, provider.info); providerInfos.put(provider.getComponentName(), provider.info); } this.mProviders = Collections.unmodifiableMap(providers); this.mProviderInfos = Collections.unmodifiableMap(providerInfos); this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);
当使用getContentResolver的query时,会通过IPC通信,调用acquireProvider获取IContentProvider的binder对象。而PluginContentResolver对acquireProvider等多个方法做了代理。
@Override protected IContentProvider acquireProvider(Context context, String auth) { if (mPluginManager.resolveContentProvider(auth, 0) != null) { return mPluginManager.getIContentProvider(); } return super.acquireProvider(context, auth); }
在getIContentProvider方法,做了hook IContentProvider动作。
public synchronized IContentProvider getIContentProvider() { if (mIContentProvider == null) { hookIContentProviderAsNeeded(); } return mIContentProvider; }
使用了反射器对mProviderMap反射操作,获取mProviderMap来能获取到占坑的 Provider。对authority、mProvider做了setAccessible(true),最终IContentProviderProxy.newInstance生成IContentProviderProxy对象。这里还使用了RemoteContentProvider包装Uri。
protected void hookIContentProviderAsNeeded() { 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); } }
IContentProvider使用了动态代理方式
public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) { return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(), new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider)); }
看看invoke方法,使用wrapperUri后直接调用method.invoke
@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(); } }
在RemoteContentProvider的getContentProvider可以看到加载和使用插件的ContentProvider。
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); } } return null; }
八、支持插件中的Resources
资源的加载入口时加载插件时生成LoadedPlugin对象,这时开始加载资源。
this.mResources = createResources(context, getPackageName(), apk);
Constants.COMBINE_RESOURCES一直为true,直接进入ResourcesManager.createResources
protected Resources createResources(Context context, String packageName, File apk) throws Exception { if (Constants.COMBINE_RESOURCES) { return ResourcesManager.createResources(context, packageName, apk); } else { Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk); return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } }
createResources加载资源后,用hook Resources一遍
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return createResourcesForN(hostContext, packageName, apk); } Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath()); ResourcesManager.hookResources(hostContext, resources); return resources; }
createResourcesForN是兼容N
@TargetApi(Build.VERSION_CODES.N) private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception { long startTime = System.currentTimeMillis(); String newAssetPath = apk.getAbsolutePath(); ApplicationInfo info = context.getApplicationInfo(); String baseResDir = info.publicSourceDir; info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); LoadedApk loadedApk = Reflector.with(context).field("mPackageInfo").get(); Reflector rLoadedApk = Reflector.with(loadedApk).field("mSplitResDirs"); String[] splitResDirs = rLoadedApk.get(); rLoadedApk.set(append(splitResDirs, newAssetPath)); final android.app.ResourcesManager resourcesManager = android.app.ResourcesManager.getInstance(); ArrayMap> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get(); synchronized (resourcesManager) { HashMap > resolvedMap = new HashMap<>(); if (Build.VERSION.SDK_INT >= 28 || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // P Preview ResourcesManagerCompatForP.resolveResourcesImplMap(originalMap, resolvedMap, context, loadedApk); } else { ResourcesManagerCompatForN.resolveResourcesImplMap(originalMap, resolvedMap, baseResDir, newAssetPath); } originalMap.clear(); originalMap.putAll(resolvedMap); } android.app.ResourcesManager.getInstance().appendLibAssetForMainAssetPath(baseResDir, packageName + ".vastub"); Resources newResources = context.getResources(); // lastly, sync all LoadedPlugin to newResources for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) { plugin.updateResources(newResources); } Log.d(TAG, "createResourcesForN cost time: +" + (System.currentTimeMillis() - startTime) + "ms"); return newResources; }
按照系统版本、厂家来兼容
private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception { Resources hostResources = hostContext.getResources(); Resources newResources = null; AssetManager assetManager; Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { assetManager = AssetManager.class.newInstance(); reflector.bind(assetManager); final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);; if (cookie1 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir); } } else { assetManager = hostResources.getAssets(); reflector.bind(assetManager); } final int cookie2 = reflector.call(apk); if (cookie2 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + apk); } ListpluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); for (LoadedPlugin plugin : pluginList) { final int cookie3 = reflector.call(plugin.getLocation()); if (cookie3 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation()); } } if (isMiUi(hostResources)) { newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); } else if (isVivo(hostResources)) { newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); } else if (isNubia(hostResources)) { newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); } else if (isNotRawResources(hostResources)) { newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); } else { // is raw android resources newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } // lastly, sync all LoadedPlugin to newResources for (LoadedPlugin plugin : pluginList) { plugin.updateResources(newResources); } return newResources; }