Android插件化原理,从以下三个问题切入:

  1. 什么是插件化
  2. 如何实现插件类的加载
  3. 如何实现插件资源的加载

什么是插件化

插件化技术最初是源于免安装运行APK的想法,这个免安装的APK就可以理解为插件,而支持插件的app,则称之为宿主;一方面减小了安装包的大小,另一方面可以实现 app 功能的动态扩展

  • 插件化解决的问题

  1. APP的功能越来越多,体积越来越大
  2. 模块之间的耦合度高,协同开发的沟通成本越来越大
  3. APP功能变多之后,导致方法数可能会超过65535,APP占用内存过大
  4. 应用之间的相互调用
  • 插件化与组件化的区别

  • 组件化开发就是将一个APP拆分成多个模块,每一个模块都是一个组件,在开发的过程中可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并为一个APK,这就是组件化开发。
  • 插件化和组件化略有不同,插件化是将整个APP拆分成多个模块,这些模块包括一个宿主和多个插件,每个模块都是一个单独的APK,最终打包的时候,宿主APK和插件APK分开打包。(最终online部署发布时,可以根据客户需求选择只发布宿主APK,或者是发布宿主APK和其中需要用到的插件APK)
  • 各大插件化对比

特性

DynamicAPK

dynamic- load-apk

Small

DroidPlugin

RePlugin

VirtualAPK

作者

携程

任玉刚

wequick

360

360

滴滴

支持四大组件

只支持Activity

只支持Activity

只支持

Activity

全支持

全支持

全支持

组件无需在宿主manifest 中预注册

×

插件可以依赖宿主

×

支持PendingIntent

×

×

×

Android特性支持

大部分

大部分

大部分

几乎全部

几乎全部

几乎全部

兼容性适配

一般

一般

中等

插件构建

部署aapt

Gradle插件

Gradle插件

Gradle插件

在选择开源框架的时候,需要根据自身的需求来,如果加载的插件不需要和宿主有任何耦合,也无须和宿主进
行通信,比如加载第三方 App,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk

插件化的实现

如何去实现插件化呢?插件是免安装的,那么要思考几个问题:

  1. 插件也是APK,APK里面有资源和类,那么需要考虑的问题无非就是两个

    1. 如何加载插件资源
    2. 如何加载插件类
  2. 要加载Android类,as we all konw,四大组件是需要在AndroidManifest.xml中注册的,要在宿主中调用插件的四大组件,显然插件APK中的用到的四大组件相关类,是没有在宿主的AndroidMainfest.xml中注册过的,那么怎么通过宿主去调用插件的四大组件
  • 类加载

Java中会通过javac命令将.java文件编译生成.class文件,jvm会加载class文件,解析之后初始化,然后就可以运行了;Android中会将代码编译成一个APK,解压APK可以看到一个或者多个.dex文件,dex文件是DVM的可执行文件,dex就是把class合并优化后生成的(odex);下面我们来学习DVM如何加载dex文件。

  • ClassLoader

Android中是通过ClassLoader加载dex文件的,ClassLoader是一个抽象类,实现主要分为两种类型:系统类加载器和自定义加载器,其实按照我的理解,系统类加载器主要就是BootClassLoader,DexClassLoader和PathClassLoader

  1. BootClassLoader,用于加载Android Framework层class文件。
  2. PathClassLoader,用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
  3. DexClassLoader,用于加载指定的dex,以及jar、zip、apk中的classes.dex

类继承关系如下图:

我们先来看下 PathClassLoader 和 DexClassLoader。

注意:有些帖子可能会有如下说法:

DexClassLoader:能够加载未安装的jar、apk、dex等;而PathClassLoader只能够加载系统中已经安装的apk;这个说法是有问题的,或者说针对Android8.0之后的系统,这个说法是错误的

在8.0(API 26)之前,它们二者的唯一区别是第二个参数 optimizedDirectory,这个参数的意思是生成的 odex(优化的dex)存放的路径。在8.0(API 26)及之后,二者就完全一样了。

看Android源码

// API26之后和API26之前的PathClassLoader的源码是相同的
// /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {// optimizedDirectory 直接为 nullpublic PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}// optimizedDirectory 直接为 nullpublic PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}// API 小于等于26 /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {//从API26开始,super的构造方法,也就是BaseDexClassLoader的构造方法有变动public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);}
}
// API 大于26 /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {/*** @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.*/public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {//可以看到第二个参数optimizedDirectory直接为null,已经和PathClassLoader的构造方法一样了super(dexPath, null, librarySearchPath, parent);}
}// API 26/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(parent);// DexPathList 的第四个参数是 optimizedDirectory,可以看到这儿为 nullthis.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
}// API 25/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}

上述源码可以看出,PathClassLoader和DexClassLoader都是继承自BaseDexClassLoader的,而且他们两个都只有构造函数,其类加载相关逻辑全都是在父类BaseDexClassLoader中。

唯一的区别就是第二个参数optimizedDirectory有没有使用,从API26,也就是8.0开始,他们两个的构造函数完全一样,已经没有任何区别,故DexClassLoader能干的事,PathClassLoader也能干

在搞清楚了PathClassLoader、DexClassLoader和BaseDexClassLoader之间的关系之后,我们写一段代码,研究一下PathClassLoader和BootClassLoader之间的关系。

在AS中创建一个Android项目,创建完成之后,再New 一个Module,项目的Project结构视图如下:

在宿主的MainActivity的onCreate方法中加入下代码:

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = findViewById(R.id.sample_text);tv.setText("xxxxxxx");printClassloader();//invokePluginMethod();//invokePluginByLoadAPK();}private void printlassCloader() {ClassLoader classLoader = getClassLoader();while (classLoader != null) {Log.d(TAG, "printlassCloader classloader = " + classLoader);classLoader = classLoader.getParent();}}

输出结果如下:

01-13 11:44:15.513 13212-13212/com.enjoy.myplugin D/Rayman: printClassloader classloader = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.myplugin-1/base.apk", zip file "/sdcard/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.myplugin-1/lib/arm64, /vendor/lib64, /system/lib64]]]
01-13 11:44:15.513 13212-13212/com.enjoy.myplugin D/Rayman: printClassloader classloader = java.lang.BootClassLoader@15bc8d9c

从打印结果来看,当前应用程序的类加载器,或者说MainActivity的ClassLoader就是PathClassLoader,而PathClassLoader的parent属性的值是BootClassLoader,需要说明的是,这儿的parent并不是父类,parent是ClassLoader类本身的一个成员属性。

那么在修改一下printClassLoader方法,看看Activity和AppCompactActivity的类加载器具体都是什么:

 private void printClassloader() {ClassLoader classLoader = getClassLoader();while (classLoader != null) {Log.d(TAG, "printClassloader classloader = " + classLoader);classLoader = classLoader.getParent();}Log.d(TAG,"activity clsloader = "+ Activity.class.getClassLoader());Log.d(TAG,"appcompactactivity clsloader = "+ AppCompatActivity.class.getClassLoader());}

打印结果如下:

01-13 11:55:37.838 13435-13435/? D/Rayman: printClassloader classloader = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.myplugin-2/base.apk", zip file "/sdcard/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.myplugin-2/lib/arm64, /vendor/lib64, /system/lib64]]]
01-13 11:55:37.838 13435-13435/? D/Rayman: printClassloader classloader = java.lang.BootClassLoader@15bc8d9c
01-13 11:55:37.838 13435-13435/? D/Rayman: activity clsloader = java.lang.BootClassLoader@15bc8d9c
01-13 11:55:37.838 13435-13435/? D/Rayman: appcompactactivity clsloader = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.myplugin-2/base.apk", zip file "/sdcard/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.myplugin-2/lib/arm64, /vendor/lib64, /system/lib64]]]

从打印结果可知:

正好印证了,前面谈到的BootClassLoader和PathClassLoader各自的作用范围

  1. BootClassLoader,用于加载Android Framework层class文件。
  2. PathClassLoader,用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex

说到这儿,可能会有些疑问:

为什么Activity和AppCompactActivity的类加载器不同,需要说明的是BootClassLoader用于加载Android Framework中的类,或者说是BootClassLoader加载的类一定是在Android SDK中的,而AppCompactActivity并不是在SDK中,而是我们在创建项目的时候添加的依赖库,在build.gradle中添加的dependencies,

implementation 'androidx.appcompat:appcompat:1.1.0'
  • 如何实现类加载

如何利用ClassLoader去加载一个类呢?其实非常简单,在之前创建的项目中有个Module是plugin,在plugin中添加一个java类,叫做PluginTest,如下图:

PluginTest的代码如下:

package com.enjoy.plugin;import android.util.Log;public class PluginTest {private final static String TAG = "Rayman";private void ShowPluginMsg(String msg){Log.d(TAG,"PluginTest ShowPluginMsg msg = "+msg);}
}

现在我们有一个apk文件,路径是 apkPath,然后里面有个类 com.enjoy.plugin.PluginTest,可以参照如下代码,实现对PluginTest类的加载,在宿主APP的MainActivity中实现如下方法:

private void invokePluginMethod() {//在这里我们需要先生成PluginTest.dex文件DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/PluginTest.dex",getCacheDir().getAbsolutePath(), null, getClassLoader());//注意此处如果替换为PathClassLoader也是可以正常执行的PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/PluginTest.dex",null,getClassLoader());try {//通过DexClassLoader加载PluginTest类Class<?> clz = dexClassLoader.loadClass("com.enjoy.plugin.PluginTest");//用DexClassLoader或者PathClassLoader都是可以的//Class<?> clz = pathClassLoader.loadClass("com.enjoy.plugin.PluginTest");//通过反射调用PluginTest类中的方法Object obj = clz.newInstance();Method method = clz.getDeclaredMethod("ShowPluginMsg",String.class);method.setAccessible(true);method.invoke(obj,"This is invoke Plugin method...");} catch (Exception e) {e.printStackTrace();}}

生成dex文件的步骤:

  1. 首先在AS中编译plugin这个Module,生成相应的.class文件,即生成PuginTest.class文件,.class的路径如下:
  2. 在Android SDK路径下找到dx.bat(Windows系统)这个文件,也就是找到自己的SDK路径,dx.bat在Android\Sdk\build-tools\28.0.3(这儿是Android SDK版本,也有可能是27.0.3或者25.0.2等),可参考如下截图:

将这个路径配置到环境变量,打开cmd,然后用如下命令生成dex文件:

dx --dex --output=output.dex input.class

使用这个命令需要注意,一定要在包名的外层目录执行,比如要生成前面提到的PluginTest.dex,需要按照下面方式执行,在cmd中先进入PluginTest.class文件的包名外层目录,如下:

进入到这儿之后,执行dx --dex --output=PluginTest.dex com\enjoy\plugin\PluginTest.class

执行完上述命令,就会在MyPlugin\plugin\build\intermediates\javac\debug\classes这个目录下生成PluginTest.dex文件

注意:

执行dx命令,只能在包名的外层目录执行,比如说在cmd里面直接进入到.class的目录MyPlugin\plugin\build\intermediates\javac\debug\classes\com\enjoy\plugin,那么在执行dx命令是会报错,无法生成相应的dex文件。

OK,到这儿dex文件就生成了,也就是生成了ClassLoader可以加载的dex文件,亦即DVM可以执行的dex文件。

接下来只需将PluginTest.dex拷贝或者push到手机的/sdcard中,或者是Android虚拟机的/sdcard目录下(/sdcard/PuginTest.dex),然后执行上面已经写好的invokePluginMethod方法,这里我直接在MainActivity的OnCreate方法中调用:

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = findViewById(R.id.sample_text);tv.setText(stringFromJNI());printClassloader();invokePluginMethod();}

运行结果如下:

01-13 14:43:48.050 13645-13645/? D/Rayman: printClassloader classloader = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.myplugin-1/base.apk", zip file "/sdcard/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.myplugin-1/lib/arm64, /vendor/lib64, /system/lib64]]]
01-13 14:43:48.050 13645-13645/? D/Rayman: printClassloader classloader = java.lang.BootClassLoader@15bc8d9c
01-13 14:43:48.050 13645-13645/? D/Rayman: activity clsloader = java.lang.BootClassLoader@15bc8d9c
01-13 14:43:48.050 13645-13645/? D/Rayman: appcompactactivity clsloader = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.myplugin-1/base.apk", zip file "/sdcard/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.myplugin-1/lib/arm64, /vendor/lib64, /system/lib64]]]
01-13 14:43:48.054 13645-13645/? D/Rayman: PluginTest ShowPluginMsg msg = This is invoke Plugin method...

LOG中的最后一句,就是我们在PluginTest.java中打印出来的,到这儿说明在宿主APP中成功的调用了插件APP中的代码。

需要知道插件代码的包名,类名,方法名,以及参数,并不需要安装插件APP,就可以通过ClassLoader加载dex文件,再利用反射调用插件代码,只需要dex文件就实现了免安装调用app代码的功能。

本章节讲述的是通过dex文件加载调用插件APP的代码,到这儿就暂时结束了,下一章节会讲述ClassLoader类加载机制,双亲委托机制,不用生成dex文件,只需要插件APK就可以通过ClassLoader和反射调用插件APP的代码。

下一章节链接:https://blog.csdn.net/qq_31429205/article/details/103959814

Android 插件化原理(一),通过dex文件调用插件app代码相关推荐

  1. 【Android 插件化】插件化原理 ( 类加载器 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  2. Android插件化原理—ClassLoader加载机制

    前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...

  3. 【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. Android插件化原理和实践 (一) 之 插件化简介和基本原理简述

    1 插件化简介 Android插件化技术是一种这几年间非常火爆的技术,也是只有在中国才流行起来的技术,这几年间每每开发者大会上几乎都会提起关于插件化技术和相关方向.在国内各大互联网公司无不都有自己的插 ...

  5. Android热修复及插件化原理

    1.前言 热修复一直是这几年来很热门的话题,主流方案大致有两种,一种是微信Tinker的dex文件替换,另一种是阿里的Native层的方法替换.这里重点介绍Tinker的大致原理. 2.类加载机制 介 ...

  6. Android插件化原理解析

    概述 Android插件化技术,可以实现功能模块的按需加载和动态更新,其本质是动态加载未安装的apk. 本文涉及源码为API 28 插件化原理 插件化要解决的三个核心问题: 类加载. 资源加载. 组件 ...

  7. Android 插件化原理入门笔记

    Android开发笔记 onGithub 笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication 1.综述 2015年是Android插件化技术突飞猛进的 ...

  8. android插件化原理

    最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接.随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内 ...

  9. 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. 【目标检测】(6) YOLOV2 目标检测在V1基础上的改进
  2. json的简单的数据格式
  3. 城市大脑全球标准研究2:如何理解和定义城市大脑?
  4. 【模拟】【贪心】POJ2709Painter
  5. CALL FUNCTION START NEW TASK
  6. python实战===如何优雅的打飞机
  7. Appium同时运行多个设备
  8. ado filter 多条记录_注意!武汉江南中心绿道武九线综合管廊工程开工,青山区多条道路通行规则有变...
  9. 2008年具有高校自主选拔录取资格的考生名单 - 江苏版
  10. sql科学计数法转换为普通数字_Python3数据类型之数字-Python入门到精通
  11. UI交互设计师在准备简历时应该注意什么?
  12. molloc/free和new/delete的区别
  13. 线性空间与坐标空间的同构
  14. java textarea滚动条_java中swing的textArea滚动条显示不出来
  15. VBS可扩展类库--语音库
  16. vue-meta插件动态设置meta和title标签(适用于ssr)
  17. YARN-client提交任务处理过程
  18. 广西移动摇一摇送话费_跟大家分享一个今天碰见的中国移动中奖坑钱套路
  19. python爬取头条视频_Python爬虫:爬取某日头条某瓜视频,有/无水印两种方法
  20. 4核处理器_苹果电脑便宜卖!4核i5处理器,480G固态硬盘,带刻录,13.4寸,双系统...

热门文章

  1. LoRaWAN协议-物理层(PHY)详解
  2. matplotlib+basemap画出标记地图
  3. 计蒜客-蒜头君回家(bfs)
  4. Word2010中怎样压缩图片使文件变小
  5. Delphi大师弗兰克·宝兰德(Frank Borland)回来了!
  6. 在 Windows10 系统中安装 Homestead 本地开发环境
  7. 在eclipse上运行html文件
  8. 湖南大学计算机学硕经验分享,2021湖南大学计算机专硕备考经验分享
  9. 为什么单线程的Redis能这么快?
  10. IT 界惊现文豪!华为领导及阿里 P10 遭吐槽!