Gradle自定义task打包AAR和APK

工作中,几乎总会遇到多样化打包的场景,本篇文章主要内容分为:

  1. 分析AndroidStudio打包一个AAR SDK(APK)的流程原理
  2. 如何自定义task来任意干预系统打包流程来实现多样化需求
AS如何打包

    我们想要打包一个APK,一般有两种 方式,第一种是直接使用AS的图形化界面来操作。

image.png

    第二种是我们运行gradle脚本来输出APK或者SDK。

    脚本指令为 assembleRelease,其中release为我们在gradle中定义的buildType,本篇文章就以release为例子来讲解说明。其实对于第一种方法,本质也是运行该指令,只不过AS存在友好交互界面。

  • 如果我们在一个类型为library的module中,运行 该指令,那么会最终生成AAR包。 

    image (1).png

  • 如果我们在一个类型为application的module中,运行该指令,那么最终会生成APK。 

    image (4).png

         运行指令的方式也很简单,如果我们想要生成AAR包,那么就可以在控制台里,在指令assembleRelease的前方加上module名称,然后运行。

image.png

上图示例演示如何运行打包生成AAR,其中我的module名称为SDK,所以我最后的指令为 :SDk:assembleRelease 回车运行。

然后就可以在build目录下看到AAR SDK。

image (5).png

同理,如果我们在APP的gradle中运行指令,那么会生成对应apk.

image (6).png

可以在APP module中找到对应的APK。

image (7).png

详解assembleRelease

我们可能会疑惑,我们怎么知道要执行指令 assembleRelease呢?

我们来看下打包指令的组成:

  • buildType

首先assemble是固定的,但是后边的结构是动态的。 以打包AAR为例,我们到modulegradle文件中,可以看到存在buildType,我们可以扩展定义我们自己的buildType,也可以使用默认的ReleaseDebug,如果我们想打包Release包,那么指令就是 assembleRelease,同理打包Debug包就是 assembleDebug 。

我们可能会定义我们自己的buildType,例如图中定义了test,那么运行指令则为 assembleTest

image (8).png

buildType的含义是指我们想要构建的SDK类型,一般常见的是releasedebugdev,当然也可以根据我们的需要额外定义我们自己的。

  • productFlavors

除了打包类型,可能我们还会存在多个产品,每个产品又可以有不同的类型包,所以这个时候只是一个buildType就不适用了,这就又出了一个定义productFlavors,我们可以定义来更多样化操作打包,包括多样化定义包名等。

如果我们定义了 productFlavors ,例如,如图定义了normal,那么我们打包指令就为 assembleNormal

image (9).png

  • 同时定义buildType和productFlavors

往往我们的项目中都会同时定义这两个类型,那么这个时候指令怎么执行代表什么含义呢?以下说明中,默认buildTyperelease和debugproductFlavorsnormallow

  1. assembleNormalRelease

这个指令加上了 productFlavorsbuildType,注意buildType要在productFlavors之后。这个指令会使用指定的buildTypeproductFlavors里的配置,然后生成对应的AAR或者APK。这里只会生成一个AAR或者APK包。

image (10).png

  1. assembleNormal

注意这里的Normal是我们定义的 productFlavors。如果我们执行上述命令,那么他会为每个buildType生成一个normal的APK或AAR。

image (11).png

  1. assembleRelease

这个指令含义和assembleNormal类似,只不过这个是为当前选择的这个BuildType,使用其里边声明定义的配置,为所有的 productFlavors 生成对应的APK或AAR。

image (12).png

注意的是 2和3 这两条指令,生成的最后APK/AAR的位置还是有些许区别。按照存在的意义,productFlavors下存在多个buildType,所以 productFlavors 为父目录,其下根据不同buildType生成子目录。

  1. assemble

如果我们直接运行这个指令,没有任何后缀,那么代表的是运行所有buildType下的所有productFlavors,分别生成一个APK或AAR,假设我们有2个buildType,3个productFlavors,那么就一共会生成2x3=6个APK或AAR。分别对应不同的配置。

image (13).png

AS打包原理

如果我们在gradle中执行指令 assembleNormalRelease,那么会发现控制台打印很多如下所示日志。没错,这些都是打包过程中执行的task。

所谓的打包APK/AAR,其实就是通过执行AS已经预制好的一系列task脚本,将APK/AAR所需要的资源和文件根据规则放到指定位置,然后压缩为APK/AAR格式。

image (14).png

我们不需要太多详细了解每一个task的作用,为了可以自定义打包,我们只需要知道一些关键task的执行顺序和作用,以便我们来控制流程。

本文接下来会详细分析打包AAR的原理。

打包AAR原理

一个AAR的组成可以分为几部分:

  1. class.jar文件
  2. so文件
  3. 资源文件
  4. R文件
  5. manifest文件
  6. 混淆文件

image (15).png

  • 关于AAR中没发现混淆文件

如果我们发现打包后的AAR里没有混淆文件,那也不用慌,这是正常的,混淆文件需要特殊配置一下,才可以打包到我们的AAR中。

image (16).png

在AAR module中,图片对应位置中,添加如下配置,配置项加上你的混淆文件就可以了,可以加多个混淆文件,打包时候会自动merge为一个文件。

 

arduino

复制代码

consumerProguardFiles 'aar-proguard-rules.pro'

task执行顺序

task执行顺序 (1) (1).png

插入我们自己的task自定义打包

上图图示为打包AAR过程中,所经历的一系列task任务,以及执行顺序。如果我们想要修改其中的某一步,只需要定义我们自己的task对build目录下的文件做修改,将task插入到目标动作task中间,即可完成功能。

以下列举多个例子来演示一下。

实际应用例子
1. 根据不同的buildType,自动打包出包含不同类型SO的AAR

需求:我们存在buildType类型为elease1release2,我们存在arm64armeabi-v7aarmeabi三种类型的SO。我们想要打包出的release1只包含arm64的so,release2 包含所有的so。

先看下最终想要的效果:

image (17).png

想要实现这种效果,我们就需要自定义task了。

对比上边发的那个task执行顺序图,我们知道想要修改AAR中的SO,可以定义task 插入到系统task mergeRelease1JniLibFoldersmergeRelease1NativeLibs之间执行即可。

首先找到我们当前SDK代码的module,笔者这里这个SDK代码的module名字就叫做SDK。

image (18).png

找到该module下的build.gradle,我们在gradle对应位置下,编写如下代码。

image (19).png

文本:

variantFilter {
        String buildTypeName = it.buildType.name
        if ("release1" == buildTypeName) {
            tasks.register("deleteUnusedSOForRelease1", {
                doLast {
                    delete("build\\intermediates\\merged_jni_libs\\release1\\out\\armeabi")
                    delete("build\\intermediates\\merged_jni_libs\\release1\\out\\armeabi-v7a")
                }
            })
        }
    }

    variantFilter会遍历当前module下的所有buildTypeproductFlavors,我们呢只需要对relese1进行删除SO的处理,所以在这里呢过滤release1buildType,然后为他注册一个task,task里做的操作就是删除build对应目录下的so。

    现在删除SO的task制作好了,我们怎么才能让他在合适的时机去自动运行呢?毕竟我们打包指令和方法不想改,仍然想通过执行指令assemble来执行打包。

我们找到工程根目录,工程根目录也会存在build.gradle,来到这个文件,我们到文件最末尾,编写如下代码。注意编写完成后,要sync一下,每次修改完gradle都需要SYNC一下当前项目。

image (20).png

文本:

Task current = tasks.findByPath(":SDK:deleteUnusedSOForRelease1")
Task firstTask = tasks.findByPath(":SDK:mergeRelease1JniLibFolders")
Task nextTask = tasks.findByPath(":SDK:mergeRelease1NativeLibs")
nextTask.dependsOn(current)
current.mustRunAfter(firstTask)
println("finish ===> ")

    这段代码含义是,将我们刚才为release1注册的task deleteUnusedSOForRelease1 设置了编译顺序,让其在系统task mergeRelease1JniLibFolders执行后,在mergeRelease1NativeLibs执行前执行。

注意这里直接写到gradle中,是因为在gradle编译时候,就要将gradle执行顺序定义好。

接着我们运行一下指令assembleRelease1进行打包,看输出:

image (21).png

会看到,合适的时机正常的执行了我们的task,删除了SO,看下生成的AAR,确实没有多余So了~

image (22).png

2. 根据不同的buildType 修改AAR中的manifest文件内容

我们现在有个需求,想要让打包好的AAR中的manifest不指定targetSDKVersionminSDKVersion。毕竟这两个会影响到集成方。

按照正常的打包配置是没法做到删除这两个东东的,那么我们只能自定义task来修改manifest文件了。

image (23).png

老规矩,看下最开始理的那个task执行顺序,发现执行完task processRelease1Manifest 后,会将最终的manifest放到build下,那么我们定义的task就要放到这个task之后。

然后我们看到这个task之后对应的是 extractReleaseAnnotations,那么我们将我们自定义修改manifest内容的task放到这两个task之间即可。

  • 我们一步一步来实现这个需求
  1. 先自定义好我们的task来删除manifest中的内容

参考流程图,可以找到manifestbuild目录下的位置,我们自定义如下task,将对应内容移除。

String firstChar = buildTypeName.charAt(0).toUpperCase()
        String formatName = firstChar + buildTypeName.substring(1)

        Task manifestTask = tasks.findByPath("change${formatName}ManifestContentTask")
        if (manifestTask == null) {
            tasks.register("change${formatName}ManifestContentTask", {
                doLast {
                    File manifestFile = file("build/intermediates/library_manifest/${buildTypeName}/AndroidManifest.xml")
                    def manifestContent = manifestFile.text
                    manifestContent = manifestContent.replaceAll("android:minSdkVersion=\"${project.MIN_SDK_VERSION}\"", ' ')
                    manifestContent = manifestContent.replaceAll("android:targetSdkVersion=\"${project.TARGET_SDK_VERSION}\"", ' ')

                    manifestFile.text = manifestContent
                    println("i am here to change manifest content!!! \n $manifestContent")
                }
            })
        }

这个task主要是根据当前buildtype在build下找到manifest,然后修改里边的内容,去掉targetSDKVersionminSDKVersion

注意,我们这里想针对所有的buildType来进行这个修改manifest内容的操作,所以可以针对每个buildType都注册一个task来操作。

最终完整代码如图:

image (24).png

要注意这个task在gradle中的位置。

  1. 使用我们自定义的task

第一步我们自定义好task了,我们接下来需要将其插入到对应的位置中,让其在编译时候可以自动运行。

由于这里我们针对了所有的buildType都注册了一个task,所以我们制定执行顺序需要依赖buildType,故只能在varIanFilter中去设置顺序了。我们使用方法afterEvaluate

afterEvaluate {
            Task currentManifest = tasks.findByName("change${formatName}ManifestContentTask")
            Task firstTaskManifest = tasks.findByName("process${formatName}Manifest")
            Task nextTaskManifest = tasks.findByName("extract${formatName}Annotations")
            nextTaskManifest.dependsOn(currentManifest)
            currentManifest.mustRunAfter(firstTaskManifest)
            println("i am here to sort task!!!")
        }

代码位置如图所示,一样在varianFileter作用域中。

image (25).png

最后,我们SYNC一下代码,在gradle运行一下指令assembleRelease1给release1打包,发现生成的AAR manifest中没有了targetSDKVersionminSDKVersion

image (26).png

PS:AAR中的manifest文件,空的uses-sdk也是支持的,没问题哦~

3. 去掉AAR中的注解包

    我们如果在代码中使用了自定义的注解,那么注解信息是会打包到SDK中的,但是这些注解实际上并没有什么用处,他们的用处仅仅在编码时候规范我们,所以实际对SDK的运行并无影响,为了减小SDK大小,我们是可以直接将这个zip包删掉的。

image (27).png

    看下怎么删掉这个注解包。

老规矩,从之前的流程图里可以知道,task extractReleaseAnnotations 会生成这个注解包,那么我们就定义一个task,在 extractReleaseAnnotations 之后运行,将build目录下的注解包删掉即可。

  • 自定义task

注意我这个操作也是针对所有buildType,如果是针对单个buildType,请参考 删除SO 的task写法

   // 删除 annotations.zip task
        tasks.register("delete${formatName}AnnotationTask", {
            doLast {
                println("do delete for ${formatName}")
                delete("./build/intermediates/annotations_zip/${buildTypeName}/annotations.zip")
            }
        })

注意要将task插入到对应位置。

Task deleteAnnotationTask = tasks.findByName("delete${formatName}AnnotationTask")
            Task deleteAnnotationFirstTask = tasks.findByName("extract${formatName}Annotations")
            Task deleteAnnotationNextTask = tasks.findByName("bundle${formatName}Aar")
            deleteAnnotationNextTask.dependsOn(deleteAnnotationTask)
            deleteAnnotationTask.mustRunAfter(deleteAnnotationFirstTask)

代码位置整体截图

image (28).png

运行assembleRelease1,查看结果,不带注解包了。

image (29).png

打包APK和AAR原理流程一样,可以参考这个流程来。同时打包APK,AS本身提供了各种gradle配置,并且网上相关介绍很多,这里就不详细说了,后续遇到了再补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值