- 动态加载测试实现(这里仅仅是一个示例)
*/
public class ShowMessageImpl_one implements IMessage_one {
@Override
public String showMessage(Context context) {
Toast.makeText(context, “dexlibrary1生成的dex文件已加载”, Toast.LENGTH_LONG).show();
return “大家好才是真的好”;
}
}
编译生成Jar及进行转化
OK,接下来要做的就是将dexlibrary1打包为jar了,这里需要注意将哪些文件进行打包,从哪里获取到需要打包的文件呢?如下图所示:
即:对应module或project目录下—>build—>intermediates—>classes—>debug下(如果没有classes目录,需要先进行编译<bulid或Make Projrect>),如下图所示:
然后我们通过在build.gradle中定义task任务进行构建,构建的方式有多种,不过都大同小异,例如:
//删除jar包任务
task clearJar(type: Delete) {
delete ‘build/libs/dexlibrary1.jar’
}
task makeJar(type: Jar) {
//指定生成的jar名
baseName ‘dexlibrary1’
//archiveName = ‘dexlibrary1’ //这样指定名称也可以
//从哪里打包class文件
from(‘build/intermediates/classes/debug/org/gaochun/dexlibrary1/’)
//将assets目录打入jar包
//from fileTree(dir: ‘src/main’, includes: [‘assets/**’])
//打包到jar后的目录结构
into(‘org/gaochun/dexlibrary1/’)
//去掉不需要打包的目录和文件
exclude(‘test/’, ‘BuildConfig.class’, ‘R.class’)
//去掉R$开头的文件
exclude { it.name.startsWith(‘R$’) }
}
makeJar.dependsOn(clearJar, build)
代码中注释也描述的比较清晰了,这个涉及到Groovy脚本相关的一些知识点,感兴趣的同学可以去网上搜索资料进行学习,个人也推荐一个不错的资料:**Gradle学习系列 ,**这里也不多说我们继续,上面的task定义好之后就可以进行打包dex了,方式有两种,一种是在AS的Terminal输入命令:gradlew makeJar,另外一种就是在AS工具右边的Gradle中找到上面定义的Task,双击该任务就可以了,dexlibrary1—>other—>makeJar,如图:
任务执行完成之后,我们会发现在 build 目录下生成了一个jar包,但这个包并非我们所需要的,前面有提到,我们需要对其进行再次编译,生成对应的包含dex的jar,如下图所示:
接下来我们对该jar包进行编译转化,转化需要用到dx命令,在 android-sdk\build-tools\对应的sdk版本[如:27.0.0] 下,所以需要将该jar拷贝到对应目录下,在Android Studio的Terminal终端cd到对应的sdk版本目录(例如我们将jar拷贝到了27.0.0的目录下,此时就需要在终端将路径切到对应的目录下),然后输入编译命令:
dx --dex --output=dexlibrary1_dex.jar dexlibrary1.jar
(注:–output是输出目录,默认在当前的根目录下,执行完后在当前目录下生成了Davilk虚拟机可执行的dex文件,
因为该命令同时会打包dex文件,因此后缀是jar,可使用WinRaR即可看到里面的class.dex文件)
以上操作完成后,我们可以拿到命令执行完后所生成的 dexlibrary1_dex.jar 文件进行动态加载了。
动态加载dex
关于动态加载技术相信移动端的小伙伴或多或少都有了解,毕竟Android中当初火热流行的热更新其原理就基于此。Java程序中JVM虚拟机通过类加载器ClassLoader来加载class文件和jar文件(本质还是class文件)。Android与Java类似,只不过Android使用的是Dalvik/ART虚拟机,加载的是dex文件(可以理解为一种对class文件优化的产物),Android中类加载器分为两种类型,一种是系统 ClassLoader 另一种是自定义 ClassLoader ,其中系统ClassLoader包括三种,分别是 BootClassLoader 、PathClassLoader 和 DexClassLoader,如下图所示:
根据图中所示,我们大致可以总结下 ClassLoader 继承关系,我们大致说下其原理就行,更深入的研究还请小伙伴们自己去探索:
-
ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;
-
BootClassLoader是ClassLoader的内部类,用于预加载preload()常用类以及一些系统Framework层级需要的类;
-
BaseDexClassLoader继承ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它;
-
PathClassLoader加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件;
-
DexClassLoader可以加载自定义的dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载。
所以我们这里需要用到的就是 DexClassLoader 类,对比 PathClassLoader ,DexClassLoader 的不同点是它可以加载任意目录下的 jar | dex | apk | zip 文件,比PathClassLoader更加灵活,是实现热修复和插件化技术的重点,划重点,下次要考,源码如下图所示:
/**
-
DexClassLoader类参数含义
-
@param dexPath 待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限
-
@param optimizedDirectory 解压后的.dex文件存储路径,不可为空,此位置一定要是可读写且仅该应用可读写
-
@param librarySearchPath 指向包含本地库(so)的文件夹路径,可以设为null
-
@param parent 父级类加载器,一般可以通过Context.getClassLoader获取到,也可通过ClassLoader.getSystemClassLoader()获取到
*/
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)
**注:**4.1以后不能够将第二个参数 optimizedDirectory 设置到sd卡目录, 否则抛出异常,强烈建议使用内部私有存储路径(即应用的data/data/xx包名/下面创建一个app_dex文件夹),不要放到sdcard上,代码容易被注入攻击。
下面我们将编译好的含有dex文件的 dexlibrary1_dex.jar 文件放到app下的assets目录下,当然也可以通过其他手段进行加载,例如放到服务器上Download下来 等等,下面演示通过放置到assets目录进行加载:
/**
- 加载dex文件中的class,并调用其中的showMessage方法
*/
private void loadDexClass() {
File dexOutputDir = getDir(“dex”, 0);//在data/data/xx包名/下面创建一个app_dex文件夹
String internalPath = dexOutputDir.getAbsolutePath() + File.separator + “dexlibrary1_dex.jar”;
File dexFile = new File(internalPath);
try {
if (!dexFile.exists()) {
dexFile.createNewFile();
//将assets目录下的文件copy到app/data/cache目录
FileUtils.copyFiles(this, “dexlibrary1_dex.jar”, dexFile);
}
} catch (IOException e) {
e.printStackTrace();
}
//加载dex class
DexClassLoader dexClassLoader = new DexClassLoader(internalPath, dexOutputDir.getAbsolutePath(), null, getClassLoader());
try {
//该name就是internalPath路径下的dex文件里面的ShowMessageImpl_one这个类的包名+类名
Class<?> clz = dexClassLoader.loadClass(“org.gaochun.dexlibrary1.ShowMessageImpl_one”);
IMessage_one impl = (IMessage_one) clz.newInstance();//通过该方法得到IMessage_one类
if (impl != null) {
String value = impl.showMessage(this);//调用打开弹窗并获取值
mTextView.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
划重点:Class<?> clz = dexClassLoader.loadClass(“org.gaochun.dexlibrary1.ShowMessageImpl_one”); 这个loadClass的包名必须保持一致,即app下的包名和 dexlibrary1 组件下的包名必须保持一致,不然会出现java.lang.ClassCastException或ClassNotFoundException 等错误,所以需要保持一致,如下图所示:
这里给个这样的建议,定义了一个Common的基类Module,里面存放各种interface接口文件,然后剥离出来的组件引用了Common且都implements了对应的接口,宿主app也同样引用了Common,这样在宿主app中加载dex包时就不会出现上面转换错误或者找不到类的错误了,也让项目变得更加清晰一些,画个粗糙的图吧,绿色箭头表示依赖,红色箭头表示对打包好的dex进行加载,大致是这么个意思:
ok,加载成功前后的效果图:
到此我们知识点和功能也都基本完善了,按照上面的操作流程,Demo也能正常的运行起来,用着用着,因为项目的需求,独立出来的module越来越多,每个module的build.gradle文件中都有一大坨clearJar、makeJar的任务代码,看着有些碍眼,这是其一,其二就是每次都需要将编译好的jar拷贝到指定目录通过命令再生成包含dex的jar,这重复机械性的工作做多了也是有点头皮发麻,所以针对这个下面做了一些优化。
优化编译脚本
优化的目的总结下来有以下几点:
① Module统一版本管理
② 将clearJar/makeJar等任务抽离开,不要在每个module中都写一大堆
③ 通过自定义的Task一键生成包含class.dex的jar,省去手动编译重复性的工作
④ 上传到Git后确保让每个协同开发的小伙伴也能直接执行task任务进行编译,无需修改其他配置
下面分别来简单进行说明:
一、Module统一版本管理
首先可以在我们在项目的根目录创建一个 versionConfig.gradle 文件,该文件中定义的内容只做版本相关的定义和配置(也可以在根目录的build.gradle目录定义),例如:
ext {
versions = [
sdkMinVersion : 14,
sdkTargetVersion : 27,
sdkCompileSdkVersion: 27
//其他…
]
depVersion = [
appCompatVersion : “27.1.1”,
recyclerViewVersion : “27.1.1”,
constraintLayoutVersion: “1.1.0”
]
deps = [
suport: [
appcompat : “com.android.support:appcompat-v7:${depVersion.appCompatVersion}”,
recyclerview : “com.android.support:recyclerview-v7:${depVersion.recyclerViewVersion}”,
constraint_layout: “com.android.support.constraint:constraint-layout:${depVersion.constraintLayoutVersion}”
]
]
}
注意由于各个module都需要引用到该配置信息,所以该文件需要在 根目录build.gradle中apply:
接下来在各个module中使用:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
一键领取:【Android超硬核面试资料】
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-uIz2Bkyx-1710816687175)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-dloG1DnG-1710816687176)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-uRRQOvpJ-1710816687176)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图,大家可以点击这里自行获取。