插件化需要解决的几个问题:
- 插件的类是如何加载的
- 插件的资源文件如何获取
- 插件中的四大组件如何启动(Activity、Service、BroadCastReceiver、ContentProvider)
本问以VirtualApk为例 分析插件化实现的几个关键点:
一、VirualApk中的几个重要的类
1、LoadedPlugin 代表一个被加载的插件
LoadedPlugin 中持有插件Apk的保存位置,插件自己独有的ClassLoader、Resources、PluginContext(上下文)和PluginPakageManager(插件包管理器)
public class LoadedPlugin {
//PluginManager实例
protected PluginManager mPluginManager;
//宿主的Context
protected Context mHostContext;
//保存被加载的apk的位置
protected final String mLocation;
//插件自己的Context
protected Context mPluginContext;
//插件自己的Resource
protected Resources mResources;
//插件自己的ClassLoader
protected ClassLoader mClassLoader;
//插件自己的PackageManager
protected PluginPackageManager mPackageManager;
//插件的Application
protected Application mApplication;
//插件中注册的Activity、Service、Receiver、ContentProvider
protected Map<ComponentName, ActivityInfo> mActivityInfos;
protected Map<ComponentName, ServiceInfo> mServiceInfos;
protected Map<ComponentName, ActivityInfo> mReceiverInfos;
protected Map<ComponentName, ProviderInfo> mProviderInfos;
protected Map<String, ProviderInfo> mProviders; // key is authorities of provider
}
2、PluginContext
PluginContext 插件中使用的上下文,通过复写 getApplication()、getClassLoader()、getResources()、getAssets()、getContentResolver()、getPackageManager() 等方法,返回插件的Application、ClassLoader、Resoures、AssetManager、ContentProvider、PackageManager。
class PluginContext extends ContextWrapper {
private final LoadedPlugin mPlugin;
public PluginContext(LoadedPlugin plugin) {
super(plugin.getPluginManager().getHostContext());
this.mPlugin = plugin;
}
public PluginContext(LoadedPlugin plugin, Context base) {
super(base);
this.mPlugin = plugin;
}
@Override
public Context getApplicationContext() {
return this.mPlugin.getApplication();
}
private Context getHostContext() {
return getBaseContext();
}
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
@Override
public ClassLoader getClassLoader() {
return this.mPlugin.getClassLoader();
}
@Override
public PackageManager getPackageManager() {
return this.mPlugin.getPackageManager();
}
@Override
public Resources getResources() {
return this.mPlugin.getResources();
}
@Override
public AssetManager getAssets() {
return this.mPlugin.getAssets();
}
@Override
public Resources.Theme getTheme() {
return this.mPlugin.getTheme();
}
}
3、PluginManager
PlugInManager是VirtualApk的大管家,持有宿主的Content、application,维护着所有加载过的插件LoadedApk,hook的系统的Instrumentation、ActivityManager、ContentProvider
public class PluginManager {
//HostApp的Content和Application
protected final Context mContext;
protected final Application mApplication;
//维护所有加载过的LoadedPlugin插件
protected final Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();
//hook的系统的Instumentation
protected VAInstrumentation mInstrumentation; // Hooked instrumentation
//hook系统的ActivityManager
protected IActivityManager mActivityManager; // Hooked IActivityManager binder
//hook系统的ContentProvider
protected IContentProvider mIContentProvider; // Hooked IContentProvider binder
}
二、插件中ClassLoader、Resources 是如何创建的,so是如何处理的。
2.1、插件的ClassLoader
public class LoadedPlugin {
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {
DexUtil.insertDex(loader, parent, libsDir);
}
return loader;
}
}
LoadedPlugin中createClassLoader()方法创建自己的ClassLoader();
- 以宿主的classLoader为父classLoader,创建了一个DexClassLoader作为插件的classLoader,并指定了dexPath为插件apk的路径。
- 如何指定了Constants.COMBINE_CLASSLOADER,则会修改宿主classLoader,将插件classLoader的dexElements和宿主ClassLoader的dexElements的合并(宿主在前、插件在后),使宿主也可以加载插件中的类。
2.2、插件的Resources是如何创建的。
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {
//(3)COMBINE_RESOURCES模式下 resource处理
return
ResourcesManager.createResources(context, packageName, apk);
} else {
//(1)利用插件的assetManager 创建一个Resources对象
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
- 插件中创建Resources核心操作:创建一个属于插件的AssetManager,利用assetManager和宿主的DisplayMetric和configuration 创建一个独立的Resources实例作为插件的Resources
- 创建中创建AssetManager实例:创建一个空的AssetManager,利用反射调用addAssetPath方法,加载插件中的资源(包括res和assets)
//创建插件的AssetManager实例
protected AssetManager createAssetManager(Context context, File apk) throws Exception {
AssetManager am = AssetManager.class.newInstance();
Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath());
return am;
}
- 如果开启了COMBINE_RESOURCES合并资源模式,处理会稍微复杂一些。
首先在创建插件AssetManager时,会通过addAssetPath,将宿主的资源目录,正在被加载的apk,已经加载过的所有插件的apk资源 都添加到新创建的AssetManager
其次利用符合的AssetManager创建的Resources作为插件的Resources,可以访问访问宿主和所有插件的Resource资源
再次 还有hook 替换掉宿主Context中的Resrouces
这样,宿主和插件使用同一个Resources对象,可以访问宿主和插件中的资源
2.3、插件的so是如何处理的
VirtualApk 会在宿主的内部路径创建一个“valibs”文件夹,来保存插件的so文件。
安装插件时,会将插件apk中的so,选择一个合适的arm-abi拷贝到valibs下。
protected void tryToCopyNativeLib(File apk) throws Exception {
PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir);
}
public static void copyNativeLib(File apk, Context context, PackageInfo packageInfo, File nativeLibDir) throws Exception {
long startTime = System.currentTimeMillis();
ZipFile zipfile = new ZipFile(apk.getAbsolutePath());
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
for (String cpuArch : Build.SUPPORTED_ABIS) {
if (findAndCopyNativeLib(zipfile, context, cpuArch, packageInfo, nativeLibDir)) {
return;
}
}
} else {
if (findAndCopyNativeLib(zipfile, context, Build.CPU_ABI, packageInfo, nativeLibDir)) {
return;
}
}
findAndCopyNativeLib(zipfile, context, "armeabi", packageInfo, nativeLibDir);
} finally {
zipfile.close();
Log.d(TAG, "Done! +" + (System.currentTimeMillis() - startTime) + "ms");
}
}
三、 四大组件是如何启动的。
3.1、Activity的启动
VirtualApk也是采用占坑位的方式,在宿主的Manifest中预先注册了多个Activity。
利用VmInstrumentation类Hook了系统的Instrumenation类。
为什么要hookInstrumentation类呢?因为Activity的创建、启动、以及各个声明周期的回调 都会经过Instrumentation,所以在Instrumentation中对Activity进行替换是最佳的一个hook点。
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
}
protected void injectIntent(Intent intent) {
//(1)将隐式的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
//(2)将Intent中启动的activity 替换成宿主中占位的Activity,并将真正要启动的Activity信息 以参数的形式 保存在原Intent中
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
}
}
Activity启动前 会调用execStartActivity()方法,
在此方法中将待启动的插件中的Activity 替换为宿主中占位的Activity
-(1)将隐式的Intent 转换为显式的Intent
- (2)将Intent中启动的activity 替换成宿主中占位的Activity,并将真正要启动的Activity信息 以参数的形式 保存在原Intent中
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);
}
}
执行execStartActivity()后,启动Activity的信息会通过IPC 传递给ActivityManagerService,ActivityManagerService 对Activity进行校验通过之后,会通知原app进程来创建Activity实例,最终会调用Instrumentation.newActivity()
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
@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));
//(1) 创建占位Activity时,因为宿主中 只对StubActivity进行了声明,而并没有对应的Class,所以此时会异常,异常时 尝试创建插件的Activity
} 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.
Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
}
//(2) 创建 利用插件的ClassLoader 创建插件的Activity实例
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
//(3)替换插件Activity中的Resources
// for 4.1+
Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
return newActivity(activity);
}
return newActivity(mBase.newActivity(cl, className, intent));
}
}
在Instrumentation的newActivity方法中,找出要启动的真正的Activity类 创建实例,并返回给ActivityManagerService.
- (1) 创建占位Activity时,因为宿主中 只对StubActivity进行了声明,而并没有对应的Class,所以此时会异常,异常时 尝试创建插件的Activity
- (2) 创建 利用插件的ClassLoader 创建插件的Activity实例
- (3)替换插件Activity中的Resources
3.2、Service的启动
VirtualApk中 Mainifest中预留了两个Service(LocalService和RemoteService),作为占位的本进程Service和独立进程Service。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.didi.virtualapk.core">
<application>
<!-- Local Service running in main process -->
<service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" />
<!-- Daemon Service running in child process -->
<service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
<intent-filter>
<action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>
VirtualApk hook了系统的ActivityManagerProxy类,利用动态代理技术,接管了Service的启动。
/**
* hookSystemServices, but need to compatible with Android O in future.
*/
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);
}
}
- 启动插件中的Service时,会将intent中的Component替换为宿主中占位的LocalServie或者RemoteService
- 真正的插件Service和Service的操作 都保存在Intent的参数中
- 待LocalService或者RemoteService启动之后,onStartCommand()方法中取出插件Service相关的参数,然后在LocalService或者RemoteService中 创建插件Service的实例,并调用相应的回调方法。
public class LocalService extends Service {
private static final String TAG = Constants.TAG_PREFIX + "LocalService";
/**
* The target service, usually it's a plugin service intent
*/
public static final String EXTRA_TARGET = "target";
public static final String EXTRA_COMMAND = "command";
public static final String EXTRA_PLUGIN_LOCATION = "plugin_location";
public static final int EXTRA_COMMAND_START_SERVICE = 1;
public static final int EXTRA_COMMAND_STOP_SERVICE = 2;
public static final int EXTRA_COMMAND_BIND_SERVICE = 3;
public static final int EXTRA_COMMAND_UNBIND_SERVICE = 4;
private PluginManager mPluginManager;
@Override
public IBinder onBind(Intent intent) {
return new Binder();
}
@Override
public void onCreate() {
super.onCreate();
mPluginManager = PluginManager.getInstance(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ComponentName component = target.getComponent();
LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
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;
}
case EXTRA_COMMAND_BIND_SERVICE: {
ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service = null;
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) {
Log.w(TAG, t);
}
}
try {
IBinder binder = service.onBind(target);
IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
if (Build.VERSION.SDK_INT >= 26) {
iServiceConnection.connected(component, binder, false);
} else {
Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
}
} catch (Exception e) {
Log.w(TAG, e);
}
break;
}
case EXTRA_COMMAND_STOP_SERVICE: {
Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
if (null != service) {
try {
service.onDestroy();
} catch (Exception e) {
Log.e(TAG, "Unable to stop service " + service + ": " + e.toString());
}
} else {
Log.i(TAG, component + " not found");
}
break;
}
case EXTRA_COMMAND_UNBIND_SERVICE: {
Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
if (null != service) {
try {
service.onUnbind(target);
service.onDestroy();
} catch (Exception e) {
Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString());
}
} else {
Log.i(TAG, component + " not found");
}
break;
}
}
return START_STICKY;
}
}
3.3、ContentProvider的启动
VirtualApk中预留了RemoteContentProvider的坑位ContentProivder
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.didi.virtualapk.core">
<application>
<provider
android:exported="false"
android:name="com.didi.virtualapk.delegate.RemoteContentProvider"
android:authorities="${applicationId}.VirtualAPK.Provider"
android:process=":daemon" />
</application>
</manifest>
ContentProvider的支持依然是通过代理分发。
Cursor bookCursor = getContentResolvewr().query(bookUri,new String[]{"_id","name"},null,null,null)
这里用到了PluginContext,在生成Activity、Service的时候,为其设置的Context都为PluginContext对象。
所以当你调用getContentResolver时,调用的为PluginContext的getContentResolver。
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
返回的是一个PluginContentResolver对象,当我们调用query方法时,会辗转调用到
ContentResolver.acquireUnstableProvider方法。该方法被PluginContentResolver中复写:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected IContentProvider acquireUnstableProvider(Context context, String auth) {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return super.acquireUnstableProvider(context, auth);
}
PluginManager.getIContentProvider()返回的又是什么呢?
PluginManager.getIContentProvider() 返回的其实是RemoteContentProvider的代理对象IContentProviderProxy。
VirtualApk hook了宿主的RemoteContentProvider,利用动态代理接管了RemoteContentProvider的操作。
protected void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
//(1)提前请求一些RemoteContentProvider 创建RemoteContentProvider的实例
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);
//(2)利用动态代理 接管RemoteContentProvider的操作
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
- (1)提前请求一些RemoteContentProvider 创建RemoteContentProvider的实例
- (2)利用动态代理 接管RemoteContentProvider的操作
有了IContentProviderProxy对象 就可以拦截query、insert、update、delete操作,把用户调用的uri,替换为占坑provider(RemouteContentPrpvider)的uri,再把原本的uri作为参数拼接在占坑provider的uri后面。
RemouteContentProvider中收到请求后,
取出原本的uri,拿到auth,在通过加载plugin得到providerInfo,反射生成provider对象,在调用其attachInfo方法,即完成了ContentProvider的代理分发。
3.4、BroadCastReciever的注册
BroadcastReceiver的处理比较简单,只需将插件中静态注册的BroadCastReceiver转化成动态注册即可。
// 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);
}
}
3.5、小结:
- Activity:在宿主apk中提前占几个坑,然后通过“欺上瞒下”(这个词好像是360之前的ppt中提到)的方式,启动插件apk的Activity;因为要支持不同的launchMode以及一些特殊的属性,需要占多个坑。
- Service:通过代理Service的方式去分发;主进程和其他进程,VirtualAPK使用了两个代理Service。
- BroadcastReceiver:静态转动态。
- ContentProvider:通过一个代理Provider进行分发。
四、插件中的资源如何被加载
4.1、首先明确什么地方会用到Resouces
Resources是context的行为能力,所有Context的子类 才有可能取到Resources,即 Application、Activity、Service。
另外 ContentProvider 会在attach()方法时 传递进去一个Context对象。
所以天生持有Resource(或者Context)的类 有四个:Application、Activity、Service、ContentProvider
4.2 Application、Activity、Service、ContentProvider中Resources的替换
PluginContext中保存了插件对应的Resource
- Application 是如何替换Resouces的
创建Application对象时,传递的是PluginContext - Activity 中 是如何替换Resouces资源的
VAInstrumentation中 创建Activity 之后, 立即为Activity设置了mResources(PluginContext.Resouces) - Service 中是如何替换资源的
Service.attach() 传递的是PluginContext - ContentProvider 是如何替换Resources资源的
contentProvider.attachInfo() 传递的是PluginContenxt
五、 插件的ClassLoader是如何生效的?
5.1、四大组件创建时ClassLoader的选择
- 创建Application时 用的是Plugin.classLoader()
- 创建Activity时 classLoader 用的是Plugin.getClassLoader()
- 创建Service时 用的Plugin.ClassLoader()
- 创建BroadCastReceiver时, 用的是Plugin.getClassLoader()
- 创建ContentProvider时, 用的是??? 没有用插件的ClassLoader?
5.2、四大组件之外的类如何加载
对于宿主apk,插件是一个相对独立的整体,宿主调用插件,只能通过四大组件来调用(启动插件activity、service、broadcast、contentProvider)。
我们知道,通常情况下
在A类中加载B类,那么加载B类用到的ClassLoadert通常情况下就是A的classLoader
由此得出,我们只需要启动插件Activity时 选用插件的ClassLoader,那么Activity内用到的类,默认都是插件ClassLoader来加载的。
六、其他
6.1、插件apk 是如何解析的?
利用系统自带的PackageParser.parsePackage(),来解析一个APK
6.2、坑位中的Activity是如何匹配的。
Manifest中预埋了 各种模式的Activity,分为ABCD四种:
- A 代表 stand模式的Activity, 2个
- B 代表 singleTop模式的Activity ,8个
- C 代表 singleTask模式的Activity,8个
- D 代表 singleInstance模式的Activity,8个
StubActivityInfo.getStubActivity() 根据launchMode 来选择未使用的预留Activity坑位。
<application>
<activity android:exported="false" android:name="com.didi.virtualapk.delegate.StubActivity" android:launchMode="standard"/>
<!-- 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"/>
<application>
6.3、virtualapk-gradle-plugin 做了什么事情
virtualapk-gradle-plugin 分为host和plugIn两部分
- 宿主-plugin
apply plugin: 'com.didi.virtualapk.host'
1、generateDependencies()记录速录依赖库信息,生成version.txt、allVersions.txt两个文件
2、backupHostR 备份R.txt文件 生成Host_R.txt
3、backupProguardMapping 备份混淆文件,生成mapping.txt
生成四个文件 version.txt、allVersions.txt、Host_R.txt、mapping.txt
- 插件-plugin
插件工程引入
apply plugin: 'com.didi.virtualapk.plugin'
插件app moudle的build.gradle中配置
virtualApk {
packageId = 0x6f // The package id of Resources.
targetHost='source/host/app' // The path of application module in host project.
applyHostMapping = true // [Optional] Default value is true.
}
virtualapk.plugin 根据宿主生成的 version.txt、Host_R.txt、mapping.txt 对插件的依赖、资源进行了去重操作。重复的依赖或资源以宿主依赖和资源为主,去除插件的对应项。