需求产生的原因,有时候我们接入三方包的时候,会出现类冲突,这个时候我们就想能不能把三方包中的冲突类过滤掉,不参与编译。网上百度,google都没有找到一个好的解决方案,然后自己动手丰衣足食。
AAR库的Class和SO文件过滤
将aar库导入项目的方式有2种:
1,
android{
repositories {
flatDir {
dirs 'libs'
}
}
}
implementation"(name: path, ext: 'aar')
2,
- 添加已编译的 AAR(或 JAR)文件:
- 点击 File > New Module。
- 依次点击 Import .JAR/.AAR Package 和 Next。
- 输入 AAR 或 JAR 文件的位置,然后点击 Finish。
- 将库模块导入到您的项目中:
- 点击 File > New > Import Module。
- 输入库模块目录的位置,然后点击 Finish。
这里我们采用第二种方法将aar包(后续的jar包也一样)导入我们的项目中,因为我希望有一个独立的module和相应的Gradle脚本文件来完成过滤class文件脚本的编写。我这里已经将想要过滤的aar导入工程了:
其实就是一个简单的module,打开它的build.gradle文件看一下:
简单解释一下这2个方法:
NamedDomainObjectContainer#maybeCreate(String)
/**
* Looks for an item with the given name, creating and adding it to this container if it does not exist.
*
* @param name The name to find or assign to the created object
* @return The found or created object. Never null.
*/
T maybeCreate(String name);
简单翻译就是:查找具有给定名称的项目,如果该项目不存在,则创建它并将其添加到此容器中。
此容器是那个容器?
根据代码我们可以看到是:ConfigurationContainer
此项目是什么什么?
根据代码我们可以看到是:Configuration
ArtifactHandler#add(String,Object)
/**
* Adds an artifact to the given configuration.
*
* @param configurationName The name of the configuration.
* @param artifactNotation The artifact notation, in one of the notations described above.
* @return The artifact.
*/
PublishArtifact add(String configurationName, Object artifactNotation);
简单翻译就是:给给定的配置(Configuratin)添加一个工件(Artifact)。
该方法最终调用的是:
private PublishArtifact pushArtifact(Configuration configuration, Object notation, Action<? super ConfigurablePublishArtifact> configureAction) {
ConfigurablePublishArtifact publishArtifact = publishArtifactFactory.parseNotation(notation);
configuration.getArtifacts().add(publishArtifact);
configureAction.execute(publishArtifact);
return publishArtifact;
}
可以看到通过artifactNotation生成了一个publishArtifact并添加到名为configurationName的Configuration中了。
那么我们如何引用这个aar的module依赖呢?
其实就简单的工程依赖就可以了:implementation project(':xxx-2.2.2')。
注:默认情况下,当你声明依赖于projectA时,你实际上声明依赖于projectA的'default'配置。如果您需要依赖projectA的特定配置,需要这样:
configurationName project(path: ':projectA', configuration: 'someOtherConfiguration')
所以上面也可以这样写:implementation project(path:':xxx-2.2.2',configuration:'default')
那我是不是可以仿照上面的,自己写一个特定的配置,然后依赖这个特定的配置?
我的思路是这样的:
1,获取到需要过滤的原始AAR包
2,解压AAR包
3,解压AAR包中所包含的jar
4,删除解压AAR包中包含的jar
5,按照过滤规则对解压之后的class文件按重新打包(AAR的class文件过滤在这里实现的)
6,按照过滤规则重新打包成AAR包(AAR的SO文件过滤在这里实现的)
7,创建一个新的Configuration并且添加一个Artifact
8,在工程中引用过滤后的AAR
其实思路很简单啦~(*^__^*)
① 获取到需要过滤的原始AAR包
def getDefaultAar() {
//获取到名称为default的Configuration
Configuration c = configurations.getByName("default")
//在Configuration中通过PublishArtifactSet获取到对应的文件
def files = c.artifacts.files.filter {
it.name ==~ /.*\.aar/
}
def file = null
if (!files.empty) {
file = files[0]
}
return file
}
简单的解释已经体现在注释上面了
② 解压AAR包
/**
* 解压getDefaultAar()返回的aar包
*/
task unZipAar(type: Copy) {
from zipTree(getDefaultAar())
into unZipAarFile
//完成aar解压之后,设置unzipJar的from和deleteJars的delete
doLast {
Set<File> jarFiles = new HashSet<>()
if (unZipAarFile.exists()) {
unZipAarFile.traverse(type: groovy.io.FileType.FILES, nameFilter: ~/.*\.jar/) { file ->
jarFiles.add(file)
}
}
unzipJar.from(
jarFiles.collect {
zipTree(it)
}
)
deleteJars.delete(jarFiles)
}
}
③ 解压AAR包中所包含的jar
/**
* 注意:from 已经在上面的unZipAar设置了
* 解压aar包中包含的jar包
*/
task unzipJar(type: Copy) {
into unZipJarFile
}
④ 删除解压之后的jars
/**
*
* 注意:from 已经在上面的unZipAar设置了
* 删除解压之后的jars
*/
task deleteJars(type: Delete)
⑤ 按照过滤规则对解压的class.jar重新打包(这个是重点)
/**
* 用Jar任务过滤并生成新的jar包
*/
task zipJar(type: Jar) {
baseName = 'classes'
from unZipJarFile
destinationDir unZipAarFile
exclude getExcludePackageRegex(excludePackages)
exclude getExcludeClassRegex(excludeClasses)
}
⑥ 重新打包成AAR包
/**
* 重新把文件压缩成新的aar包
*/
task excludeAar(type: Zip) {
group 'Siy'
description '生成一个过滤之后的aar包'
baseName excludeAarName
extension "aar"
from unZipAarFile
exclude getExcludeSoRegex(excludeSos)
destinationDir excludeAarFile
}
⑦ 创建一个新的Configuration并且添加一个Artifact
configurations.maybeCreate("exclude")
artifacts.add("exclude",excludeAar)
⑧ 在工程中引用过滤后的AAR
implementation project(path: ':xxx_aar', configuration: 'exclude')
附:
//需要过滤的包名
String[] excludePackages = ['com.baidu']
//需要过滤的类(需要全类名,不需要.class结尾)
String[] excludeClasses = []
//需要过滤的so
String[] excludeSos = ['liblocSDK7b']
static String[] getExcludePackageRegex(String[] packages) {
packages?.collect {
it?.replace('.', '\\')?.plus("\\**")
}
}
static String[] getExcludeClassRegex(String[] classes) {
classes?.collect {
it?.replace('.', '\\')?.plus(".class")
}
}
static String[] getExcludeSoRegex(String[] sos) {
sos?.collect {
"**\\${it}.so"
}
}
OK!我们看看效果:
那么我的过滤配置需要这样写:
//需要过滤的包名
String[] excludePackages = ["com.payeco.android.plugin.ui.datepick","com.payeco.android.plugin.ui.view"]
//需要过滤的类(需要全类名,不需要.class结尾)
String[] excludeClasses = ["com.payeco.android.plugin.payin.PayecoPluginPayIn"]
//需要过滤的so
String[] excludeSos = []
过滤后的效果:
很明显通过依赖到External Libraries的AAR已经没有datepick,view这个2个包也没有PayecoPluginPayIn这个类了。
其实写到这里,AAR过滤指定package和class已经实现了。这里我再提几个小知识点:
1,不是任何类型都可以做artifactNotation
从这个错误图里面可以看出它支持哪些类型。
2,我们可以这样创建一个新的Configuration并且添加一个Artifact
configurations {
exclude
}
artifacts {
exclude excludeAar
}
3,为了解耦我们可以把这些过滤脚本写在一个单独的Gradle文件中,如上面:excludeAar.gradle
引用是在build.gradle中加上
apply from: "${project.projectDir.absoluteFile}\\excludeAar.gradle"
这里附上完整的excludeAar.gradle代码:excludeAar.gradle
Jar库的Class文件过滤
Jar库的实现思路和AAR库是一样的,比AAR还要简单
1,获取到需要过滤的原始JAR包
2,解压JAR包
3,按照过滤规则对解压的class文件重新打包
4,创建一个新的Configuration并且添加一个Artifact
5,在工程中引用过滤后的JAR
① 获取到需要过滤的原始JAR包
def getDefaultJar() {
Configuration c = configurations.getByName("default")
def files = c.artifacts.files.filter {
it.name ==~ /.*\.jar/
}
def file = null
if (!files.empty) {
file = files[0]
}
return file
}
② 解压JAR包
/**
* 解压getDefaultJar()返回的jar文件
*/
task unzipJar(type: Copy) {
def zipFile = getDefaultJar()
def outputDir = unZipJarFile
from zipTree(zipFile)
into outputDir
}
③ 按照过滤规则对解压的JAR重新打包
/**
* 用Jar任务过滤并生成新的jar包
*/
task excludeJar(type: Jar) {
group 'Siy'
description '生成一个过滤之后的jar包'
//需要打包的资源所在的路径集和
from unZipJarFile
//去除路径集下部分的资源
exclude getExcludePackageRegex(excludePackages)
exclude getExcludeClassRegex(excludeClasses)
//整理输出的 Jar 文件后缀名
extension = "jar"
//最终的 Jar 文件名......如果没设置,默认为 [baseName]-[appendix]-[version]-[classifier].[extension]
baseName = excludeJarName
//生成jar包的路径
destinationDir excludeJarFile
}
④ 创建一个新的Configuration并且添加一个Artifact
configurations.maybeCreate("siy_test")
artifacts.add("siy_test", zipJar)
⑤ 在工程中引用过滤后的JAR
implementation project(path: ':test_jar_exclude', configuration: 'siy_test')
附:
//需要过滤的包名
def excludePackages = ['com.baidu']
//需要过滤的类(需要全类名)
def excludeClasses = []
static def getExcludePackageRegex(def packages) {
packages?.collect {
it?.replace('.', '\\')?.plus("\\**")
}
}
static def getExcludeClassRegex(def classes) {
classes?.collect {
it?.replace('.', '\\')?.plus(".class")
}
}
这里附上完整的excludeJar.gradle代码:excludeJar.gradle
上面说的那么多都是用来解释原理,这里给出一个简单的使用方法。
JCenter地址:
classpath 'coder.siy:exclude-dependencies-plugin:1.0.0'
使用方法
apply plugin: 'exclude_plugin'
excludePluginExt {
autoDependencies = true //是否自动依赖即是否依赖过滤之后的架包
aars {
BaiduLBS_Android_debug { //过滤架包的名称
path "/libs/exclude/BaiduLBS_Android_debug.aar" //架包的路径
excludePackages 'com.baidu.location' //过滤的包名
}
}
jars{
BaiduLBS_Android_7_5_2{//过滤架包的名称
path "/libs/exclude/BaiduLBS_Android_7.5.2.jar" //架包的路径
excludePackages 'com.baidu.android','com.baidu.lbsapi' //过滤的包名
}
map_baidu{//过滤架包的名称
path "/libs/exclude/map-baidu.jar"//架包的路径
excludePackages "io.dcloud.js.map.adapter"//过滤的包名
excludeClasses "io.dcloud.js.map.IFMapDispose","io.dcloud.js.map.JsMapCircle","io.dcloud.js.map.MapJsUtil"//过滤的类名
}
}
}
注意:
配置完之后运行一下根据名称生成的过滤任务
属性解释:
属性名 | 默认值 | 解释 |
path | 路径 | 无默认值(必要值) |
excludePackages | 空数组 | 需要过滤的包名 |
excludeClasses | 空数组 | 需要过滤的类名(全类名,不要".class"结尾) |
excludeSos | 空数组 | 需要过滤的so名(不要".so"结尾,aar包特有) |
番外:如果你认真看了上面的文章,提一个问题:apply plugin: 'com.android.application' 的工程可以被依赖么。
答案:可以的
从上面知道 implementation project(':app')就相当于implementation project(path: ':app', configuration: 'default')。
apply plugin: 'com.android.application' 工程 default 下面没有任何文件。
Configuration c = project.configurations.getByName("default")
c.artifacts.files.each {
println(it.name)
}
所以没办法引用,如果我们这样改一下,application也是可以依赖的。
configurations.maybeCreate("test")
artifacts.add("test", file('BaiduLBS_Android_release.aar'))
dependencies {
implementation project(path: ':app',configuration:'test')
}