Android插件化——VirtualAPK接入与源码分析

本文详细介绍了VirtualAPK的接入步骤,包括宿主工程的配置、插件Apk的构建和加载,以及在宿主程序中的使用。接着,深入探讨了VirtualAPK的源码,分析了PluginManager的初始化、插件加载过程,涉及PluginContext、ClassLoader、资源和Activity的加载。最后,讲解了Activity、Service、ContentProvider的插件化加载机制,揭示了VirtualAPK实现插件化的核心原理。
摘要由CSDN通过智能技术生成

1、VirtualAPK的接入

1.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.2、配置插件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.3、执行生成插件Plugin
  • 执行assemablePlugin 产生Plugin文件

  • 将插件Plugin安装到手机中

 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.4、在宿主程序中使用插件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);

2、Virtual APK 源码分析

2.1、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 {
   
      //1、获取metaData
      Bundle metaData = context.getPackageManager()
            .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA)
            .metaData;
      if (metaData == null) {
   
         return new PluginManager(context); //2、
      }
      String factoryClass = metaData.getString("VA_FACTORY”); //3、此处获取的是什么?
      if (factoryClass == null) {
   
         return new PluginManager(context);
      }
      //4、创建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()执行逻辑:

  1. 从宿主Context中解析APk中的metaData
  2. 如果metaData为null,则直接使用context创建PluginManager
  3. 从metaData中获取factoryClass,并反射创建PluginManager,否则直接创建PluginManager
  • PluginManager的构造函数
protected PluginManager(Context context) {
   
   if (context instanceof Application) {
    // 1、
      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();     //2、
   hookCurrentProcess();     //3、
}

PluginManager()执行流程:

  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(); //1、
final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); //2、
//3、
Reflector.with(activityThread).field("mInstrumentation").set(instrumentation); 
//4、
Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
Reflector.with(mainHandler).field("mCallback").set(instrumentation);
this.mInstrumentation = instrumentation; // 赋值 PluginManager的mInstrumentation

执行流程:

  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) {
   //1、
   //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); // 4、
if (defaultSingleton.get() == activityManagerProxy) {
   
   this.mActivityManager = activityManagerProxy;//5、
}

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.2、加载Plugin插件
  • loadPlugin()
public void loadPlugin(File apk) throws Exception {
   
   //1、
   LoadedPlugin plugin = createLoadedPlugin(apk);
   //2、
   this.mPlugins.put(plugin.getPackageName(), plugin);
}

loadPlugin()中主要完成两件事:

  1. 根据传入的apk,创建LoadedPlugin对象
  2. 在mPlugins中根据包名保存创建的对象,使用时直接根据插件的包名获取
  • LoadedPlugin:在构造函数中完成了大量的数据操作,具体细节见注释,下面逐步分析VirtualAPK是如何加载插件的
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
   
   //1、保存安装包的APk路径、pluginManager、context
   this.mPluginManager = pluginManager;
   this.mHostContext = context;
   this.mLocation = apk.getAbsolutePath();
   //2、解析Plugin的AndroidManifest.xml文件 
   this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
   this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
   //3、创建并实例化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];
   //4、创建插件中自己的PackManager
   this.mPackageManager = createPluginPackageManager();
   //5、创建插件中自己的PluginContext
   this.mPluginContext = createPluginContext(null);
   this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);
   //获取so文件路径
   this.mPackage.applicationInfo.nativeLibraryDir = this
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值