Android动态加载学习总结(一):类加载器

本文取自本人CSDN的博客:
http://blog.csdn.net/gaozhan_csdn/article/details/52085911
话说简书不是很适合代码编写啊,好麻烦。

参考资料: 《深入理解Java虚拟机》 -周志明 Android中的动态加载机制
本篇不深入涉及Java类加载器,如果想更深入了解,可以看一下这篇博客http://blog.csdn.net/zhoudaxia/article/details/35824249
前言:动态加载在应用开发中有着很重要的地位,当我们项目越来越大,我们可以通过插件化来减少应用的内存,然后动态加载那些插件。还有一个方面,如果我们的应用频繁的更新,频繁的发布新版本,肯定会造成用户体验下降,那么我们可以用动态加载技术在不发布新版本的情况下更新一些模块。
那么既然要用动态加载,就肯定涉及到类加载器,我们先看一下Java中的类加载器。
一、Java中类加载器
在这里,我们不去说类加载的具体过程,只是总结一下Java中类加载器与双亲委派模型。

1.类加载器与类本身确定类的唯一性
对于一个类,这个类本身和加载它的类加载器共同确定其在虚拟机中的唯一性。 我们使用两个类加载器进行加载同一个类,那么这两个类是不相等的,那么虚拟机中会存在两个同名的类。
2.Java三种预定义类型类加载器

  • 启动类加载器(Bootstrap ClassLoader,也称为引导类加载器)
    该类加载器负责将存放在\lib目录中,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(这点很重要)类加载到虚拟机中。
    加载虚拟机识别的这一点与双亲委派模型配合很重要。在下面介绍,先留意这一点。
    对于启动类加载器还有一点需要注意,也是和双亲委派模型有关,如果我们自定义一个类加载器,想把一个加载请求委派给启动类加载器,只需要使用null替代即可(可以看下面的findClass()方法中的代码实现)。
    开发者不可以直接使用该加载器。

  • 扩展类加载器(Extension ClassLoader)
    该加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路程中的所有类库。
    开发者可以直接使用该加载器。

  • 应用程序类加载器(Application ClassLoader,也称为系统类加载器)
    该类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。该加载器负责加载用户类路径(ClassPath)上所指定的类库。
    开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下该加载器是程序中的默认的类加载器。

3.双亲委派模型


这里写图片描述

上图显示了类加载器之间的层次关系,被称为类加载器的双亲委派模型。

除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

那么根据上图,如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把请求委派给父类加载器去加载,每一个层次的类加载器都是这样,那么所有的请求其实最后都是会到启动类加载器中,如果父类加载器反馈无法加载,子加载器才会尝试自己加载。

实现双亲委派模型的代码在loadClass()方法中:

jianshu1.PNG

4.双亲委派模型的优点

大家都知道Object类是个基础类,如果我们自己写了一个Object类,那么如果没有双亲委派模型的话,再加上我们并没有用启动类加载器去加载我们写的这个Object类的话,系统中会存在两个Object类(参考上述的类在虚拟机中的唯一性)。那么有了双亲委派模型,我们写了一个Object类,会先去检查它是否加载了(肯定已经加载了),那么我们写的这个就不会被重复加载,也就保证了基础类的唯一性,防止混乱破坏。就算没有检查,根据上面关于启动类加载器的介绍,必须是虚拟机识别的,Object存放在rt.jar中,我们写的不会被识别。

那么从另一个方面,基础类Object怎么保证的在任何环境下都是同一个类(即加载器在任何情况下都是同一个),这就是双亲委派模型的作用了,每次这个加载请求都会委派给处于最顶端的启动类加载器进行加载,虚拟机识别rt.jar,那么就保证了每次都是由启动类加载器加载的Object,那么根据第1条的唯一性,这样做就保证了Object的唯一性。

5.自定义类加载器符合双亲委派模型

我们根据上面的介绍知道,双亲委派模型的逻辑都在loadClass()方法中,那么我们为了不破坏双亲委派模型,自定义类加载器时不去重写loadClass()方法,而是重写findClass()方法,将自己的类加载逻辑写到findClass()方法中,在loadClass()方法中,最后父类加载器无法加载的时候,调用的就是findClass()方法。这样我们就保证了我们自定义的类加载器是符合双亲委派模型的。如果重写loadClass()方法,会出现一系列错误,比如基础类加载不上等。

findClass()方法是在JDK1.2以后引入的。

5.defineClass()方法
//定义类型,一般在findClass方法中读取到对应字节码后调用,final,不可继承,一般不用重写,直接调用。在findClass()方法中调用。
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }

介绍这个是为了注明下面的Android类加载与Java中类加载的区别。

二、Android类加载器

前面介绍的是Java中的类加载器,我们知道android中的虚拟机是Dalvik,它不是标准的Java虚拟机,所以在类加载机制上,和Java中的类加载器有一些区别。

我们在java标准的虚拟机中,如果自定义类加载器,会继承ClassLoader,并重写findClass()方法,在内部调用defineClass()去从一个二进制流中加载Class。那么在Android中,这个defineClass()方法去调用VMClassLoader的defineClass本地静态方法,而这个方法内部除了抛出了异常“UnsupportedOperationException”还有一些属性值,其他什么都没做。在博客开头写的链接中有源码,这就不贴了。
那么在Dalvik虚拟中,动态加载类就需要另外由ClassLoader派生出的两个类:DexClassLoader和PathClassLoader。

这两个类重载了ClassLoader的findClass()方法,并没有重写loadClass()方法,所以这两个类加载器符合双亲委派模型。

在介绍这两个类加载器区别之前,说明两点
(1)Dalvik虚拟机识别的是dex文件,而不是class文件,因此,我们加载的是dex文件,或者包含dex文件的apk文件或jar文件。
(2)DexFile类。上述两个类加载器都是通过DexFile这个类去加载类。

区别: PathClassLoader不能主动从zip包中释放出dex,所以只支持直接操作dex格式文件,或者已经安装的apk(已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
下面会有加载的demo。

加载好类以后,我们可以通过反射来使用加载好的类,但过多的使用反射会有一定的性能开销,代码复杂凌乱。我们还有一种方式,即接口。我们可以将一些方法提取出来作为一个接口,将待加载的类实现这个接口,在写待加载的类时,注意要有一个参数为空的构造函数,我们在主代码中就能将类对象强制转为接口对象,直接调用成员方法。

三、Demo

这个Demo是Android中的动态加载机制里的,为了展示,简略了一下,并且由于android系统的变更,其中有一些小坑,我们需要改一下代码。

1.接口与待加载类的实现与处理


这里写图片描述
  • 接口代码

public interface IDynamic {
void init(Activity var1);
//弹出消息
void showSomething();
void destory();
}

  • 待加载类
jianshu2.PNG
  • 接口的处理
    将接口导出一个jar包(由于AS导包得修改Gradle,嫌麻烦加上电脑上有Eclipse for Android ,我用它打的包):


    这里写图片描述

    然后将接口的jar包放入AS工程里的libs目录下。

  • 待加载类的处理
    将待加载类按上述方式打包,将打好的jar包复制到SDK的build-tools目录下,打开命令行,进入build-tools目录,输入:
    dx –dex –output dynamic1.apk dynamic.jar

    注意:上面dex和output前是两个‘-’ ,csdn显示的是一个,只不过长度长一点,坑了我一会。 如下图:
    这里写图片描述

    经过上述步骤,build-tools目录下就会生成一个名为dynamic1.apk的文件,那么上述步骤究竟做了什么?

我们知道DexClassLoader和PathClassLoader这两个类加载器加载的并不是class文件,而是将class文件优化后的dex文件,上述步骤便是将jar包解压,将其中的class文件优化成dex文件,然后压缩为apk文件。

  • 目标类的实现与处理
3.PNG
4.PNG
  • AndroidManifest的处理

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.dynamicloading"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter> <action android:name="android.intent.action.MAIN"/>
//注意,和目标类中相对应
<action android:name="com.example.impl"/>
<category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
</activity>
</application>
</manifest>

  • 将dynamic1.apk放入相应目录

好了,处理的差不多了,该处理我们在待加载类处理那一步中生成的dynamic1.apk了。因为用DexClassLoader按照Android中的动态加载机制中的方法我并没有成功,因为博主是将jar包放进SD卡中,现在好像并不支持这样做,所以这次我打算用PathClassLoader,看了上面介绍的小伙伴已经知道,PathClassLoader只能识别dex文件和已经安装好的apk文件(安装好的会有相应的缓存dex文件),那么我们接下来就要想办法把咱们生成的apk文件放到一个目录下,放在哪个目录下呢?

一开始我也不清楚,不过我将下面这个属性用Toast显示了一下,便得到了目录 String apkPath = actInfo.applicationInfo.sourceDir;
要放的目录是: /data/app/com.example.administrator.dynamicloadi ng-1

这里需要说明一点,如果你用的是真机的话,可能需要root权限才能进去这些目录,我用的模拟器,不需要权限。

还有一个坑,这个项目需要先运行一下,app目录下才会生成com.example.administrator.dynamicloading-1这个文件(当然你生成的不一定是这个,取决于的你的包名)

注意:下面的一些命令都有一个前提,需要开启模拟器。或者连着真机。

项目运行完后,你可以使用以下命令看看有没有相应包名的文件:

这里写图片描述

adb shell进入模拟器,然后cd /data/app进入app目录,然后ls命令查看一下该目录中都有哪些文件。 我要进入的目录如下(因为上面那个属性Toast显示的是它):
这里写图片描述

好了,那么用命令行将apk文件放入这个目录,先使用exit命令退出模拟器。
接下来,用命令将dynamic1.apk放入这个目录 命令如下: adb push apk所在目录 要放入的目录
例如我的apk在刚才的build-tools目录下,要放进/data/app/com.example.administrator.dynamicloading-1,所以命令如下图:

这里写图片描述

如果想查看有没有复制成功,可以按照上述的方法检查一下,adb shell ,然后进入这个目录,ls一下。

好了,运行项目,看看结果:
这里写图片描述

成功加载了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容