admin管理员组文章数量:1634983
-
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中使用:
apply plugin: ‘com.android.application’
android {
def versions = rootProject.ext.versions
compileSdkVersion versions.sdkCompileSdkVersion
defaultConfig {
minSdkVersion versions.sdkMinVersion
targetSdkVersion versions.sdkTargetVersion
versionCode 1
versionName “1.0”
applicationId “org.gaochun.dexlibrary”
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
}
}
dependencies {
def dependencies = rootProject.ext.deps
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation dependencies.suport.appcompat
implementation dependencies.suport.constraint_layout
//implementation ‘com.android.support.constraint:constraint-layout:1.1.0’
}
二、抽离clearJar/makeJar等编译任务并自定义Task任务执行编译dex任务
同样我们单独定义个文件:makeDexJar.gradle,将上面我们编译jar所定义的 clearJar/makeJar 任务放到这个文件中,如下图所示:
这个时候问题来了,如何引用这个文件;这个给多个module引用的打包编译任务有很多公共的属性,怎么封装成方法;另外生成包含class.dex的jar编译命令怎么写;下面是优化好的代码,根据注释可以清楚每一行代码的含义及作用,供大家参考:
//------------------------- 构建Jar和包含Dex的Jar ---------------------------------
ext {
readLocalSDKPropertiesToMakeDexJar = this.&readLocalSDKPropertiesToMakeDexJar
}
def readLocalSDKPropertiesToMakeDexJar(outputDexJarName, jarName, packagePath) {
//println(“我被调用了”)
//编译工具
//def buildingToolPath = ‘D:\Android\android-sdk\build-tools\28.0.0\dx.bat’
def dxbatVersion = ‘25.0.0’ //因为项目用的是25Level,所以此处用25.0.0的版本构建
def dxbat = ‘\build-tools\’ + dxbatVersion + ‘\dx.bat’
def buildingToolPath
//主要是为了读取local.properties文件中的sdk.dir路径,设置编译工具的位置
//这样其他成员拉取代码后打包就不用手动更改编译工具的路径了
File file = rootProject.file(‘local.properties’)
if (file.exists()) {
InputStream inputStream = rootProject.file(‘local.properties’).newDataInputStream();
Properties properties = new Properties()
properties.load(inputStream)
if (properties.containsKey(“sdk.dir”)) {
buildingToolPath = properties.getProperty(“sdk.dir”) + dxbat
}
}
//删除jar包任务
task clearJar(type: Delete) {
delete ‘build/libs/’ + jarName
}
//生成不带dex的jar
task makeJar(type: Jar) {
//baseName ‘SmartWebAPI’ //指定生成的jar名
archiveName = jarName //打包普通jar名称
from(‘build/intermediates/classes/debug/’ + packagePath) //从哪里打包class文件
into(packagePath) //打包到jar后的目录结构
exclude(‘test/’, ‘BuildConfig.class’, ‘R.class’) //去掉不需要打包的目录和文件
exclude { it.name.startsWith('RKaTeX parse error: Expected 'EOF', got '}' at position 4: ') }̲ //去掉R开头的文件
}
//执行makeJar任务时会在之前执行clearjar任务 和 build
makeJar.dependsOn(clearJar, build)
//执行此任务生成包含dex的jar
task makeDexJar(type: Exec) {
def mCommond = [
buildingToolPath, ‘–dex’,//输出包含dex的jar路径及名称
‘–output=build/libs/’ + outputDexJarName,
‘build/libs/’ + jarName //使用dx将jar中的代码优化成dex文件,该步骤也可以手动命令行完成
]
commandLine mCommond
}
//执行makeDexJar的时候会在之前执行makeJar
makeDexJar.dependsOn(makeJar)
}
上面代码中新增了一个task任务:task makeDexJar(type: Exec) ,这个任务就是将编译好的jar通过sdk中的编译工具再次打包为含有dex的jar包,这样就不用将jar拷贝到指定目录再手动用命令打包了。还有上面有一段去读取 local.properties 的操作,代码注释中有提到,主要是为了获取sdk下的编译工具路径,动态读取出来其他小伙伴也不用去单独修改这个文件的路径了,读取示例:
def readLocalProperties(){
File file = rootProject.file(‘local.properties’)
if(file.exists()){
InputStream inputStream = rootProject.file(‘local.properties’).newDataInputStream();
Properties properties = new Properties()
properties.load(inputStream)
if (properties.containsKey(“sdk.dir”)){
println properties.getProperty(“sdk.dir”)
}
}
}
三、多个Gradle文件中方法相互调用
这里要着重说明的是这一段代码:
ext {
readLocalSDKPropertiesToMakeDexJar = this.&readLocalSDKPropertiesToMakeDexJar
}
gradle提供了ext,所以我们可以很容易获取其他gradle的属性,例如 2.gradle 需要调用 1.gradle 文件中的方法,这个时候就需要像上面的写法一样,注意左右的方法名字是一样,this 后面多了一个 & 符号,其他Gradle文件如果想调用这个方法,一般可以这样:
def outputDexJarName = ‘Smart24Decode_dex.jar’
def jarName = ‘Smart24Decode.jar’
def packagePath = ‘com/ccn/Smart24Decode/’
//直接调用
readLocalSDKPropertiesToMakeDexJar(outputDexJarName, jarName, packagePath)
或者通过task调用:
task CustomTask << {
def outputDexJarName = ‘Smart24Decode_dex.jar’
def jarName = ‘Smart24Decode.jar’
def packagePath = ‘com/ccn/Smart24Decode/’
readLocalSDKPropertiesToMakeDexJar(outputDexJarName, jarName, packagePath)
}
CustomTask << ,是gradle的语法,如果不加 << 的话,每次编译时都会执行这个task,加了**<<** ,只有执行这个task的时候才会执行里面的代码。包括后面如果有用到构建打包自动上传到服务器或者第三方的蒲公英平台,也会运用到类似的方式。
OK 下面我们在 dexlibrary1 的build.gradle引用并调用打包方法,传递相关参数:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。
最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
[外链图片转存中…(img-x2aKSoZ9-1713783904794)]
最后
一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
[外链图片转存中…(img-DT7mHFuk-1713783904796)]
如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。
最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
版权声明:本文标题:Android一键生成包含 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1729203452a1189819.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论