Android App热更新中的插件化(ClassLoader、DexLoader、PathClassLoader)与虚拟机之间的关系(1)

PathClassLoader在热更新的作用?

Android ClassLoader流程解读并简单方式实现热更新- https://www.jianshu.com/p/2f4939320eb1

- 源码注释:PathClassLoader和DexClassLoader,它们都继承自BaseDexClassLoader
 PathClassLoader被用来加载本地文件系统上的文件或目录,但不能从网络上加载,关键是它被用来加载系统类和我们的应用程序;
 DexClassLoader用来加载jar、apk,其实还包括zip文件或者直接加载dex文件,它可以被用来执行未安装的代码或者未被应用加载过的代码。

-- Android插件化与虚拟机之间的关系
Android热修复与插件化(二)虚拟机详解- https://blog.csdn.net/qq_28595679/article/details/76169836
JVM整体结构:
 1.使用javac将java文件编译成class文件。
 2.类加载器(ClassLoader)将class字节码加载进JVM对应的内存中。
 3.JVM将内存分配给方法区、堆区、栈区、本地方式栈4个部分,这4个部分分别存储字节码不同的部分。
 4.垃圾回收器(gc)会管理整个内存空间中的垃圾。

  Java代码编译的详细流程(即,javac的执行过程),了解即可,一般只要知道java文件是通过javac命令编译成class文件,再通过java命令运行。

-- 类加载器,加载流程:
 Loading:类的信息从文件中获取并载入到JVM的内存中。
 Verifying:检查读入的结构是否符合JVM规范的描述。
 Preparing:分配一个结构用来存储类信息。
 Resolving:把类的常量池中的所有符号引用变成直接引用。
 Initializing:执行静态初始化程序,把静态变量初始化成指定的值。

  堆区分为新生代(Young Generation)与老年代(Old Generation),程序在创始对象时,对象会先被分配到新生代中,当新生代区内存不足时,JVM会通过一定的算法规则将新生代中的对象转移至老年代中,当新生代与老年代都没有足够的内存空间时,JVM就会抛出OOM异常。
  java中的引用类型有4种:强引用、软引用、弱引用、虚引用。其中,强引用和弱引用在开发中最常用。

1.Dalvik VM 与 JVM 的不同
 执行的文件不同,一个是class,一个是dex。
 类加载的系统与JVM区别较大。
 可以同时存在多个DVM,但JVM只能存在一个。
 Dalvik是基于寄存器的,而JVM是基于栈的。
2、Dalvik VM 与 ART 的不同
 DVM使用JIT来将字节码转换成机器码,效率低。
 ART采用了AOT预编译技术,执行速度更快。
 ART会占用更多的应用安装时间和存储空间。

> Android 动态升级
 1.Android 插件化 —— 指将一个程序划分为不同的部分,比如一般 App 的皮肤样式就可以看成一个插件;
 2.Android 组件化 —— 这个概念实际跟上面相差不那么明显,组件和插件较大的区别就是:组件是指通用及复用性较高的构件,比如图片缓存就可以看成一个组件被多个 App 共用;
 3.Android动态加载 — 这个实际是更高层次的概念,也有叫法是热加载或 Android 动态部署,指容器(App)在运⾏状态下动态加载某个模块,从而新增功能或改变某⼀部分行为. 

-- 1.DexClassLoader类 ,可以加载jar/apk/dex,可以从SD卡中加载未安装的apk。

    2.PathClassLoader类,只能加载已经安装到Android系统中的apk文件。

Android动态加载jar、apk的实现- https://blog.csdn.net/bboyfeiyu/article/details/11710497

-- Android 热补丁动态修复框架小结- http://blog.csdn.net/lmj623565791/article/details/49883661/
  热修复入门:Android 中的 ClassLoader。ClassLoader 是个抽象类,其具体实现的子类有 BaseDexClassLoader 和SecureClassLoader。
  SecureClassLoader 的子类是 URLClassLoader,其只能用来加载 jar 文件,这在 Android 的 Dalvik/ART 上没法使用的。
  BaseDexClassLoader 的子类是 PathClassLoader和 DexClassLoader。
> Dalvik,ART 

 JVM 及 Dalvik ART对类唯一的识别是 ClassLoader id + PackageName + ClassName。
  Android 也有自己的 ClassLoader,分为 dalvik.system.DexClassLoader 和 dalvik.system.PathClassLoader。  
  关于动态加载apk,理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。
 DexClassLoader :可以加载外部的 apk、jar 或 dex文件,并且会在指定的outpath 路径存放其 dex 文件
 PathClassLoader:可以加载/data/app目录下的apk,这也意味着,它只支持直接操作dex文件或只能加载已经安装的apk,不能直接从 zip 包中得到 dex
 URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在android中无法使用,尽管还有这个类 
   
  在Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存储了多有Java编译字节码的归档文件。PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

> Android 插件化
  对于虚拟机来说,其实所有的代码都是在运行时被加载进来的。而不同于C语言还存在着静态链接。虚拟机在所有Java代码执行之前被启动,然后开始把字节码加载到环境中执行,我们可以理解成所有的代码都是动态加载到虚拟机里的。
  在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。
 关于插件,已经在各大平台上出现过很多,eclipse插件、chrome插件、3dmax插件,所有这些插件大概都为了在一个主程序中实现比较通用的功能,把业务相关或者让可以让用户自定义扩展的功能不附加在主程序中,主程序可在运行时安装和卸载。

   在android如何实现插件也已经被广泛传播,实现的原理都是实现一套插件接口,把插件实现编成apk或者dex,然后在运行时使用DexClassLoader动态加载进来,这里分享一下DexClassLoader加载原理和分析在实现插件时不同操作造成错误的原因。

  DexClassLoader是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。能够加载自定义的jar/apk/dex;
  PathClassLoader提供一个简单的ClassLoader实现,可以操作在本地文件系统的文件列表或目录中的classes,但不可以从网络中加载classes。只能加载系统中已经安装过的apk;
  所以Android系统默认的类加载器为PathClassLoader,而DexClassLoader可以像JVM的ClassLoader一样提供动态加载。
  很多博客里说PathClassLoader只能加载已安装的apk的dex,其实这说的应该是在dalvik虚拟机上,在art虚拟机上PathClassLoader可以加载未安装的apk的dex(在art平台上已验证)。

  Dalvik采用的是JIT技术,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而ART采用Ahead-of-time(AOT)技术,应用在第一次安装的时候,字节码就会预先编译成机器码,这个过程叫做预编译。ART同时也改善了性能、垃圾回收(Garbage Collection)、应用程序除错以及性能分析。但是请注意,运行时内存占用空间较少同样意味着编译二进制需要更高的存储。 

  Android中类加载器有BootClassLoader,URLClassLoader,PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最终继承自java.lang.ClassLoader。
 自定义ClassLoader和双亲委派机制,JVM中的类的加载机制。

> Android 插件化 Sample
1.先来回顾一下如何在Android平台下做插件吧,首先定义一个插件接口IPlugin(其实不使用接口也可以,在加载类的时候直接使用反射调用相关类,但写代码来比较蛋疼):
public interface IPlugin {
  public String getName();
  public String getVersion();
  public void show();
}

public abstract class AbsPlugin {
  public abstract String getName();
  public abstract String getVersion();
  public abstract void show();
}

2.写好这个接口后,导出这个IPlugin生成jar包,这个相当于SDK了,然后新建一个工程并,这个工程以引用方式(即eclipse中externallibrary)引用这个包后,实现这个接口:
public class PluginImp extends AbsPlugin {
  public String getName() {
    return "PluginImp";
  }

  public String getVersion() {
    return "1.0";
  }

  public void show() {
    android.util.Log.("PluginImp", "ha ha I'm pluginimp");
  }
}

3.编译这个工程并生成apk或者导出实现类生成dex,这时就做好了我们的插件实体,最后在我们的主工程里把插件接口的jar(即插件SDK)放在lib目录下在apk编译时打包进来,同时用下面的代码在需要的时候加载进来调用:
try {
  ClassLoader classLoader= context.getClassLoader() ;
  DexClassLoader localDexClass Loader = newDexClassLoader("/sdcard/plugin.apk", dexoutputpath, null ,classLoader) ;
  //load class
  Class localClass = localDexClassLoader.loadClass("org.cmdmac.plugin.PluginImpl");

  //construct instance
  Constructor localConstructor = localClass.getConstructor(new Class[] {});
  Object instance = localConstructor.newInstance(new Object[] {});

  //call method
  IPlugin plugin = (IPlugin)instance;
  plugin.show ();
 } catch (Excpetion e) {
  //To do something
}

-- 原理剖析,这样我们就实现了一个简单的插件,现在来问两个问题:
 a.为什么插件SDK要放在lib目录?放在lib目录和非lib目录以external方式引用的区别是什么?
 b.为什么插件SDK只能导出接口,在插件工程里要以external方式引用又不是放在lib目录了?

-- 在回答这两个问题之前,我们来做下实验:
  a.主工程不把插件sdk放在lib目录下,而是以external方式引用,插件SDK和插件工程引用的方式不变。这时在运行时会产生如下错误:
java.lang.ClassNotFoundException: PluginImpl
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:61)
at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
at org.cmdmac.host.MainActivity.onCreate(MainActivity.java:23)
at android.app.Activity.performCreate(Activity.java:5084)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
at
……

  b.在插件工程里把SDK放到lib目录下,主工程引用方式不变,会出现下面的错误
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
dalvik.system.DexFile.defineClass(Native Method)
dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211)
dalvik.system.DexPathList.findClass(DexPathList.java:315)
dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.j

  c.在插件工程把SDK放到lib目录下,加载的classloader改为:
ClassLoaderclassLoader=ClassLoader.getSystemClassLoader();

-- 会出现下面的错误:
java.lang.ClassCastException: org.cmdmac.plugin.PluginImp cannot be cast to org.cmdmac.pluginsdk.AbsPlugin
com.example.org.cmdmac.host.test.MainActivity.onCreate(MainActivity.java:30)
android.app.Activity.performCreate(Activity.java:5084)
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
com.lbe.security.service.core.client.internal.InstrumentationDelegate.callActivityOnCreate(InstrumentationDelegate.java:76)
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2044)

这些错误是怎么来的?解析答案得从JAVA类加载原理出发:
Java的类加载器一般为URLClassLoader,在Android里是不能用的,取而代之的是DexClassLoader和PathClassLoader。
Java中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由Java应用开发人员编写的。系统提供的类加载器主要有下面三个:
  a.引导类加载器(bootstrapclassloader):它用来加载Java的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
  b.扩展类加载器(extensionsclassloader):它用来加载Java的扩展库。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。
  c.系统类加载器(systemclassloader):它根据Java应用的类路径(CLASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。

  类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下Java虚拟机是如何判定两个Java类是相同的。Java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个Java类com.example.Sample,编译之后生成了字节代码文件Sample.class。两个不同的类加载器ClassLoaderA和ClassLoaderB分别读取了这个Sample.class文件,并定义出两个java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于Java虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常ClassCastException。

-- 由java类加载器原理可以得到如下答案:
关于第一个错误:
  Android默认的类加载器是PathClassLoader那么:ClassLoaderclassLoader=context.getClassLoader();
  这个得到的结果就是PathClassLoader,它加载了一个apk或者dex里的所有类,当以exteral方式引用时,由于生成的主工程的apk是没有把接口类打包进来的,这时使用PathClassLoader去加载时也是没有加载到Impl的,由于PathClassLoader是父加载器,它找不到就会使用类加载器本身(即DexClassLoader)去查找,他去查找时发现需要引用AbsPlugin和IPlugin,这时再去找了一圈,也是没有找到,因此出现ClasNotFound错误。

关于第二个错误:
  第二个错误是由于主工程和插件都包含和插件的接口,这时使用PathClassLoader在主工程查找时找到AbsPlugin和IPlguin,用DexClassLoader加载Impl时因为也会加载AbsPlugin和IPlugin,但这时使用DexClassLoader在plugin.apk也找到了,因此出现两个相同类的但是由不同的类加载器加载的,就出现了这个错误,这个错误类型出错的代码可以查看Resolve.cpp的dvmResolveClass函数。

关于第三个错误:
  这个错误是在类型转换的时候出现,原因也是两个不同的基类,但原因不同,是因为使用SystemClassLoader加载时只能在plugin.apk里找到,但在进行类型转换时查找AbsPlugin和IPlugin是在主工程中查找的,这时的情况下,主工程的AbsPlugin和Impl继承的AbsPlugin是在不同的类加载器加载的,不能进行类型转换了。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值