插件化需要解決的幾個(gè)問題:
- 插件的類是如何加載的
- 插件的資源文件如何獲取
- 插件中的四大組件如何啟動(dòng)(Activity丙躏、Service、BroadCastReceiver矿辽、ContentProvider)
本問以VirtualApk為例 分析插件化實(shí)現(xiàn)的幾個(gè)關(guān)鍵點(diǎn):
一拍霜、VirualApk中的幾個(gè)重要的類
1筋蓖、LoadedPlugin 代表一個(gè)被加載的插件
LoadedPlugin 中持有插件Apk的保存位置,插件自己獨(dú)有的ClassLoader、Resources澜搅、PluginContext(上下文)和PluginPakageManager(插件包管理器)
public class LoadedPlugin {
//PluginManager實(shí)例
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;
//插件中注冊(cè)的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 插件中使用的上下文,通過復(fù)寫 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,維護(hù)著所有加載過的插件LoadedApk,hook的系統(tǒng)的Instrumentation弥臼、ActivityManager宴咧、ContentProvider
public class PluginManager {
//HostApp的Content和Application
protected final Context mContext;
protected final Application mApplication;
//維護(hù)所有加載過的LoadedPlugin插件
protected final Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();
//hook的系統(tǒng)的Instumentation
protected VAInstrumentation mInstrumentation; // Hooked instrumentation
//hook系統(tǒng)的ActivityManager
protected IActivityManager mActivityManager; // Hooked IActivityManager binder
//hook系統(tǒng)的ContentProvider
protected IContentProvider mIContentProvider; // Hooked IContentProvider binder
}
二、插件中ClassLoader径缅、Resources 是如何創(chuàng)建的掺栅,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()方法創(chuàng)建自己的ClassLoader();
- 以宿主的classLoader為父classLoader,創(chuàng)建了一個(gè)DexClassLoader作為插件的classLoader,并指定了dexPath為插件apk的路徑氧卧。
- 如何指定了Constants.COMBINE_CLASSLOADER,則會(huì)修改宿主classLoader,將插件classLoader的dexElements和宿主ClassLoader的dexElements的合并(宿主在前氏堤、插件在后)沙绝,使宿主也可以加載插件中的類。
2.2、插件的Resources是如何創(chuàng)建的闪檬。
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 創(chuàng)建一個(gè)Resources對(duì)象
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
- 插件中創(chuàng)建Resources核心操作:創(chuàng)建一個(gè)屬于插件的AssetManager,利用assetManager和宿主的DisplayMetric和configuration 創(chuàng)建一個(gè)獨(dú)立的Resources實(shí)例作為插件的Resources
- 創(chuàng)建中創(chuàng)建AssetManager實(shí)例:創(chuàng)建一個(gè)空的AssetManager星著,利用反射調(diào)用addAssetPath方法,加載插件中的資源(包括res和assets)
//創(chuàng)建插件的AssetManager實(shí)例
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合并資源模式,處理會(huì)稍微復(fù)雜一些。
首先在創(chuàng)建插件AssetManager時(shí),會(huì)通過addAssetPath,將宿主的資源目錄,正在被加載的apk,已經(jīng)加載過的所有插件的apk資源 都添加到新創(chuàng)建的AssetManager
其次利用符合的AssetManager創(chuàng)建的Resources作為插件的Resources,可以訪問訪問宿主和所有插件的Resource資源
再次 還有hook 替換掉宿主Context中的Resrouces
這樣粗悯,宿主和插件使用同一個(gè)Resources對(duì)象虚循,可以訪問宿主和插件中的資源
2.3、插件的so是如何處理的
VirtualApk 會(huì)在宿主的內(nèi)部路徑創(chuàng)建一個(gè)“valibs”文件夾,來保存插件的so文件样傍。
安裝插件時(shí),會(huì)將插件apk中的so,選擇一個(gè)合適的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");
}
}
三、 四大組件是如何啟動(dòng)的衫哥。
3.1剪廉、Activity的啟動(dòng)
VirtualApk也是采用占坑位的方式,在宿主的Manifest中預(yù)先注冊(cè)了多個(gè)Activity炕檩。
利用VmInstrumentation類Hook了系統(tǒng)的Instrumenation類斗蒋。
為什么要hookInstrumentation類呢?因?yàn)锳ctivity的創(chuàng)建、啟動(dòng)笛质、以及各個(gè)聲明周期的回調(diào) 都會(huì)經(jīng)過Instrumentation泉沾,所以在Instrumentation中對(duì)Activity進(jìn)行替換是最佳的一個(gè)hook點(diǎn)。
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 轉(zhuǎn)換為顯式的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中啟動(dòng)的activity 替換成宿主中占位的Activity,并將真正要啟動(dòng)的Activity信息 以參數(shù)的形式 保存在原Intent中
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
}
}
Activity啟動(dòng)前 會(huì)調(diào)用execStartActivity()方法妇押,
在此方法中將待啟動(dòng)的插件中的Activity 替換為宿主中占位的Activity
-(1)將隱式的Intent 轉(zhuǎn)換為顯式的Intent
- (2)將Intent中啟動(dòng)的activity 替換成宿主中占位的Activity,并將真正要啟動(dòng)的Activity信息 以參數(shù)的形式 保存在原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);
}
}
執(zhí)行execStartActivity()后跷究,啟動(dòng)Activity的信息會(huì)通過IPC 傳遞給ActivityManagerService,ActivityManagerService 對(duì)Activity進(jìn)行校驗(yàn)通過之后,會(huì)通知原app進(jìn)程來創(chuàng)建Activity實(shí)例,最終會(huì)調(diào)用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) 創(chuàng)建占位Activity時(shí),因?yàn)樗拗髦?只對(duì)StubActivity進(jìn)行了聲明,而并沒有對(duì)應(yīng)的Class,所以此時(shí)會(huì)異常,異常時(shí) 嘗試創(chuàng)建插件的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) 創(chuàng)建 利用插件的ClassLoader 創(chuàng)建插件的Activity實(shí)例
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方法中,找出要啟動(dòng)的真正的Activity類 創(chuàng)建實(shí)例敲霍,并返回給ActivityManagerService.
- (1) 創(chuàng)建占位Activity時(shí),因?yàn)樗拗髦?只對(duì)StubActivity進(jìn)行了聲明,而并沒有對(duì)應(yīng)的Class俊马,所以此時(shí)會(huì)異常,異常時(shí) 嘗試創(chuàng)建插件的Activity
- (2) 創(chuàng)建 利用插件的ClassLoader 創(chuàng)建插件的Activity實(shí)例
- (3)替換插件Activity中的Resources
3.2、Service的啟動(dòng)
VirtualApk中 Mainifest中預(yù)留了兩個(gè)Service(LocalService和RemoteService)肩杈,作為占位的本進(jìn)程Service和獨(dú)立進(jìn)程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了系統(tǒng)的ActivityManagerProxy類,利用動(dòng)態(tài)代理技術(shù),接管了Service的啟動(dòng)。
/**
* 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);
}
}
- 啟動(dòng)插件中的Service時(shí),會(huì)將intent中的Component替換為宿主中占位的LocalServie或者RemoteService
- 真正的插件Service和Service的操作 都保存在Intent的參數(shù)中
- 待LocalService或者RemoteService啟動(dòng)之后,onStartCommand()方法中取出插件Service相關(guān)的參數(shù),然后在LocalService或者RemoteService中 創(chuàng)建插件Service的實(shí)例扩然,并調(diào)用相應(yīng)的回調(diào)方法艘儒。
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的啟動(dòng)
VirtualApk中預(yù)留了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的支持依然是通過代理分發(fā)夫偶。
Cursor bookCursor = getContentResolvewr().query(bookUri,new String[]{"_id","name"},null,null,null)
這里用到了PluginContext界睁,在生成Activity、Service的時(shí)候兵拢,為其設(shè)置的Context都為PluginContext對(duì)象翻斟。
所以當(dāng)你調(diào)用getContentResolver時(shí),調(diào)用的為PluginContext的getContentResolver说铃。
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
返回的是一個(gè)PluginContentResolver對(duì)象访惜,當(dāng)我們調(diào)用query方法時(shí)敞斋,會(huì)輾轉(zhuǎn)調(diào)用到
ContentResolver.acquireUnstableProvider方法。該方法被PluginContentResolver中復(fù)寫:
@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() 返回的其實(shí)是RemoteContentProvider的代理對(duì)象IContentProviderProxy。
VirtualApk hook了宿主的RemoteContentProvider,利用動(dòng)態(tài)代理接管了RemoteContentProvider的操作衙解。
protected void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
//(1)提前請(qǐng)求一些RemoteContentProvider 創(chuàng)建RemoteContentProvider的實(shí)例
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)利用動(dòng)態(tài)代理 接管RemoteContentProvider的操作
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
- (1)提前請(qǐng)求一些RemoteContentProvider 創(chuàng)建RemoteContentProvider的實(shí)例
- (2)利用動(dòng)態(tài)代理 接管RemoteContentProvider的操作
有了IContentProviderProxy對(duì)象 就可以攔截query阳柔、insert、update蚓峦、delete操作舌剂,把用戶調(diào)用的uri,替換為占坑provider(RemouteContentPrpvider)的uri暑椰,再把原本的uri作為參數(shù)拼接在占坑provider的uri后面霍转。
RemouteContentProvider中收到請(qǐng)求后,
取出原本的uri,拿到auth一汽,在通過加載plugin得到providerInfo避消,反射生成provider對(duì)象,在調(diào)用其attachInfo方法召夹,即完成了ContentProvider的代理分發(fā)岩喷。
3.4、BroadCastReciever的注冊(cè)
BroadcastReceiver的處理比較簡(jiǎn)單,只需將插件中靜態(tài)注冊(cè)的BroadCastReceiver轉(zhuǎn)化成動(dòng)態(tài)注冊(cè)即可监憎。
// 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纱意、小結(jié):
- Activity:在宿主apk中提前占幾個(gè)坑,然后通過“欺上瞞下”(這個(gè)詞好像是360之前的ppt中提到)的方式鲸阔,啟動(dòng)插件apk的Activity偷霉;因?yàn)橐С植煌膌aunchMode以及一些特殊的屬性,需要占多個(gè)坑褐筛。
- Service:通過代理Service的方式去分發(fā)类少;主進(jìn)程和其他進(jìn)程,VirtualAPK使用了兩個(gè)代理Service渔扎。
- BroadcastReceiver:靜態(tài)轉(zhuǎn)動(dòng)態(tài)瞒滴。
- ContentProvider:通過一個(gè)代理Provider進(jìn)行分發(fā)。
四赞警、插件中的資源如何被加載
4.1妓忍、首先明確什么地方會(huì)用到Resouces
Resources是context的行為能力,所有Context的子類 才有可能取到Resources愧旦,即 Application世剖、Activity、Service笤虫。
另外 ContentProvider 會(huì)在attach()方法時(shí) 傳遞進(jìn)去一個(gè)Context對(duì)象旁瘫。
所以天生持有Resource(或者Context)的類 有四個(gè):Application祖凫、Activity、Service酬凳、ContentProvider
4.2 Application惠况、Activity、Service宁仔、ContentProvider中Resources的替換
PluginContext中保存了插件對(duì)應(yīng)的Resource
- Application 是如何替換Resouces的
創(chuàng)建Application對(duì)象時(shí),傳遞的是PluginContext - Activity 中 是如何替換Resouces資源的
VAInstrumentation中 創(chuàng)建Activity 之后, 立即為Activity設(shè)置了mResources(PluginContext.Resouces) - Service 中是如何替換資源的
Service.attach() 傳遞的是PluginContext - ContentProvider 是如何替換Resources資源的
contentProvider.attachInfo() 傳遞的是PluginContenxt
五稠屠、 插件的ClassLoader是如何生效的?
5.1、四大組件創(chuàng)建時(shí)ClassLoader的選擇
- 創(chuàng)建Application時(shí) 用的是Plugin.classLoader()
- 創(chuàng)建Activity時(shí) classLoader 用的是Plugin.getClassLoader()
- 創(chuàng)建Service時(shí) 用的Plugin.ClassLoader()
- 創(chuàng)建BroadCastReceiver時(shí), 用的是Plugin.getClassLoader()
- 創(chuàng)建ContentProvider時(shí), 用的是翎苫?权埠?? 沒有用插件的ClassLoader?
5.2煎谍、四大組件之外的類如何加載
對(duì)于宿主apk,插件是一個(gè)相對(duì)獨(dú)立的整體,宿主調(diào)用插件攘蔽,只能通過四大組件來調(diào)用(啟動(dòng)插件activity、service呐粘、broadcast满俗、contentProvider)。
我們知道作岖,通常情況下
在A類中加載B類漫雷,那么加載B類用到的ClassLoadert通常情況下就是A的classLoader
由此得出,我們只需要啟動(dòng)插件Activity時(shí) 選用插件的ClassLoader鳍咱,那么Activity內(nèi)用到的類降盹,默認(rèn)都是插件ClassLoader來加載的。
六谤辜、其他
6.1蓄坏、插件apk 是如何解析的?
利用系統(tǒng)自帶的PackageParser.parsePackage(),來解析一個(gè)APK
6.2丑念、坑位中的Activity是如何匹配的涡戳。
Manifest中預(yù)埋了 各種模式的Activity,分為ABCD四種:
- A 代表 stand模式的Activity, 2個(gè)
- B 代表 singleTop模式的Activity 脯倚,8個(gè)
- C 代表 singleTask模式的Activity渔彰,8個(gè)
- D 代表 singleInstance模式的Activity,8個(gè)
StubActivityInfo.getStubActivity() 根據(jù)launchMode 來選擇未使用的預(yù)留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兩個(gè)文件
2、backupHostR 備份R.txt文件 生成Host_R.txt
3尊残、backupProguardMapping 備份混淆文件炒瘸,生成mapping.txt
生成四個(gè)文件 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 根據(jù)宿主生成的 version.txt拐邪、Host_R.txt、mapping.txt 對(duì)插件的依賴隘截、資源進(jìn)行了去重操作扎阶。重復(fù)的依賴或資源以宿主依賴和資源為主,去除插件的對(duì)應(yīng)項(xiàng)技俐。