插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。
创新互联建站坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站设计、成都做网站、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的宁远网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展;
今天我们就来讲下插件化
在 Android 系统中,应用是以 Apk 的形式存在的,应用都需要安装才能使用。但实际上 Android 系统安装应用的方式相当简单,其实就是把应用 Apk 拷贝到系统不同的目录下、然后把 so 解压出来而已;
常见的应用安装目录有:
Apk 的构成,一个常见的 Apk 会包含如下几个部分:
其实 Android 系统在打开应用之后,也只是开辟进程,然后使用 ClassLoader 加载 classes.dex 至进程中,执行对应的组件而已;
那大家可能会想一个问题,既然 Android 本身也是使用类似反射的形式加载代码执行,凭什么我们不能执行一个 Apk 中的代码呢?
这其实就是插件化的目的,让 Apk 中的代码(主要是指 Android 组件)能够免安装运行,这样能够带来很多收益,最显而易见的优势其实就是通过网络热更新、热修复;
ClassLoader调用loadClass方法加载类,代码如下:
- protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- //首先从已经加载的类中查找
- Class> clazz = findLoadedClass(className);
- if (clazz == null) {
- ClassNotFoundException suppressed = null;
- try {
- //如果没有加载过,先调用父加载器的loadClass
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- suppressed = e;
- }
- if (clazz == null) {
- try {
- //父加载器都没有加载,则尝试加载
- clazz = findClass(className);
- } catch (ClassNotFoundException e) {
- e.addSuppressed(suppressed);
- throw e;
- }
- }
- }
- return clazz;
- }
可以看出ClassLoader加载类时,先查看自身是否已经加载过该类,如果没有加载过会首先让父加载器去加载,如果父加载器无法加载该类时才会调用自身的findClass方法加载,该机制很大程度上避免了类的重复加载;
简单来说,插件化场景下,会存在同一进程中多个 ClassLoader 的场景:
我们称这个过程叫做 ClassLoader 注入;
完成注入后,所有来自宿主的类使用宿主的 ClassLoader 进行加载,所有来自插件 Apk 的类使用插件 ClassLoader 进行加载;
而由于 ClassLoader 的双亲委派机制,实际上系统类会不受 ClassLoader 的类隔离机制所影响,这样宿主 Apk 就可以在宿主进程中使用来自于插件的组件类了;
ClassLoader 注入后,就可以在宿主进程中使用插件 Apk 中的类,但是我们都知道 Android 组件都是由系统调用启动的,未安装的 Apk 中的组件,是未注册到 AMS 和 PMS 的,就好比你直接使用 startActivity 启动一个插件 Apk 中的组件,系统会告诉你无法找到;
我们的解决方案很简单,即运行时容器技术,简单来说就是在宿主 Apk 中预埋一些空的 Android 组件,以 Activity 为例,我预置一个 ContainerActivity extends Activity 在宿主中,并且在 AndroidManifest.xml 中注册它;
它要做的事情很简单,就是帮助我们作为插件 Activity 的容器,它从 Intent 接受几个参数,分别是插件的不同信息,如:
当完成加载后,ContainerActivity 要做两件事:
我们可以通过复写 ContainerActivity 的生命周期方法来完成第一步,而第二步我们需要定义一个 PluginActivity,然后在编写插件 Apk 中的 Activity 组件时,不再让其集成 android.app.Activity,而是集成自我们的 PluginActivity,后面再通过字节码替换来自动化完成这部操作,后面再说为什么,我们先看伪代码;
- public class ContainerActivity extends Activity {
- private PluginActivity pluginActivity;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- String pluginActivityName = getIntent().getString("pluginActivityName", "");
- pluginActivity = PluginLoader.loadActivity(pluginActivityName, this);
- if (pluginActivity == null) {
- super.onCreate(savedInstanceState);
- return;
- }
- pluginActivity.onCreate();
- }
- @Override
- protected void onResume() {
- if (pluginActivity == null) {
- super.onResume();
- return;
- }
- pluginActivity.onResume();
- }
- @Override
- protected void onPause() {
- if (pluginActivity == null) {
- super.onPause();
- return;
- }
- pluginActivity.onPause();
- }
- // ...
- }
- public class PluginActivity {
- private ContainerActivity containerActivity;
- public PluginActivity(ContainerActivity containerActivity) {
- this.containerActivity = containerActivity;
- }
- @Override
- public
T findViewById(int id) { - return containerActivity.findViewById(id);
- }
- // ...
- }
- // 插件 `Apk` 中真正写的组件
- public class TestActivity extends PluginActivity {
- // ......
- }
但大概原理就是这么简单,启动插件组件需要依赖容器,容器负责加载插件组件并且完成双向转发,转发来自系统的生命周期回调至插件组件,同时转发来自插件组件的系统调用至系统;
最后要说的是资源注入,其实这一点相当重要,Android 应用的开发其实崇尚的是逻辑与资源分离的理念,所有资源(layout、values 等)都会被打包到 Apk 中,然后生成一个对应的 R 类,其中包含对所有资源的引用 id;
资源的注入并不容易,好在 Android 系统给我们留了一条后路,最重要的是这两个接口:
我们要做的就是在上面 ContainerActivity#onCreate 中加载插件 Apk 的时候,用这两个方法创建出来一份插件资源实例。具体来说就是先用 PackageManager#getPackageArchiveInfo 拿到插件 Apk 的 PackageInfo,有了 PacakgeInfo 之后我们就可以自己组装一份 ApplicationInfo,然后通过 PackageManager#getResourcesForApplication 来创建资源实例,大概代码像这样:
- PackageManager packageManager = getPackageManager();
- PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(
- pluginApkPath,
- PackageManager.GET_ACTIVITIES
- | PackageManager.GET_META_DATA
- | PackageManager.GET_SERVICES
- | PackageManager.GET_PROVIDERS
- | PackageManager.GET_SIGNATURES
- );
- packageArchiveInfo.applicationInfo.sourceDir = pluginApkPath;
- packageArchiveInfo.applicationInfo.publicSourceDir = pluginApkPath;
- Resources injectResources = null;
- try {
- injectResources = packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- // ...
- }
拿到资源实例后,我们需要将宿主的资源和插件资源 Merge 一下,编写一个新的 Resources 类,用这样的方式完成自动代理:
- public class PluginResources extends Resources {
- private Resources hostResources;
- private Resources injectResources;
- public PluginResources(Resources hostResources, Resources injectResources) {
- super(injectResources.getAssets(), injectResources.getDisplayMetrics(), injectResources.getConfiguration());
- this.hostResources = hostResources;
- this.injectResources = injectResources;
- }
- @Override
- public String getString(int id, Object... formatArgs) throws NotFoundException {
- try {
- return injectResources.getString(id, formatArgs);
- } catch (NotFoundException e) {
- return hostResources.getString(id, formatArgs);
- }
- }
- // ...
- }
然后我们在 ContainerActivity 完成插件组件加载后,创建一份 Merge 资源,再复写 ContainerActivity#getResources,将获取到的资源替换掉:
- public class ContainerActivity extends Activity {
- private Resources pluginResources;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // ...
- pluginResources = new PluginResources(super.getResources(), PluginLoader.getResources(pluginApkPath));
- // ...
- }
- @Override
- public Resources getResources() {
- if (pluginActivity == null) {
- return super.getResources();
- }
- return pluginResources;
- }
- }
这样就完成了资源的注入
合并式的资源处理方式,会引入资源冲突,原因在于不同插件中的资源id可能相同,所以解决方法就是使得不同的插件资源拥有不同的资源id;
资源id是由8位16进制数表示,表示为0xPPTTNNNN。PP段用来区分包空间,默认只区分了应用资源和系统资源,TT段为资源类型,NNNN段在同一个APK中从0000递增;
市面上的插件化框架实际很多,如 Tecent 的 Shadow、Didi 的 VirtualApk、360 的 RePlugin。他们各有各的长处,不过大体上差不多;
他们大体原理其实都差不多,运行时会有一个宿主 Apk 在进程中跑,宿舍 Apk 是真正被安装的应用,宿主 Apk 可以加载插件 Apk 中的组件和代码运行,插件 Apk 可以任意热更新;
文章题目:深入理解Android插件化技术原理
网页网址:http://www.mswzjz.cn/qtweb/news11/539661.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能