Android插件化——VirtualAPK接入與源碼分析

一、VirtualAPK的接入

1.一、宿主工程引入VirtualApk
  • 在項目Project的build.gradle中添加依賴
dependencies {
    classpath 'com.didi.virtualapk:gradle:0.9.8.6'
}
複製代碼
  • 在宿主app的build.gradle中引入VirtualApk的host插件
apply plugin: 'com.didi.virtualapk.host'
複製代碼
  • 在app中添加依賴
dependencies {
    implementation 'com.didi.virtualapk:core:0.9.8'
}
複製代碼
  • 在Application中完成PluginManager的初始化
public class VirtualApplication extends Application {
   @Override
   protected void attachBaseContext(Context base) {
      super.attachBaseContext(base);
      PluginManager.getInstance(base).init();
   }
}
複製代碼
1.二、配置插件Apk
  • 在插件project中配置
classpath 'com.didi.virtualapk:gradle:0.9.8.6'
複製代碼
  • 在插件app的build.gradle中引入plugin插件
apply plugin: 'com.didi.virtualapk.plugin'
複製代碼
  • 配置插件信息和版本
virtualApk{
    // 插件資源表中的packageId,須要確保不一樣插件有不一樣的packageId
    // 範圍 0x1f - 0x7f
    packageId = 0x6f
    // 宿主工程application模塊的路徑,插件的構建須要依賴這個路徑
    // targetHost能夠設置絕對路徑或相對路徑
    targetHost = '../../../VirtualAPkDemo/app'
    // 默認爲true,若是插件有引用宿主的類,那麼這個選項可使得插件和宿主保持混淆一致
    //這個標誌會在加載插件時起做用
    applyHostMapping = true
}
複製代碼
  • 設置簽名(Virtual僅支持Release,host項目和plugin項目簽名一致)
signingConfigs {
    release {
        storeFile file('/Users/wuliangliang/AndroidSubjectStudyProject/PluginProject/VirtualAPkDemo/keystore/keystore') storePassword '123456' keyAlias = 'key'
        keyPassword '123456'
    }
}
buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}
複製代碼
1.三、執行生成插件Plugin
  • 執行assemablePlugin 產生Plugin文件java

  • 將插件Plugin安裝到手機中android

adb push ./app/build/outputs/plugin/release/com.alex.kotlin.virtualplugin_20190729172001.apk  /sdcard/plugin_test.apk
複製代碼
  • 在插件Plugin項目中全部的四大組件的使用都和原生使用方法一致
  • 注意問題
  1. 要先構建一次宿主app,才能夠構建plugin,不然異常
  2. 插件佈局文件中要設置資源的ID,不然異常:Cannot get property 'id' on null object
  3. plugin 增長 gradle.properties 文件並配置android.useDexArchive=false,不然異常
1.四、在宿主程序中使用插件Plugin
  1. 在宿主App中加載插件apk
private void loadApk() {
   File apkFile = new File(Environment.getExternalStorageDirectory(), "Test.apk");
   if (apkFile.exists()) {
      try {
         PluginManager.getInstance(this).loadPlugin(apkFile);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
複製代碼

在插件下載或安裝到設備後,獲取插件的文件,調用PluginManager.loadPlugin()加載插件,PluginManager會完成全部的代碼解析和資源加載,詳細內容後面的源碼分析; 2. 執行界面跳轉至插件中緩存

final String pkg = "com.alex.kotlin.virtualplugin」; //插件Plugin的包名
Intent intent = new Intent();
intent.setClassName(pkg, "com.alex.kotlin.virtualplugin.MainPluginActivity」);  //目標Activity的全路徑
startActivity(intent);
複製代碼

二、Virtual APK 源碼分析

2.一、PluginManager初始化
  • 在Application中添加VirtualApk初始化
PluginManager.getInstance(base).init();
複製代碼
  • PluginManager.getInstance(base):建立PluginManager實例,單例對外提供
public static PluginManager getInstance(Context base) {
   if (sInstance == null) {
      synchronized (PluginManager.class) {
         if (sInstance == null) {
            sInstance = createInstance(base); // 調用createInstance()方法建立PluginManager,單例對外提供
         }
      }
   }
   return sInstance;
}
複製代碼
  1. createInstance(base):建立PluginManager對象
private static PluginManager createInstance(Context context) {
   try {
      //一、獲取metaData
      Bundle metaData = context.getPackageManager()
            .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA)
            .metaData;
      if (metaData == null) {
         return new PluginManager(context); //二、
      }
      String factoryClass = metaData.getString("VA_FACTORY」); //三、此處獲取的是什麼?
      if (factoryClass == null) {
         return new PluginManager(context);
      }
      //四、建立PluginManager
      PluginManager pluginManager = Reflector.on(factoryClass).method("create", Context.class).call(context);
      if (pluginManager != null) {
         return pluginManager;
      }
   } catch (Exception e) {
      Log.w(TAG, "Created the instance error!", e);
   }
   return new PluginManager(context);
}
複製代碼

createInstance()執行邏輯:cookie

  1. 從宿主Context中解析APk中的metaData
  2. 若是metaData爲null,則直接使用context建立PluginManager
  3. 從metaData中獲取factoryClass,並反射建立PluginManager,不然直接建立PluginManager
  • PluginManager的構造函數
protected PluginManager(Context context) {
   if (context instanceof Application) { // 一、
      this.mApplication = (Application) context;
      this.mContext = mApplication.getBaseContext();
   } else {
      final Context app = context.getApplicationContext();
      if (app == null) {
         this.mContext = context;
         this.mApplication = ActivityThread.currentApplication();
      } else {
         this.mApplication = (Application) app;
         this.mContext = mApplication.getBaseContext();
      }
   }
   mComponentsHandler = createComponentsHandler();     //二、
   hookCurrentProcess();     //三、
}
複製代碼

PluginManager()執行流程:app

  1. 根據context類型,分別進行獲取並賦值mApplication & mContext
  2. 建立ComponentsHandler對象,ComponentsHandler的做用是做爲工具類,用於處理Intent和Service服務,關於ComponentsHandler後面會再提到
  3. Hook Instrumentation和SystemServices(主要使用Hook技術
  • hookCurrentProcess()
protected void hookCurrentProcess() {
   hookInstrumentationAndHandler();
   hookSystemServices();
}
複製代碼
  • hookInstrumentationAndHandler():Hook Activity啓動中使用的Instrumentation和Handler的CallBack,關於Handler的Callback查看Android消息隊列
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; // 賦值 PluginManager的mInstrumentation
複製代碼

執行流程:ide

  1. 從ActivityThread中獲取的系統中的Instrumentation對象
  2. 建立代理的Instrumentation對象,內部保存原對象
  3. 反射設置代理的VAInstrumentation對象到ActivityThread中
  4. 一樣Hook替換 ActivityThread 中的類H(Handler)中的mCallback,攔截 handleMessage()的回調邏輯
  • hookSystemServices():Hook系統的IActivityManager服務
Singleton<IActivityManager> defaultSingleton;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//一、
   //8.0 以上獲取IActivityManagerSingleton,採用AIDL獲取AMS
   defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
} else {
   //8.0 以前獲取gDefault,使用代理執行AMS
   defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
}
IActivityManager origin = defaultSingleton.get(); //2
IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[]{IActivityManager.class},createActivityManagerProxy(origin));//3
Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); // 四、
if (defaultSingleton.get() == activityManagerProxy) {
   this.mActivityManager = activityManagerProxy;//五、
}
複製代碼

hook系統服務在Hook 技術 文章中已經接收過程了,這裏簡單介紹邏輯:函數

  1. 根據Android版本的處理方式不一樣 ,8.0 以上獲取IActivityManagerSingleton,採用AIDL獲取AMS,8.0 以前獲取gDefault,使用代理執行AMS
  2. 反射獲取系統中原類的IActivityManager的代理類;
  3. 動態代理IActivityManager接口,此處建立的是ActivityManagerProxy對象;
  4. 反射設置代理的IActivityManager實例到系統中;
  5. 獲取設置的代理的Proxy,保存在mActivityManager中;

由四大組件啓動過程源碼分析,Hook了Instrumentation對象就能夠完成對Activity的建立過程的修改,Hook了系統的IActivityManager能夠實現對AMS工做的攔截,而AMS對四大組件的整個工做過程相當重要,也就是說咱們已經掌控了四大組件;工具

2.二、加載Plugin插件
  • loadPlugin()
public void loadPlugin(File apk) throws Exception {
   //一、
   LoadedPlugin plugin = createLoadedPlugin(apk);
   //二、
   this.mPlugins.put(plugin.getPackageName(), plugin);
}
複製代碼

loadPlugin()中主要完成兩件事:源碼分析

  1. 根據傳入的apk,建立LoadedPlugin對象
  2. 在mPlugins中根據包名保存建立的對象,使用時直接根據插件的包名獲取
  • LoadedPlugin:在構造函數中完成了大量的數據操做,具體細節見註釋,下面逐步分析VirtualAPK是如何加載插件的
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
   //一、保存安裝包的APk路徑、pluginManager、context
   this.mPluginManager = pluginManager;
   this.mHostContext = context;
   this.mLocation = apk.getAbsolutePath();
   //二、解析Plugin的AndroidManifest.xml文件 
   this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
   this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
   //三、建立並實例化PackageInfo對象
   this.mPackageInfo = new PackageInfo();
   this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
   this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
   if (Build.VERSION.SDK_INT >= 28
         || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) {
      try {
         this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
      } catch (Throwable e) {
         PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
         this.mPackageInfo.signatures = info.signatures;
      }
   } else {
      this.mPackageInfo.signatures = this.mPackage.mSignatures;
   }
   //保存包名
   this.mPackageInfo.packageName = this.mPackage.packageName;
   this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
   this.mPackageInfo.versionName = this.mPackage.mVersionName;
   this.mPackageInfo.permissions = new PermissionInfo[0];
   //四、建立插件中本身的PackManager
   this.mPackageManager = createPluginPackageManager();
   //五、建立插件中本身的PluginContext
   this.mPluginContext = createPluginContext(null);
   this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);
   //獲取so文件路徑
   this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();
   //六、建立Resource和ClassLoader對象
   this.mResources = createResources(context, getPackageName(), apk);
   //七、建立ClassLoader對象
   this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
   tryToCopyNativeLib(apk);
   Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
   for (PackageParser.Activity activity : this.mPackage.activities) {
      activity.info.metaData = activity.metaData;
      activityInfos.put(activity.getComponentName(), activity.info);    // 八、緩存插件中解析的Activity
   }
   this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);
  
   Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
   for (PackageParser.Service service : this.mPackage.services) {
      serviceInfos.put(service.getComponentName(), service.info);  // 九、緩存插件中解析的服務Service
   }
   this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);
  
   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);  // 十、緩存插件中解析的ContentProvider
   }
   this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);
   // 十一、緩存插件中解析的廣播,對於靜態註冊的廣播改成動態註冊
   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.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
   invokeApplication();    // 十二、初始化Plugin的Application
}
複製代碼
2.2.一、建立插件中的PackManager
protected class PluginPackageManager extends PackageManager {
   protected PackageManager mHostPackageManager = mHostContext.getPackageManager(); //1
   @Override
   public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
      LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName); // 2
      if (null != plugin) {
         return plugin.mPackageInfo; // 3
      }
      return this.mHostPackageManager.getPackageInfo(packageName, flags); //4
   }
}
複製代碼

PluginPackageManager繼成PackageManager重些其中的方法,在調用方法時判斷獲取的是插件APK仍是宿主APK,並對應處理返回,具體以下:佈局

  1. PluginPackageManager中保存宿主的PackageManager對象
  2. 在查找信息時根據包名獲取緩存的LoadedPlugin對象
  3. 對於獲取插件中的信息,返回插件的PackageInfo對象
  4. 若是根據包名獲取的LoadedPlugin = null,返回宿主的PackageInfo對象
  • 建立插件中本身的PluginContext對象 this.mPluginContext = createPluginContext(null);
2.2.2 建立PluginContext對象
public PluginContext createPluginContext(Context context) {
   if (context == null) {
      return new PluginContext(this); //一、由前面知道傳入contetx爲null,執行此處傳遞LoadedPlugin對象
   }
   return new PluginContext(this, context);
}
複製代碼
class PluginContext extends ContextWrapper { // 一、繼承ContextWrapper,用於在Activity中使用替換原來的Context
 private final LoadedPlugin mPlugin;
    public PluginContext(LoadedPlugin plugin) {
        super(plugin.getPluginManager().getHostContext()); // 賦值mBase對象,此處使用HostContext
        this.mPlugin = plugin;  // 保存插件建立的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 Resources getResources() {
        return this.mPlugin.getResources();
    }
    @Override
    public void startActivity(Intent intent) {
        // 重寫startActivity處理Intent
        ComponentsHandler componentsHandler = mPlugin.getPluginManager().getComponentsHandler(); 
        componentsHandler.transformIntentToExplicitAsNeeded(intent);
        super.startActivity(intent);
    }
}
複製代碼

在Activity中一些資源和服務都是經過Context獲取的,而Context是ContextWrapper的子類,在PluginContext中一樣繼承ContextWrapper,重寫一系列的Context方法,在加載插件時使用PluginContext替代插件APK中的Context、控制插件中資源和一些類的獲取,例如代碼中的getResources()、getContentResolver()等;

  • createResources():建立Resource對象
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
   if (Constants.COMBINE_RESOURCES) {// 1
      return ResourcesManager.createResources(context, packageName, apk); // 2
   } else {
      Resources hostResources = context.getResources();
      AssetManager assetManager = createAssetManager(context, apk);
      return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());//3
   }
}
複製代碼

由Android系統知道,程序在啓動時將資源加載到程序對應的Resource中,此處就是收動將插件apk中的資源加載到resource中,執行流程:

  1. 根據COMBINE_RESOURCES判斷是否合併插件資源,這裏的COMBINE_RESOURCES就是在插件項目中配置的applyHostMapping,對於插件中須要使用宿主資源的須要合併;
  2. 調用ResourcesManager.createResources()建立合併資源的Resource
  3. 對插件建立單獨的AssetManager對象加載插件apk路徑,而後建立Resources實例
  • ResourcesManager.createResources():建立合併的Resource對象
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //一、處理7.0 以後 Resource的建立
      return createResourcesForN(hostContext, packageName, apk);
   }
   //二、處理7.0 以前,使用AssetManager添加資源
   Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
   ResourcesManager.hookResources(hostContext, resources);
   return resources;
}
複製代碼
  1. createResourcesForN():處理7.0 以後 Resource的建立
private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception {
   String newAssetPath = apk.getAbsolutePath(); // Plugin apk的路徑
   ApplicationInfo info = context.getApplicationInfo();
   String baseResDir = info.publicSourceDir; // host的文件路徑

   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 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();
   for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) {
      plugin.updateResources(newResources);
   }
   return newResources;
}
複製代碼

Resource的建立流程:

  1. 分別獲取Plugin apk和host apk中的資源路徑
  2. 獲取當前項目中的splitSourceDirs,splitSourceDirs是保存當前程序全部已經加載的資源路徑,按索引順序獲取多個拆分apk的完整路徑,並檢查是否已經包含插件的路徑,對於未添加的將apk路徑添加進去
  3. 反射給PackageInfo設置合併以後的splitSourceDirs,此時表示將插件資源路徑添加進去了
  4. 獲取ResourceImpl及其配置的映射
  5. 獲取添加插件資源路徑後的Resource對象,並更新每一個插件apk對應的LoadedPlugin中的Resources
  • . 處理Android 7.0以前的Resource建立
Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());//建立Resource對象
ResourcesManager.hookResources(hostContext, resources); // 反射更新設置宿主Context中的resource
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) { // Android 5.0 以前
      assetManager = AssetManager.class.newInstance();
      reflector.bind(assetManager);
      //二、
      final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);
   } else {
      assetManager = hostResources.getAssets(); //三、
      reflector.bind(assetManager);
   }
   final int cookie2 = reflector.call(apk); // 四、
   
   List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
   for (LoadedPlugin plugin : pluginList) { // 五、
      final int cookie3 = reflector.call(plugin.getLocation());
   }
   //六、適配不一樣的手機類型,建立新的Resource對象
   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 {
      newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
   }
   // 七、將全部LoadedPlugin同步到新的newResources
   for (LoadedPlugin plugin : pluginList) {
      plugin.updateResources(newResources);
   }
   return newResources;
}
複製代碼

實現原理:建立一個新的AssetManager對象,使用AssetManager的addAssetPath()依次將全部插件和宿主中的資源路徑添加到對象中,根據新的AssetManager對象建立Resource對象,具體細節:

  1. 反射獲取Assetmanager的addAssetPath方法
  2. 對於Android 5.0以前,將host中的資源路徑保存在新建立的AssetManager中 ,5.0之後直接從context中獲取AssetManager對象
  3. 將Plugin中的資源路徑添加到AssetManager對象
  4. 遍歷全部插件,將全部資源添加到同一個AssetManager對象
  5. 適配不一樣的手機類型,建立新的Resource對象
  6. 將全部LoadedPlugin同步到新的newResources
2.2.3 建立ClassLoader對象
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
   File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR); 
   String dexOutputPath = dexOutputDir.getAbsolutePath();//一、獲取原APP中的dex文件路徑
   //二、建立DexClassLoader對象,同時加載host已經存在的dex文件和插件apk中的文件
   DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
   if (Constants.COMBINE_CLASSLOADER) {
      DexUtil.insertDex(loader, parent, libsDir);//三、將全部的dex資源合併到宿主的ClassLoader中
   }
   return loader;
}
複製代碼

createClassLoader()中先獲取當前app和插件的dex文件路徑,而後建立DexClassLoader同時加載兩個文件中的全部資源,在調用DexUtil.insertDex()合併dex資源,關於ClassLoader不瞭解的點擊Android熱修復之路(一)——ClassLoader

  1. DexUtil.insertDex():合併宿主和插件的dex、so庫中的類文件,具體細節件註釋和上面ClassLoader鏈接
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
    Object baseDexElements = getDexElements(getPathList(baseClassLoader)); // 一、獲取傳入的host默認的ClassLoader中加載的Elements集合
    Object newDexElements = getDexElements(getPathList(dexClassLoader)); //二、獲取dexClassLoader中加載的Elements(host & plugin)
    Object allDexElements = combineArray(baseDexElements, newDexElements); // 三、合併Elements
    Object pathList = getPathList(baseClassLoader);
    Reflector.with(pathList).field("dexElements").set(allDexElements); // 四、反射設置合併後的Elements集合
    insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);
}
複製代碼
2.2.4 解析插件apk
  • 解析PackageInfo信息
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
   this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
   //三、建立並實例化PackageInfo對象
   this.mPackageInfo = new PackageInfo();
複製代碼
  • 緩存插件中的Activity、Service、Provider
  • 緩存並註冊插件中的靜態廣播
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); 
      }
   }
複製代碼
2.2.5 初始化Plugin的Application
public void invokeApplication() throws Exception {
   final Exception[] temp = new Exception[1];
   RunUtil.runOnUiThread(new Runnable() {
      @Override
      public void run() {
         try {
            mApplication = makeApplication(false, mPluginManager.getInstrumentation()); //
         } catch (Exception e) {
            temp[0] = e;
         }
      }
   }, true);
}
複製代碼
  1. makeApplication():建立Application對象
protected Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) throws Exception {
   String appClass = this.mPackage.applicationInfo.className;
   if (forceDefaultAppClass || null == appClass) {
      appClass = "android.app.Application」; //設置執行的類
   }
   //
   this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
   mApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksProxy());
   //調用Application的onCreate(),此處執行的是插件APK中的Application
   instrumentation.callApplicationOnCreate(this.mApplication);
   return this.mApplication;
}

複製代碼

在makeApplication()內部調用instrumentation.newApplication()建立Application對象和執行Application的onCreate(),關於instrumentation中如何建立的參考(四大組件的啓動過程)

三、Activity插件化加載

  1. 實現原理:根據四大組件之Activity的工做流程知道,Activity啓動過程當中調用Instrumentation中的方法,而VirtualApk中使用VAInstrumentation代理了Instrumentation對象,VAInstrumentation在啓動插件Activity過程當中,首先替換爲空的佔位Activity騙過系統的檢查,而後在建立Activity實例時建立原來的Activity,從而達到啓動插件的效果
3.一、 VAInstrumentation

Activity在啓動過程當中調用Instrumentation的execStartActivity(),在VAInstrumentation中重寫此方法攔截啓動Activity過程,在此方法中修改處理Intent

@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); //二、調用原來邏輯繼續執行
}
複製代碼
  • . injectIntent(intent):更改原請求的Intent對象
protected void injectIntent(Intent intent) {
    //此處調用的getComponentsHandler()就是拿到前面建立的ComponentsHandler對象
    mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
    if (intent.getComponent() != null) {
        this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
    }
}
複製代碼
  • . transformIntentToExplicitAsNeeded():遍歷插件中解析緩存的Activity集合,查詢此Intent啓動哪一個插件的Activity,並獲取插件中的ResolveInfo信息
public Intent transformIntentToExplicitAsNeeded(Intent intent) {
   ComponentName component = intent.getComponent();
   if (component == null || component.getPackageName().equals(mContext.getPackageName())) {
      ResolveInfo info = mPluginManager.resolveActivity(intent); // 1
      if (info != null && info.activityInfo != null) {
         component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); //2
         intent.setComponent(component);
      }
   }
   return intent;
}
複製代碼
  1. 調用mPluginManager.resolveActivity()遍歷查找處理Intent的插件,從而獲取插件中對應Activity的信息
  2. 使用查詢到的Activity信息,建立ComponentName設置啓動的包名和Activity的類名,並設置到Intent中
  • . markIntentIfNeeded():匹配VirtualApk中設置的佔位Activity
public void markIntentIfNeeded(Intent intent) {
   String targetPackageName = intent.getComponent().getPackageName(); //一、 名
   String targetClassName = intent.getComponent().getClassName();
   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); //三、
   }
}
複製代碼

在markIntentIfNeeded()中:

  1. 獲取Intent中設置啓動Activity的包名和類
  2. 在Intent中保存真實啓動Activity的包名和類名
  3. 調用dispatchStubActivity()查找匹配的佔位Activity
  • dispatchStubActivity()
private void dispatchStubActivity(Intent intent) {
   ComponentName component = intent.getComponent();
   String targetClassName = intent.getComponent().getClassName();
   LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent); // 一、
   ActivityInfo info = loadedPlugin.getActivityInfo(component); //二、
  
   int launchMode = info.launchMode; // 三、
   Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
   themeObj.applyStyle(info.theme, true);
   //四、
   String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
   intent.setClassName(mContext, stubActivity); //五、
}
複製代碼

在VirtualAPk的註冊清單中提早註冊好了不一樣條件的佔位Activity,dispatchStubActivity()中主要從VirtualAPk中選擇合適的佔位Activity:

  1. 獲取啓動插件包對應的LoadedPlugin對象
  2. 獲取對應Activity的ActivityInfo信息
  3. 獲取Activity的啓動模式和主題
  4. 根據啓動模式和主題查找佔位Activity對象
  5. 在Intent中修改目標Activity爲佔位Activity
3.二、 建立真正Activity的實例
  • 在VAInstrumentation中重寫newActivity()方法
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    try {
        cl.loadClass(className); // 一、ClassLoader加載類名
    } catch (ClassNotFoundException e) { // 二、拋出ClassNotFoundException,表示啓動插件Activity
        ComponentName component = PluginUtil.getComponent(intent);
        String targetClassName = component.getClassName();        //三、

        LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
        
        // 四、建立Plugin的Activity真正的實例
        Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
        activity.setIntent(intent);
     
        //五、
        Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
        return newActivity(activity);
    }
    return newActivity(mBase.newActivity(cl, className, intent)); // 對於ClassLoader中查找到的直接調用原來方法初始化Activity
}
複製代碼

在Activity的啓動過程當中會回調newActivity()建立對象,因此在VAInstrumentation重寫此方法:

  1. 調用cl.loadClass(className)查找啓動的Activity,若是拋出異常即表示啓動的是插件Activity
  2. 從啓動意圖Intent中獲取目標Activity的類名,並從PluginManager中獲取插件對應的LoadedPlugin對象
  3. 利用Plugin中建立的ClassLoader建立出Activity的實例
  4. 替換插件Activity的mResources對象爲Plugin中建立的Resource,從而改變Context資源和類的獲取

四、Service 插件化加載

  1. 插件原理:經過Hook系統服務代理IActivityManager,從而攔截到啓動和中止服務、解除或綁定服務等操做,攔截操做後啓動插件中設置的代理Service,經過本地代理服務避免了插件服務的清單文件註冊檢查,在啓動的Service中代理分發插件Service,從而實現調用插件Service中的方法;
  2. 關於Service的啓動流程參見L五、四大組件的啓動過程
  • ActivityManagerProxy:IActivityManager的代理類
public ActivityManagerProxy(PluginManager pluginManager, IActivityManager activityManager) {
   this.mPluginManager = pluginManager;
   this.mActivityManager = activityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   if ("startService".equals(method.getName())) {
      try {
         return startService(proxy, method, args); // 處理啓動服務
      }
   } else if ("stopService".equals(method.getName())) {
      try {
         return stopService(proxy, method, args); // 處理中止服務
      }
   } else if ("stopServiceToken".equals(method.getName())) {
      try {
         return stopServiceToken(proxy, method, args); 
      } 
   } else if ("bindService".equals(method.getName())) {
      try {
         return bindService(proxy, method, args); // 處理綁定服務
      }
   } else if ("unbindService".equals(method.getName())) {
      try {
         return unbindService(proxy, method, args); // 處理解綁服務
      }
   } 
   try {
      return method.invoke(this.mActivityManager, args);
   } 
}
複製代碼

由前面Pluginmanager初始化過程知道VirtualAPk Hook了系統的AMS,Hook中傳遞的Binder對象就是ActivityManagerProxy對象,因此AMS對Service的任何操做都會通過此處的invoke(),咱們就能夠在invoke()方法中干涉啓動過程;

  • 啓動服務
  1. 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);//二、查找處理Intent,務
   if (null == resolveInfo || null == resolveInfo.serviceInfo) {
      return method.invoke(this.mActivityManager, args); //三、
   }
   return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);//四、處理
}
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
   Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); //五、構造代理服務的Intent
   return mPluginManager.getHostContext().startService(wrapperIntent); //六、
}
複製代碼

在ActivityManagerProxy的invoke()中攔截到startService()後調用startService(),在startService()中執行一下邏輯:

  1. 獲取Intent傳入的參數,並根據Intent判斷是否爲插件中服
  2. 對於host中的Service,直接調用原方法完成啓動
  3. 插件中的Service調用startDelegateServiceForTarget()方法,最終調用wrapperTargetIntent()修改啓動的Intent
  4. 啓動代理服務,進入LocalService中 -. wrapperTargetIntent():構造代理服務的Intent
protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
   target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
   String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();//一、
 
   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);
   return intent;
}
複製代碼

構造代理服務Intent的流程以下:

  1. 獲取插件apk路徑
  2. 判斷要啓動的插件服務是本地服務仍是遠程服務,並選擇LocalService或RemoteService
  3. 將Intent的Class設置代理服務,並保存目標插件Service信息
  • LocalService:服務啓動後代理分發調用插件服務的方法
public int onStartCommand(Intent intent, int flags, int startId) {
    Intent target = intent.getParcelableExtra(EXTRA_TARGET);    //一、
    ComponentName component = target.getComponent();
    LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
    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()傳入參數
            service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
            break;
        }
    }
    return START_STICKY;
}
複製代碼

LocalService啓動後,程序執行到onStartCommand()中,在onStartCommand()會建立插件Service的實例並完成服務的分發:

  1. 獲取真實Service對應的ComponentName 和插件的LoadedPlugin
  2. 先從緩存中是獲取Service對象
  3. 緩存中沒找到的,使用插件的ClassLoader建立插件Service的對象
  4. 反射獲取插件的attach()方法,而後執行插件服務的attach()和onCreate()
  5. 緩存Service 對象
  • 中止服務
  1. 攔截stopService()後設置代理服務的Intent,中止LocalService
  2. 在LocalService中代理分發中止對應的服務
case EXTRA_COMMAND_STOP_SERVICE: {
    //一、獲取Service並移除當前緩存
    Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
    if (null != service) {
        try {
            service.onDestroy(); //二、調用onDestroy()
        }
    } 
    break;
}
複製代碼
  • 綁定服務
  1. bindService()
protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
   Intent target = (Intent) args[2];
   ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
   Bundle bundle = new Bundle();
   PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]); // 一、保存綁定時傳入的ServiceConnection對象
   startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);
   mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target); // 二、緩存IServiceConnection
   return 1;
}
複製代碼
  1. 設置代理服務的Intent
  2. 緩存綁定服務的IServiceConnection
public void remberIServiceConnection(IBinder iServiceConnection, Intent intent) {
   synchronized (this.mBoundServices) {
      mBoundServices.put(iServiceConnection, intent); // 
   }
}
複製代碼
  1. 在LocalService中處理綁定服務,回調onBind()和connected()方法
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();
            ......
    try {
        IBinder binder = service.onBind(target); // 一、回調Service的onBind()
        IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
        IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
        if (Build.VERSION.SDK_INT >= 26) {
            iServiceConnection.connected(component, binder, false);//二、獲取傳入的serviceConnection,回調connected()
        } else {
            Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
        }
    }
    break;
}
複製代碼

綁定服務總體過程和啓動相似,只是多了兩個步驟:

  1. 建立ServiceConnection後,在mBoundServices中保存Intent和IServiceConnection
  2. 在LocalService中回調Service的onBind()和 connected()
  • 解綁服務
  1. 從緩存的IServiceConnection中獲取啓動服務時的Intent
protected Object unbindService(Object proxy, Method method, Object[] args) throws Throwable {
   IBinder iServiceConnection = (IBinder) args[0];
   Intent target = mPluginManager.getComponentsHandler().forgetIServiceConnection(iServiceConnection);
   
   ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
   startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_UNBIND_SERVICE);
   return true;
}
public Intent forgetIServiceConnection(IBinder iServiceConnection) {
   synchronized (this.mBoundServices) {
      Intent intent = this.mBoundServices.remove(iServiceConnection);
      return intent;
   }
}
複製代碼
  1. 從綁定服務時保存的mBoundServices中獲取啓動的Intent
  2. 再LocalService中處理解綁服務
case EXTRA_COMMAND_UNBIND_SERVICE: {
    Service service = this.mPluginManager.getComponentsHandler().forgetService(component); // 獲取服務
    if (null != service) {
        try {
            service.onUnbind(target); // 解綁服務
            service.onDestroy();
        } 
    } 
    break;
}
複製代碼

五、ContentProvider插件化

  • ContentProvider使用
Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book」); // 查找的Uri
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg); // 一、獲取插件的LoadedPlugin
bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);  //二、替換Uri,用於啓動宿主代理的ContentProvider
//三、獲取ContentResolver對象,執行查詢
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
複製代碼
  • Host中註冊代理的ContentProvider
<provider
    android:exported="false"
    android:name="com.didi.virtualapk.delegate.RemoteContentProvider"
    android:authorities="${applicationId}.VirtualAPK.Provider"
    android:process=":daemon" />
複製代碼
  • PluginContentResolver.wrapperUri():替換Uri啓動宿主代理Provider
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
    Uri wrapperUri = Uri.parse(builder.toString());
    return wrapperUri;
}
public static String getAuthority(Context context) {
    return context.getPackageName() + ".VirtualAPK.Provider」; // 建立代理Provider啓動權限
}
public static String getUri(Context context) {
    return "content://" + getAuthority(context);
}
複製代碼

和代理Service大體類似,Provider的使用也分爲兩部分,處理啓動的Intent和啓動Provider的代理分發,Intent處理主要將請求的Uri轉換爲啓動代理Provider的Uri,同時保存插件的請求信息:

  1. 獲取原請求Uri的包名和Uri參數
  2. 獲取代理Provider的啓動Uri替換啓動插件的Uri,將插件啓動變成請求啓動host中代理的ContentProvider
  3. 保存請求插件的路徑、插件包名、插件的Uri參數
  4. 建立並返回新的Uri
  • getContentResolver().query()
@Override
public ContentResolver getContentResolver() {
    return new PluginContentResolver(getHostContext());
}
複製代碼

由前面的PluginContext知道,在插件的四大組件中使用PluginContext代替Context。對於插件此處調用的是PluginContext中的getContentResolver(),建立PluginContentResolver對象

  • PluginContentResolver
public class PluginContentResolver extends ContentResolverWrapper {
    private PluginManager mPluginManager;
    public PluginContentResolver(Context context) {
        super(context);
        mPluginManager = PluginManager.getInstance(context);
    }
    
    @Override
    protected IContentProvider acquireProvider(Context context, String auth) {
        //二、處理插件中的Provider
        if (mPluginManager.resolveContentProvider(auth, 0) != null) {
            return mPluginManager.getIContentProvider();
        }
        return super.acquireProvider(context, auth);
    }
}
複製代碼

PluginContentResolver繼承ContentResolverWrapper類,做爲插件中使用的ContentProvider,由Provider的工做過程知道(ContentProvider使用和工做過程詳解),系統在獲取Provider對象時會調用getIContentProvider()

  1. getIContentProvider():返回建立的Provider對象,此處使用宿主的代理的Provider
public synchronized IContentProvider getIContentProvider() {
   if (mIContentProvider == null) {
      hookIContentProviderAsNeeded();  //Hook ContentProvider 攔截請求
   }
   return mIContentProvider;
}
複製代碼
  1. hookIContentProviderAsNeeded():Hook宿主中代理的Provider
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 (auth.equals(RemoteContentProvider.getAuthority(mContext))) { //二、
            if (provider == null) {
               provider = val.getClass().getDeclaredField("mProvider");
               provider.setAccessible(true);
            }
            IContentProvider rawProvider = (IContentProvider) provider.get(val);
            //三、代理指定的Provider
            IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
            mIContentProvider = proxy;
            break;
         }
      }
   }
}
複製代碼

Hook ContentProvider的過程:

  1. 反射獲取ActivityThread中保存的ContentProvider集合
  2. 遍歷mProviderMap,查找Host中的代理Provider,此時咱們知道查詢的Uri已經替換爲啓動代理Provider的Uri
  3. 而後動態代理Host中的Provider
  • RemoteContentProvider:宿主中代理Provider
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    ContentProvider provider = getContentProvider(uri); // 一、獲取真正執行的Provider對象
    Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI)); //二、獲取啓動Plugin真正的Uri
    if (provider != null) {
        return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);//三、
    }
    return null;
}
複製代碼

在宿主代理Provider中:

  1. 首先根據請求的Uri獲取插件的Provider,
  2. 而後獲取啓動Plugin真正的Uri
  3. 執行provider中方法
  • . getContentProvider():建立插件中Provider對象,並調用Provider中方法
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))); // 五、
            } 
        }
        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); //十、
                    } 
                }
            }, true);
            return sCachedProviders.get(auth);
        }
    }
    return null;
}
複製代碼

前面的整個過程都是在操縱代理Provider,真正牽涉到插件的Provider就在此處,整個建立Provider和執行方法流程以下:

  1. 從請求的Uri中獲取原來請求插件的Uri
  2. 獲取請求插件Provider的權限
  3. 首先從緩存中獲取對應的Provider對象
  4. 從Uri中插件獲取包名,再根據包名獲取對應的LoadedPlugin
  5. 若是程序未加載過此插件,則執行加載插件
  6. 根據請求的權限,遍歷解析時緩存的provider集合查找 ProviderInfo 對象
  7. 從請求的Uri中獲取插件的包名,根據包名獲取建立的LoadedPlugin對象
  8. 使用ClassLoader建立Provider對象
  9. 調用Provider.attachInfo()完成context的綁定
  10. 緩存建立的Provider對象

到此整個VirtualApk的使用和源碼分析就到此結束了,從開始接觸到如今源碼分析整個過程拖了很久,經過整個流程的學習對以前的源碼知識也有了更好的鞏固,但願對想學習插件化的同窗有所幫助;

相關文章
相關標籤/搜索