什么是插件化?
插件化包括宿主和插件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 {
Singleton<IActivityManager> defaultSingleton;
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 Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@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<T> {
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的四种启动模式。
<!-- Stub Activities -->
<activity android:exported="false" android:name=".A$1" android:launchMode="standard"/>
<activity android:exported="false" android:name=".A$2" android:launchMode="standard"
android:theme="@android:style/Theme.Translucent" />
<!-- Stub Activities -->
<activity android:exported="false" android:name=".B$1" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$2" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$3" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$4" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$5" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$6" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$7" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$8" android:launchMode="singleTop"/>
<!-- Stub Activities -->
<activity android:exported="false" android:name=".C$1" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$2" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$3" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$4" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$5" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$6" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$7" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$8" android:launchMode="singleTask"/>
<!-- Stub Activities -->
<activity android:exported="false" android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$8" android:launchMode="singleInstance"/>
如果想了解如何取对应设计模式的占坑,可以查看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
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
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 List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
ComponentName component = intent.getComponent();
List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();
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
Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();
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<ResourcesKey, WeakReference<ResourcesImpl>> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get();
synchronized (resourcesManager) {
HashMap<ResourcesKey, WeakReference<ResourcesImpl>> 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);
}
List<LoadedPlugin> pluginList = 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;
}
九、参考内容
- 聊聊 VirtualAPK 插件化框架的开源
- VirtualAPK:滴滴 Android 插件化的实践之路
- VirtualApk GitHub Wiki官方入门文档
- VirtualAPK 资源加载分析
- Android插件化快速入门与实例解析(VirtualApk)
- 深度 | 滴滴插件化方案 VirtualApk 源码解析
- 插件化探索,滴滴开源框架VirtualAPK的深入分析
- 图书推荐《Android插件化开发指南》