安卓插件化学习 - 类的加载

  • 引言
  • 一、类的加载
    • 1. 原理
    • 2. 代码
      • 2.1 宿主apk代码
        • 2.1.1 插件管理器
        • 2.1.2 配置文件
        • 2.1.3 插件初始化
        • 2.1.4 调用插件方法
      • 2.2 插件apk代码
  • 二、参考
  • 三、总结

引言

本次博客主要学习插件化知识,插件化多用于热修复,app主从功能分离等。通过学习ClassLoader的基本知识掌握类的加载。

一、类的加载

1. 原理


类加载器之间的继承关系如上图所示,一般宿主apk通过PathClassLoader进行加载,插件apk通过DexClassLoader进行加载。宿主apk启动时,类加载顺序图如下。

从宿主的类加载顺序看,最终调用到了宿主dexElements,那么我们实现插件化的方法是用DexClassLoader加载插件apk后,利用反射获取插件的dexElements,然后拷贝到宿主的dexElements。这样宿主apk就有了插件apk的dexElements信息,也就是能在宿主apk内利用反射调用到插件的类、方法等。方案如下。

这里注意下:
1)实例化DexClassLoader加载插件宿主类加载没有先后顺序之分,两者的目的在于改变dexElements的信息。
2)而在宿主调用插件化的类、方法之前,就必须要把插件的dexElements合并到宿主的dexElements里。
3)源码中,类PathClassLoader和类DexClassLoader中的方法和变量都能在其父类BaseDexClassLoader中找到,子类的构造函数提供参数输入。

2. 代码

2.1 宿主apk代码

2.1.1 插件管理器

创建一个空白activity的工程,用一个单例加载插件apk,此段代码为核心代码。

public class PluginManager {private static final String TAG = "PluginManager";private static PluginManager instance;private Context context;private PluginManager(Context context) {this.context = context;}public static PluginManager getInstance(Context context) {if (instance == null) {instance = new PluginManager(context);}return instance;}public void init() {try {loadApk();} catch (Exception e) {e.printStackTrace();}}private void loadApk() throws Exception {//加载插件的apk路径String pluginApkPath = context.getExternalFilesDir(null).getAbsolutePath() + File.separator + "pluginapp-debug.apk";Log.i(TAG, "pluginApkPath:" + pluginApkPath);// 缓存路径String cachePath = context.getDir("cache_plugin", Context.MODE_PRIVATE).getAbsolutePath();Log.i(TAG, "cachePath:" + cachePath);// 加载插件的类加载器DexClassLoader dexClassLoader = new DexClassLoader(pluginApkPath, cachePath, null, context.getClassLoader());// 通过插件的类加载器反射找到baseDexClassLoader,这里也可以通过宿主的pathClassLoader去反射该类Class<?> baseDexClassLoader = dexClassLoader.getClass().getSuperclass();Field pathListField = baseDexClassLoader.getDeclaredField("pathList");pathListField.setAccessible(true);// 1. 获取plugin的dexElementsObject pluginPathListObj = pathListField.get(dexClassLoader);// 通过插件的对象变量pathList反射找到类DexPathList的元素dexElements,这里也可以通过宿主的对象变量来获取Class<?> pathListClass = pluginPathListObj.getClass();Field dexElementsField = pathListClass.getDeclaredField("dexElements");dexElementsField.setAccessible(true);Object pluginDexElements = dexElementsField.get(pluginPathListObj);// 2. 获取host的dexElementsClassLoader pathClassLoader = context.getClassLoader();Object hostPathListObj = pathListField.get(pathClassLoader);Object hostDexElements = dexElementsField.get(hostPathListObj);// 3. 创建新的dexElements数组int hostDexElementsLength = Array.getLength(hostDexElements);int pluginDexElementsLength = Array.getLength(pluginDexElements);int newDexElementsLength = hostDexElementsLength + pluginDexElementsLength;Object newDexElements = Array.newInstance(hostDexElements.getClass().getComponentType(), newDexElementsLength);// 4. 通过拷贝合并两个dexElements数组至newDexElementsSystem.arraycopy(hostDexElements, 0, newDexElements, 0, hostDexElementsLength);System.arraycopy(pluginDexElements, 0, newDexElements, hostDexElementsLength, pluginDexElementsLength);// 把宿主的dexElements设置为合并后的dexElementsdexElementsField.set(hostPathListObj, newDexElements);}
}

调用初始化时,会打印日志,把路径给打印出来,如下

pluginApkPath:/storage/emulated/0/Android/data/com.example.pluginarchitect/files/pluginapp-debug.apk
cachePath:/data/user/0/com.example.pluginarchitect/app_cache_plugin

也就是说插件apk编译出来后,我们把它推到该手机的pluginApkPath路径下即可。

2.1.2 配置文件

宿主apk的配置文件AndroidManifest.xml

<applicationandroid:name=".MyApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
</application><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2.1.3 插件初始化

写一个Application类,此类在apk启动时先于activity加载

public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();PluginManager.getInstance(this).init();}
}

2.1.4 调用插件方法

然后在主活动页面上调用下插件的方法。

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 调用下插件的方法try {Class<?> pluinClazz = Class.forName("com.example.pluginapp.MainActivity");pluinClazz.getMethod("doSomething").invoke(null);} catch (Exception e) {e.printStackTrace();}}
}

2.2 插件apk代码

public class MainActivity extends AppCompatActivity {private static final String TAG = "PluginMainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public static void doSomething() {Log.i(TAG, "doSomething");}

就随便写个方法给测试一下即可。打开宿主app时候,日志有打印doSomething则表示调用到了该方法。

二、参考

  1. b站动脑学院宁传奇老师
  2. 安卓源码参考

三、总结

你学会了吗?

安卓插件化学习 - 类的加载相关推荐

  1. Android插件化开发之动态加载三个关键问题详解

    本文摘选自任玉刚著<Android开发艺术探索>,介绍了Android插件化技术的原理和三个关键问题,并给出了作者自己发起的开源插件化框架. 动态加载技术(也叫插件化技术)在技术驱动型的公 ...

  2. 插件化中Activity的加载

    插件化中Activity的加载 前面一系列的文章中我们介绍了Android系统资源加载流程,最后引出插件化中资源加载的方法,完成了『资源动态加载』这一大块的介绍.本系列文章将重点介绍『代码动态加载』, ...

  3. Android插件化开发之动态加载本地皮肤包进行换肤

    Android插件化开发之动态加载本地皮肤包进行换肤 前言: 本文主要讲解如何用开源换肤框架 android-skin-loader-lib来实现加载本地皮肤包文件进行换肤,具体可自行参考框架原理进行 ...

  4. 深入java虚拟机学习 -- 类的加载机制(续)

    昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...

  5. Android插件化开发之动态加载的类型

    https://segmentfault.com/a/1190000005113493 基本信息 Author:kaedea GitHub:android-dynamical-loading 现在网络 ...

  6. Android插件化开发之动态加载技术简单易懂的介绍方式

    转载地方:https://segmentfault.com/a/1190000004062866 基本信息 Author:kaedea GitHub:android-dynamical-loading ...

  7. 【Android 插件化】基于插件化的恶意软件的加载策略分析 ( 自定义路径加载插件 | 系统路径加载插件 | 用户同意后加载插件 | 隐藏恶意插件 )

    文章目录 一.自定义路径加载插件 二.系统路径加载插件 三.用户同意后加载插件 四.隐藏恶意插件 一.自定义路径加载插件 插件化应用中 , 宿主应用 加载 插件 APK , 需要获取该插件 APK 文 ...

  8. Android插件化开发之动态加载基础之ClassLoader工作机制

    类加载器ClassLoader 早期使用过Eclipse等Java编写的软件的同学可能比较熟悉,Eclipse可以加载许多第三方的插件(或者叫扩展),这就是动态加载.这些插件大多是一些Jar包,而使用 ...

  9. Android插件化开发之动态加载技术系列索引

    动态加载介绍 在Android开发中采用动态加载技术,可以达到不安装新的APK就升级APP功能的目的,可以用来到达快速发版的目的,也可以用来修复一些紧急BUG. 现在使用得比较广泛的动态加载技术的核心 ...

最新文章

  1. DotNET(C#) Socket基本编程 (1)
  2. eval语法报错 ie10_js eval 语法错误 急急急
  3. maven整合@data注解_springboot整合spring Cache(redis)
  4. 拦截器,过滤器,监听器原理
  5. Python安装时import matplotlib.pyplot as plt报错
  6. 在windows Console 平台下面 用glut编写 opengl程序 注意
  7. .NET WPF教程(1)——基础
  8. 【fake_useragent】网络爬虫获取随机User-Agent
  9. 使用js正则表达式验证
  10. 计算机主机检测不到耳机,win10电脑检测不到耳机的原因及处理方法
  11. AI上推荐 之 MIND(动态路由与胶囊网络的奇光异彩)
  12. Parallels将Win10引入Apple Silicon,实测运行效果糟糕
  13. vim 方向键和backspace乱码
  14. 五子棋-单机游戏-微信小游戏项目开发入门
  15. 如何断开mongodb数据库连接_如何创建mongodb数据库连接
  16. 支持DoH的DNS服务器,使用 Docker 自建支持 DoH、DoT 的 DNS 服务器
  17. 使用 ChatterBot 做简单的机器人
  18. 解决win10安装VC++ 出现的启动错误
  19. 【磁屏蔽电感】里让工程师“头晕脑胀”的问题
  20. Google Earth Engine(GEE)——Landsat 全球土地调查 1975年数据集

热门文章

  1. 2021秋招CVTE面经
  2. 使用OpenCV库函数将图片合成视频
  3. 易飞ERP--电子表单派班中心异常Illegal EasyFlow Account !-resak001
  4. Week2:区分己烷的同分异构体
  5. Mysql Mac 免安装教程
  6. 查看Windows10系统版本的方法
  7. 基于马尔科夫链的关于CpG岛的模式识别分类学习器
  8. 问卷调查抽奖系统开发
  9. 淘宝商家如何在得物做推广?得物推广有效果吗?
  10. 首批国外专家blog志愿者翻译人员诞生了!