用于过滤aar中冲突类(class)和so库的脚本,也可以用来过滤jar中冲突class

android 同时被 3 个专栏收录
42 篇文章 0 订阅
15 篇文章 0 订阅
1 篇文章 0 订阅

需求产生的原因,有时候我们接入三方包的时候,会出现类冲突,这个时候我们就想能不能把三方包中的冲突类过滤掉,不参与编译。网上百度,google都没有找到一个好的解决方案,然后自己动手丰衣足食。

灵感来源:Configuring Multi-Project Builds,创建 Android 库

AAR库的Class和SO文件过滤

将aar库导入项目的方式有2种:

1,

android{
 repositories {
        flatDir {
            dirs 'libs'
        }
    }
}

implementation"(name: path, ext: 'aar')

2,

  • 添加已编译的 AAR(或 JAR)文件:
    1. 点击 File > New Module。
    2. 依次点击 Import .JAR/.AAR Package 和 Next。
    3. 输入 AAR 或 JAR 文件的位置,然后点击 Finish。
  • 将库模块导入到您的项目中:
    1. 点击 File > New > Import Module。
    2. 输入库模块目录的位置,然后点击 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')
}

gitHub地址:这里

  • 4
    点赞
  • 3
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值