继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android-类加载器及分包(Multidex)动态加载

波斯汪
关注TA
已关注
手记 502
粉丝 67
获赞 430

分包解决的问题:Android5.0之前使用的是dalvik虚拟机,之后使用的是art虚拟机.dalvik虚拟机里面是使用short来保存一个dex文件里面的方法数,而short类型的最大值是65536.所以说一旦dex里面的方法书超过65536,就会报错.

首先关于虚拟机的介绍:

Dalvik虚拟机

  Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,使得Dalvik具有高效、简洁、节省资源的特点。从Android系统架构图知,Dalvik虚拟机运行在Android的运行时库层。

Dalvik虚拟机的功能

  Dalvik作为面向Linux、为嵌入式操作系统设计的虚拟机,主要负责完成对象生命周期管理、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等。Dalvik充分利用Linux进程管理的特定,对其进行了面向对象的设计,使得可以同时运行多个进程,而传统的Java程序通常只能运行一个进程,这也是为什么Android不采用JVM的原因。Dalvik为了达到优化的目的,底层的操作大多和系统内核相关,或者直接调用内核接口。另外,Dalvik早期并没有JIT编译器,直到Android2.2才加入了对JIT的技术支持。

Dalvik虚拟机和Java虚拟机的区别

  本质上,Dalvik也是一个Java虚拟机。但它特别之处在于没有使用JVM规范。大多数Java虚拟机都是基于栈的结构(详情请参考:理解Java虚拟机体系结构),而Dalvik虚拟机则是基于寄存器。基于栈的指令很紧凑,例如,Java虚拟机使用的指令只占一个字节,因而称为字节码。基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间。Dalvik虚拟机的某些指令需要占用两个字节。基于栈和基于寄存器的指令集各有优劣,一般而言,执行同样的功能,前者需要更多的指令(主要是load和store指令),而后者需要更多的指令空间。需要更多指令意味着要多占用CPU时间,而需要更多指令空间意味着数据缓冲(d-cache)更易失效。更多讨论,(虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩) 给出了非常详细的参考。

  Java虚拟机运行的是Java字节码,而Dalvik虚拟机运行的是专有文件格式dex。在Java程序中,Java类会被编译成一个或多个class文件,然后打包到jar文件中,接着Java虚拟机会从相应的class文件和jar文件中获取对应的字节码。Android应用虽然也使用Java语言,但是在编译成class文件后,还会通过DEX工具将所有的class文件转换成一个dex文件,Dalvik虚拟机再从中读取指令和数据。dex文件除了减少整体的文件尺寸和I/O操作次数,也提高了类的查找速度。


jdk是java软件开发工具,里面除了基本的API,还有核心的JVM:java虚拟机,jvm主要负责和你计算机操作系统的交互,隔离一些不需要的东西,从而形成一个比较完善的虚拟系统,就这要java虚拟机就产生了,它不是自动运行的,而是通过操作系统调用jdk的java.exe来执行的。所以JDK里面包含了JVM,不是只有API。另外java是解释型语言,经过编译后生成.class字节码文件,但是jvm会根据操作系统来解释运行成你当前操作系统可以识别的语言,所以JVM起了桥梁的作用,这也是为什么java是跨平台的特性.

Dalvik vs ART

  • Dalvik 
    Android5.0以前使用的都是Dalvik虚拟机,我们知道Apk在打包的过程中会先将java等源码通过javac编译成.class文件,但是我们的Dalvik虚拟机只会执行.dex文件,这个时候dx会将.class文件转换成Dalvik虚拟机执行的.dex文件。Dalvik虚拟机在启动的时候会先将.dex文件转换成快速运行的机器码,又因为65535这个问题,导致我们在应用冷启动的时候有一个合包的过程,最后导致的一个结果就是我们的app启动慢,这就是Dalvik虚拟机的JIT特性(Just In Time)。

  • ART 
    ART虚拟机是在Android5.0才开始使用的Android虚拟机,ART虚拟机必须要兼容Dalvik虚拟机的特性,但是ART有一个很好的特性AOT(ahead of time),这个特性就是我们在安装APK的时候就将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,ART虚拟机天生支持多dex,所以也不会有一个合包的过程,所以ART虚拟机会很大的提升APP冷启动速度。

总结

  • ART优点:

  1. 加快APP冷启动速度

  2. 提升GC速度

  3. 提供功能全面的Debug特性

ART缺点:

  1. APP安装速度慢,因为在APK安装的时候要生成可运行.oat文件

  2. APK占用空间大,因为在APK安装的时候要生成可运行.oat文件

Java中常见的类加载器:

BootstrapClassLoader:

根类加载器(Bootstrap) 它负责加载虚拟机的核心类库,使用C++编写,

没有对应的java类,它的实现依赖于底层操作系统,它并没有继承java.lang.ClassLoader类, 主要加载jre/lib/目录下的核心库,如java.lang.*等


ExtClassLoader:

类的全名是sun.misc.Launcher$ExtClassLoader.

扩展类加载器(Extension) 它的父加载器为根类加载器,它从jre\lib\ext子目录下加载类库,它使用Java代码实现,

是java.lang.ClassLoader类的子类.


AppClassLoader:

类的全名是sun.misc.Launcher$AppClassLoader .

系统类加载器(System - 应用类加载器) 它的父加载器为ExtClassLoader扩展类加载器,它从环境变量CLASSPATH中加载类,

CLASSPATH是定义类的加载路径,通知JVM去哪里可以找到将要执行的Java程序对应的class文件以及程序中引用的其它class文件。

它是用户自定义的类加载器的默认父加载器,使用java实现,是java.lang.ClassLoader类的子类.

例如:

AppClassLoader的getParent()方法获取到的是 ExtClassLoader.

ExtClassLoader.getParent()方法获取到的是null,因为BootstrapClassLoader 没有对应的java类.


Class listClazz=ArrayList..class;

ClassLoader listLoader=listClazz.getClasLoader();
====> 得到的listLoader为null,因为ArrayList是jdk里面的类.

ArrayList的位置:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/rt.jar中,

所以加载它的是BootstrapClassLoader,所以listLoader为null.


注意:父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器

1. 他们其实是一种包装关系 

protected ClassLoader(ClassLoader parent){

}

用户自定义的类加载器

- java.lang.ClassLoader的子类

- 用户可以定制类的加载方式

2. 父类委托机制:

3. 为何使用父类委托机制?

父类委托机制的优点是能够提高软件系统的安全性,假设我自己定义一个类加载器,然后随便伪造一个类,这个类不符合jvm规范,里面有不安全的代码,如果不适用父类委托机制,那么这个类就会被直接加载到内存里面了。 

如果使用父类委托,那么就会被父加载器加载,它会按照jvm规范来加载,不符合规范就不会加载。

例如:我们自己创建一个ArrayList类,包括包名也和ArrayList一样.

  • Class listClazz=ArrayList..class;

  • ClassLoader listLoader=listClazz.getClasLoader();

  • 然后获取的类加载器,依然还是空,说明它还是委托给了根类加载器去加载的.所以是空,同时也保证了软件系统的安全性.不能够去伪造它.


  • 4. 运行时包:

  • 决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同,只有属于同一运行时包的类才能互相访问包可见的类和类成员,这样的限制能避免用户自定义的类冒充核心类库的类去访问核心类库的包的可见成员。

  • Android中常用的类加载器:

  • PathClassLoader:加载/data/app/目录下的apk文件,从这个目录可以看出,PathClassLoader主要用来加载已经安装了的apk.

  • DexClassLoader:它在创建DexClassLoader时需要传入加载路径和outpath,也就是说可以加载任何路径下的.apk、.jar和.dex文件.

  • 并且会在指定的outpath路径释放出dex文件.

  • 继承关系:

  • ClassLoader:

  • 属性:parent:ClassLoader

  • 方法:defindClass()

  • findClass()

  • loadClass():loadClass方法和java的机制一样,符合父委托加载机制.

  • BaseDexClassLoader:继承自ClassLoader

  • 属性:pathList:DexPathList(Gradle动态加载dex时会用到)
    方法:重写了findClass()

  • PathClassLoader:继承于BaseDexClassLoader:只能加载已经安装好的apk文件.

  • 构造方法接收3个参数:PathClassLoader(String dexPath,String libraryPath,ClassLoader parent).


  • String dexPath(apk的路径)

  • String libraryPath(apk中so库的路径),

  • ClassLoader parent(父加载器,不是继承关系的父加载 器,是委托机制中的父加载器)
    DexClassLoader:继承自BaseDexClassLoader,跟PathClassLoader的区别就是构造方法多个optimizedDirectory参数.

  • 构造方法4个参数:PathClassLoader(String dexPath,String optimizedDirectory,String libraryPath,ClassLoader parent).
    String optimizedDirectory(是个目录用来存放odex,也就是输出路径.apk在安装的过程中有个阶段叫odex阶段,主要是对apk 中的dex文件进行优化操作)

  1.  /**使用DexClassLoader方式加载类*/  

  2.         //dex压缩文件的路径(可以是apk,jar,zip格式)  

  3.         String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";  

  4.         //dex解压释放后的目录  

  5.         //String dexOutputDir = getApplicationInfo().dataDir;  

  6.         String dexOutputDirs = Environment.getExternalStorageDirectory().toString();  

  7.         //定义DexClassLoader  

  8.         //第一个参数:是dex压缩文件的路径  

  9.         //第二个参数:是dex解压缩后存放的目录  

  10.         //第三个参数:是C/C++依赖的本地库文件目录,可以为null  

  11.         //第四个参数:是上一级的类加载器  

  12.         DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader()); 


  13. /**使用PathClassLoader方法加载类*/  

  14.         //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    

  15.         Intent intent = new Intent("com.dynamic.impl", null);    

  16.         //获得包管理器    

  17.         PackageManager pm = getPackageManager();    

  18.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);    

  19.         //获得指定的activity的信息    

  20.         ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;    

  21.         //获得apk的目录或者jar的目录    

  22.         String apkPath = actInfo.applicationInfo.sourceDir;    

  23.         //native代码的目录    

  24.         String libPath = actInfo.applicationInfo.nativeLibraryDir;    

  25.         //创建类加载器,把dex加载到虚拟机中    

  26.         //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取  

  27.         //第二个参数:是C/C++依赖的本地库文件目录,可以为null  

  28.         //第三个参数:是上一级的类加载器  

  29.         PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader()); 

    • BaseDexClassLoader的两个子类PathClassLoader和DexClassLoader都没有重写loadClass方法,所以也符合父委托加载机制的.

    • DexClassLoader和PathClassLoader其实都是通过DexFile这个类来实现类加载的。这里需要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。因此,我们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。

    • DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要将包含包名的类名中的”.”转换为”/”.

    • PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件


    • 在Android的activity中通过getSystemClassLoader(),返回一个单例的SystemClassLoader, SystemClassLoader的单例创建直接return

    • 了一个PathClassLoader. PathClassLoader的参数传了一个父加载器是BootClassLoader, BootClassLoader继承于ClassLoader,重写了findClass调用

    • VMClassLoader.loadClass()方法.也就是获取是PathClassLoader.也可以直接new PathClassLoader()和new DexClassLoader().

分包方案一Ant分包:

    • 使用类加载器实现的动态加载分dex的简单实例(Gradle实现的muldex分包之前):

    • (Gradle实现的muldex分包之前的实现原理是将自己定义的分dex的类加载器,插入到PathClassLoader和BootClassLoader之间,这样主dex就能使用分dex中的类)

    • 实际操作

      使用到的工具都比较常规:javac、dx、eclipse等其中dx工具最好是指明--no-strict,因为class文件的路径可能不匹配
      加载好类后,通常我们可以通过Java反射机制来使用这个类但是这样效率相对不高,而且老用反射代码也比较复杂凌乱。更好的做法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,并且有一个参数为空的构造函数,以使我们能够通过Class的newInstance方法产生对象然后将对象强制转换为interface对象,于是就可以直接调用成员方法了,下面是具体的实现步骤了:
      第一步:

      编写好动态代码类:


    • [java] view plain copy

      实现类代码如下:



      [java] view plain copy

      这样动态类就开发好了


      第二步:

      将上面开发好的动态类打包成.jar,这里要注意的是只打包实现类Dynamic.java,不打包接口类IDynamic.java,

    • 然后将打包好的jar文件拷贝到android的安装目录中的platform-tools目录下,使用dx命令:(我的jar文件是dynamic.jar)

      dx --dex --output=dynamic_temp.jar dynamic.jar

      这样就生成了dynamic_temp.jar,这个jar和dynamic.jar有什么区别呢?

      其实这条命令主要做的工作是:首先将dynamic.jar编译成dynamic.dex文件(Android虚拟机认识的字节码文件),然后再将dynamic.dex文件压缩成dynamic_temp.jar,当然你也可以压缩成.zip格式的,或者直接编译成.apk文件都可以的,这个后面会说到。

      到这里还不算完事,因为你想想用什么来连接动态类和目标类呢?那就是动态类的接口了,所以这时候还要打个.jar包,这时候只需要打接口类IDynamic.java了

    • 然后将这个.jar文件引用到目标类中,下面来看一下目标类的实现:

      [java] view plain copy

      这里面定义了一个IDynamic接口变量,同时使用了DexClassLoader和PathClassLoader来加载类,这里面先来说一说DexClassLoader方式加载:


      [java] view plain copy

      上面已经说了,DexClassLoader是继承ClassLoader类的,这里面的参数说明:


      第一个参数是:dex压缩文件的路径:这个就是我们将上面编译后的dynamic_temp.jar存放的目录,当然也可以是.zip和.apk格式的

      第二个参数是:dex解压后存放的目录:这个就是将.jar,.zip,.apk文件解压出的dex文件存放的目录,这个就和PathClassLoader方法有区别了,同时你也可以看到PathClassLoader方法中没有这个参数,这个也真是这两个类的区别:

      PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在手机的data/dalvik目录中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

    • 然而我们可以通过DexClassLoader方法指定解压后的dex文件的存放目录,但是我们一般不这么做,因为这样做无疑的暴露了dex文件,所以我们一般不会将.jar/.zip/.apk压缩文件存放到用户可以察觉到的位置,同时解压dex的目录也是不能让用户看到的。

      第三个参数和第四个参数用到的不是很多,所以这里就不做太多的解释了。

      这里还要注意一点就是PathClassLoader方法的时候,第一个参数是dex存放的路径,这里传递的是:

      [java] view plain copy

      指定的apk安装路径,这个值只能这样获取,不然会加载类失败的


      第三步:

      运行目标类:

      要做的工作是:

      如果用的是DexClassLoader方式加载类:这时候需要将.jar或者.zip或者.apk文件放到指定的目录中,我这里为了方便就放到sd卡的根目录中

      如果用的是PathClassLoader方法加载类:这时候需要先将Dynamic.apk安装到手机中,不然找不到这个activity,同时要注意的是:


      [java] view plain copy

      这里的com.dynamic.impl是一个action需要在指定的apk中定义,这个名称是动态apk和目标apk之间约定好的


      -------------------------------------------------------------------

  1. //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    

  2. Intent intent = new Intent("com.dynamic.impl", null);    

  1. //获得apk的目录或者jar的目录    

  2. String apkPath = actInfo.applicationInfo.sourceDir;    

  1. //定义DexClassLoader  

  2. //第一个参数:是dex压缩文件的路径  

  3. //第二个参数:是dex解压缩后存放的目录  

  4. //第三个参数:是C/C++依赖的本地库文件目录,可以为null  

  5. //第四个参数:是上一级的类加载器  

  6. DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  

  1. package com.jiangwei.demo;  

  2.   

  3. import java.io.File;  

  4. import java.util.List;  

  5.   

  6. import android.app.Activity;  

  7. import android.content.Intent;  

  8. import android.content.pm.ActivityInfo;  

  9. import android.content.pm.PackageManager;  

  10. import android.content.pm.ResolveInfo;  

  11. import android.os.Bundle;  

  12. import android.os.Environment;  

  13. import android.view.View;  

  14. import android.widget.Button;  

  15. import android.widget.Toast;  

  16.   

  17. import com.dynamic.interfaces.IDynamic;  

  18.   

  19. import dalvik.system.DexClassLoader;  

  20. import dalvik.system.PathClassLoader;  

  21.   

  22. public class AndroidDynamicLoadClassActivity extends Activity {  

  23.       

  24.     //动态类加载接口  

  25.     private IDynamic lib;  

  26.       

  27.     @Override  

  28.     public void onCreate(Bundle savedInstanceState) {  

  29.         super.onCreate(savedInstanceState);  

  30.         setContentView(R.layout.main);  

  31.         //初始化组件  

  32.         Button showBannerBtn = (Button) findViewById(R.id.show_banner_btn);  

  33.         Button showDialogBtn = (Button) findViewById(R.id.show_dialog_btn);  

  34.         Button showFullScreenBtn = (Button) findViewById(R.id.show_fullscreen_btn);  

  35.         Button showAppWallBtn = (Button) findViewById(R.id.show_appwall_btn);  

  36.         /**使用DexClassLoader方式加载类*/

  37.         //dex压缩文件的路径(可以是apk,jar,zip格式)  

  38.         String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";  

  39.         //dex解压释放后的目录  

  40.         //String dexOutputDir = getApplicationInfo().dataDir;  

  41.         String dexOutputDirs = Environment.getExternalStorageDirectory().toString();  

  42.         //定义DexClassLoader  

  43.         //第一个参数:是dex压缩文件的路径  

  44.         //第二个参数:是dex解压缩后存放的目录  

  45.         //第三个参数:是C/C++依赖的本地库文件目录,可以为null  

  46.         //第四个参数:是上一级的类加载器  

  47.         DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader()); 

  48.   

  49.           /*--------------------------------

  50.         /**使用PathClassLoader方法加载类*/  

  51.         //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    

  52.         Intent intent = new Intent("com.dynamic.impl", null);    

  53.         //获得包管理器    

  54.         PackageManager pm = getPackageManager();    

  55.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);    

  56.         //获得指定的activity的信息    

  57.         ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;    

  58.         //获得apk的目录或者jar的目录    

  59.         String apkPath = actInfo.applicationInfo.sourceDir;    

  60.         //native代码的目录    

  61.         String libPath = actInfo.applicationInfo.nativeLibraryDir;    

  62.         //创建类加载器,把dex加载到虚拟机中    

  63.         //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取  

  64.         //第二个参数:是C/C++依赖的本地库文件目录,可以为null  

  65.         //第三个参数:是上一级的类加载器  

  66.         PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());  
    -------------------------------------------*/  

  67.         //加载类  

  68.         try {  

  69.             //com.dynamic.impl.Dynamic是动态类名  

  70.             //使用DexClassLoader加载类  

  71.             //Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");  

  72.             //使用PathClassLoader加载类  

  73.             Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");  

  74.             lib = (IDynamic)libProviderClazz.newInstance();  

  75.             if(lib != null){  

  76.                 lib.init(AndroidDynamicLoadClassActivity.this);  

  77.             }  

  78.         } catch (Exception exception) {  

  79.             exception.printStackTrace();  

  80.         }  

  81.         /**下面分别调用动态类中的方法*/  

  82.         showBannerBtn.setOnClickListener(new View.OnClickListener() {  

  83.             public void onClick(View view) {  

  84.                if(lib != null){  

  85.                    lib.showBanner();  

  86.                }else{  

  87.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  

  88.                }  

  89.             }  

  90.         });  

  91.         showDialogBtn.setOnClickListener(new View.OnClickListener() {  

  92.             public void onClick(View view) {  

  93.                if(lib != null){  

  94.                    lib.showDialog();  

  95.                }else{  

  96.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  

  97.                }  

  98.             }  

  99.         });  

  100.         showFullScreenBtn.setOnClickListener(new View.OnClickListener() {  

  101.             public void onClick(View view) {  

  102.                if(lib != null){  

  103.                    lib.showFullScreen();  

  104.                }else{  

  105.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  

  106.                }  

  107.             }  

  108.         });  

  109.         showAppWallBtn.setOnClickListener(new View.OnClickListener() {  

  110.             public void onClick(View view) {  

  111.                if(lib != null){  

  112.                    lib.showAppWall();  

  113.                }else{  

  114.                    Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();  

  115.                }  

  116.             }  

  117.         });  

  118.     }  

  119. }  

  1. package com.dynamic.impl;  

  2.   

  3. import android.app.Activity;  

  4. import android.widget.Toast;  

  5.   

  6. import com.dynamic.interfaces.IDynamic;  

  7.   

  8. /** 

  9.  * 动态类的实现 

  10.  * 

  11.  */  

  12. public class Dynamic implements IDynamic{  

  13.   

  14.     private Activity mActivity;  

  15.       

  16.     @Override  

  17.     public void init(Activity activity) {  

  18.         mActivity = activity;  

  19.     }  

  20.       

  21.     @Override  

  22.     public void showBanner() {  

  23.         Toast.makeText(mActivity, "我是ShowBannber方法", 1500).show();  

  24.     }  

  25.   

  26.     @Override  

  27.     public void showDialog() {  

  28.         Toast.makeText(mActivity, "我是ShowDialog方法", 1500).show();  

  29.     }  

  30.   

  31.     @Override  

  32.     public void showFullScreen() {  

  33.         Toast.makeText(mActivity, "我是ShowFullScreen方法", 1500).show();  

  34.     }  

  35.   

  36.     @Override  

  37.     public void showAppWall() {  

  38.         Toast.makeText(mActivity, "我是ShowAppWall方法", 1500).show();  

  39.     }  

  40.   

  41.     @Override  

  42.     public void destory() {  

  43.     }  

  44.   

  45. }  

  1. package com.dynamic.interfaces;  

  2. import android.app.Activity;  

  3. /** 

  4.  * 动态加载类的接口 

  5.  */  

  6. public interface IDynamic {  

  7.     /**初始化方法*/  

  8.     public void init(Activity activity);  

  9.     /**自定义方法*/  

  10.     public void showBanner();  

  11.     public void showDialog();  

  12.     public void showFullScreen();  

  13.     public void showAppWall();  

  14.     /**销毁方法*/  

  15.     public void destory();  

  16. }  

上面例子中使用DexClassLoader加载还是使用PathClassLoader动态加载都有问题的,会报: ClassNotFoundException

DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader()); 

PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());  

因为创建的时候都是直接将获取到当前主dex的类加载器getClassLoader(获取到的是PathClassLoader),传给了自己创建的分dex的classLoader作为父类加载器.这样在当此代码被执行的时候,自己创建的类加载器,在加载分dex的时候,会委托他的父类加载器(及当前的类加载器去加载),而当前的主dex的类加载器是找不到分dex中的类的.

正确的逻辑应该是,将当前主dex的类加载器的父类加载器传递给自己创建的类加载器.然后通过反射获取ClassLoader(因为当前的主dex的类加载器也是继承于ClassLoader,并且是parent字段是属于ClassLoader的)中的parent字段将其设置为分dex的类加载的(即自己定义的类加载器).

也就说正确的逻辑是,应该将当前主dex的类加载器的父类加载器设置为自己创建的分dex类加载器的父类加载器.然后将自己创建的分dex类加载器设置为的当前主dex的父类加载器.既将自己定义的分dex的类加载器,插入到PathClassLoader和BootClassLoader之间.

这样才能找的到类,不会报ClassNotFoundException.

其中DexClassLoader的第三个参数, libraryPath,如果分dex中也使用到了第三方so库的话,也需要传入,它的路径获取通过ApplicationInfo.nativeLibraryDir获取.

分包方案二Gradle分包:

Gradle中的muldex插件的分包及动态加载的原理:

muldex分包的类加载器都是使用的DexClassLoader.

muldex库实现的原理跟上面不一样,不是通过将DexClassLoader(一般分dex使用DexClassLoader加载)插入到PathClassLoader和BootClassLoader之间.他是通过将DexClassLoader的Element数组中的dex的path追加到PathClassLoader的Element中.

  • 因为PathClassLoader和DexClassLoader的父类BaseDexClassLoader有个属性pathList ,pathList属于DexPathList 类.

  • DexPathList 中有个属性数组Element,这个数组是存放dex文件的.

分包过程中的常见问题:UNEXPECTED TOP-LEVEL ERROR:java.lang.OutOfMemoryError:Java heap space
解决方案:

android{

dexOptions{

javaMaxHeapSize:"2g"//或者更大

}



}

原文链接:http://www.apkbus.com/blog-953329-77873.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP