Android插件化——VirtualAPK接入与源码分析_splitsourcedirs-程序员宅基地

技术标签: Android源码分析和框架编写  插件化  Android高级进阶之旅  VirtualAPK  

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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Alexwll/article/details/99301652

智能推荐

新手如何使用腾讯云服务器部署Flask项目_flask云部署-程序员宅基地

文章浏览阅读541次,点赞9次,收藏5次。浅记录新手如何部署flask项目上线:配置服务器-上传项目文件-配置环境依赖-服务器开放端口-运行发布_flask云部署

【TypeScript入门】TypeScript入门篇——类_typescript 类-程序员宅基地

文章浏览阅读752次,点赞23次,收藏14次。TypeScript 是面向对象的 JavaScript。类描述了所创建的对象共同的属性和方法。_typescript 类

6个常用大数据分析工具集锦-程序员宅基地

文章浏览阅读4.4k次,点赞2次,收藏17次。大数据是一个含义广泛的术语,是指数据集,如此庞大而复杂的,他们需要专门设计的硬件和软件工具进行处理。该数据集通常是万亿或EB的大小。这些数据集收集自各种各样的来源:传感器,气候信息,公开的信息,如杂志,报纸,文章。大数据产生的其他例子包括购买交易记录,网络日志,病历,军事监控,视频和图像档案,及大型电子商务。在大数据和大数据分析,他们对企业的影响有一个兴趣高涨。大数据分析是研究大量的数据的过程中寻找模式,相关性和其他有用的信息,可以帮助企业更好地适应变化,并做出更明智的决策。一、HadoopHadoo

CSDN创作技巧_csdn正文是什么快捷键?-程序员宅基地

文章浏览阅读920次,点赞26次,收藏25次。​使用CSS样式:在csdn的样式文件中添加以下CSS代码来定义颜色区分汉字和英文:1.1.2使用JavaScript:在csdn的HTML文件中添加以下JavaScript代码来实现自动颜色区分汉字和英文:这段代码会在页面加载完毕后,遍历所有元素,将汉字和英文分别加上span标签,并加上对应的类名,从而实现自动颜色区分汉字和英文。使用jQuery:如果你已经在csdn上引入了jQuery库,可以使用下面的代码来实现自动颜色区分汉字和英文:$(document).ready(function() {_csdn正文是什么快捷键?

Linux命令(111)之groupadd-程序员宅基地

文章浏览阅读670次。linux命令之groupadd介绍_groupadd

javaee实验报告心得_java,web实验报告心得.doc-程序员宅基地

文章浏览阅读2.3k次。java,web实验报告心得java,web实验报告心得JavaWeb实验报告实验一 开发环境配置及Servlet程序设计一、实验目的1、了解并熟悉编程环境、编程工具,包括Tomcat、MyEclipse和JDK;2、学会配置环境变量;3、掌握在MyEclipse中编辑简单源程序的方法、创建包和servlet类的方法;4、掌握在Tomcat中手工创建可执行程序的方法;二、实验内容及要求本次实验内..._javaee网页和数据库实验总结

随便推点

推荐开源项目:Notion Zh-CN - 中文本地化版本-程序员宅基地

文章浏览阅读407次,点赞5次,收藏4次。推荐开源项目:Notion Zh-CN - 中文本地化版本项目地址:https://gitcode.com/Reamd7/notion-zh_CN项目简介Notion Zh-CN 是一个由开发者 Reamd7 主导的开源项目,它的目标是为流行的生产力工具 Notion 提供中文本地化的支持。Notion 是一款集文档管理、知识库、任务管理和团队协作于一体的平台,而 Notion Zh-CN ..._notion 开源吗

机器学习算法之SVM的多分类_svm多分类-程序员宅基地

文章浏览阅读1.7w次,点赞3次,收藏23次。一、SVM可以直接进行多分类吗 SVM本身是对付二分类问题的,所以在处理多分类的时候需要进行必要的改造。同样是二分类的情况,logistic回归可以直接拓展为softmax多分类。但是SVM如果直接在目标函数上进行修改的话,就是将多个分类面的参数求解合并到一个最优化问题上,显然难度太大,目前也没有任何实际操作的方法。二、SVM多分类间接实现1、1-V-rest:将某一类归为正类,其余全部是负类_svm多分类

CentOS7离线安装supervisor-程序员宅基地

文章浏览阅读485次,点赞4次,收藏6次。【解决办法】:没有setuptools的模块,说明python缺少这个模块,那我们只要安装这个模块即可解决此问题。【可能报错】:ImportError: No module named setuptools。2.安装supervisor。3.验证安装是否成功。_离线安装supervisor

[rails] 我的订餐系统 -- 小试ruby on rails _订盒饭代码-程序员宅基地

文章浏览阅读890次。前言 近期在java社区中一种新的脚本语言ruby,及用ruby开发的一个wab框架 rails也热闹了起来.引起了不少的java开发人员的关注.  本人平时还是很少接触脚本语言方面东东,看到相关的评论例如: "习惯约定优于配置" -- 那样就用象java那样麻烦且繁杂地配置N多XML "一站式面向用户的简单易用的框架" _订盒饭代码

matlab超限像素平滑法_2D-DIC | 二维数字图像相关法原理介绍 — 以开源算法Ncorr为例...-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏9次。原文链接 2D-DIC | 二维数字图像相关法原理介绍 — 以开源算法Ncorr为例​mp.weixin.qq.com欢迎各位朋友关注数字图像相关法DIC小站,本小站公众号旨在推广数字图像相关法的研究和应用。【引言】 数字图像相关法(DIC)是一种利用在物体表面喷涂随机散斑,通过在物体变形前后的散斑图像中精确匹配对应点,测量变形位移等数据的非接触式光学测量方法。相比其它传统的接..._a 117 line 2d digital image correlation code written in matlab

3-位图的使用场景_redis 存储字节流-程序员宅基地

文章浏览阅读625次。1、二进制安全redis只存储字节流,与外界交互,存取都是字节流,只要双方客户端有统一的编解码,数据就不会被破坏。redis拿的是字节流,编码是一个字符一个字节redis-cli --raw 连接redis服务,并触发编码器的格式化。如果不格式化,redis只会识别ASCII码的,超出ASCII码,则显示为16进制2、位图的使用场景2.1、场景一:统计一段时间内用户的登录天数如果用数据库实现创建表,用户每笔登录都产生一行记录,然后登录登录时间,还有其他数据也需要记录。MySQL数据_redis 存储字节流

推荐文章

热门文章

相关标签