GradlePlugin

Introduction: 🔥AndroidStudio 开发 gradle 插件开发,gradle 基本应用介绍💫,Transform 使用介绍,javassist 使用介绍🌚🌚
More: Author   ReportBugs   
Tags:

Gradle 详解:

这一次一定要系统掌握,你准备好了吗?

使用 Android Studio 开发 Gradle 插件

    1. 创建一个 Android Library 类型的 Moudle;例如 mygradleplugin
    1. 将此 Module 的 java 修改改成 groovy
    1. 包名自己可以修改
    1. 此 Moudle 下 build.gradle 内容清空,添加如下代码
    apply plugin: 'groovy'
    apply plugin: 'maven'

    dependencies {
        compile gradleApi()
        compile localGroovy()
    }

    repositories {
        mavenCentral()
    }

    // group 和 version 在后面使用自定义插件的时候会用到 可以随便起
    group='com.micky'
    version='1.0.0'

    // 上传本地仓库的 task,
    uploadArchives {
        repositories {
            mavenDeployer {
            //本地仓库的地址,自己随意选,但使用的时候要保持一致,这里就是当前项目目录
                repository(url: uri('../repo'))
            }
        }
    }
    1. 在 groovy 路径下创建一个 MyCustomPlugin.groovy,新建文件一定要带后缀名
    class MyCustomPlugin implements Plugin<Project> {
        void apply(Project project) {
            System.out.println("这是自定义插件");
            project.task('myTask') << {
                println "Hi this is micky's plugin"
            }
        }
    }
    1. 现在,我们已经定义好了自己的 gradle 插件类,接下来就是告诉 gradle,哪一个是我们自定义的插件类,因此,需要在 main 目录下新建 resources 目录,然后在 resources 目录里面再新建 META-INF 目录,再在 META-INF 里面新建 gradle-plugins 目录。最后在 gradle-plugins 目录里面新建 properties 文件,注意这个文件的命名,你可以随意取名,(这里起名 com.micky.mycustom.properties)但是后面使用这个插件的时候,会用到这个名字。比如,你取名为 com.hc.gradle.properties,而在其他 build.gradle 文件中使用自定义的插件时候则需写成:apply plugin: 'com.hc.gradle'
    1. 然后在 com.micky.mycustom.properties 文件里面指明你自定义的类
    implementation-class='com.micky.plugin.MyCustomPlugin
    1. 执行 gradle uploadArchives 上传到本地仓库会生成 jar
然后在项目的 app 目录下的 build.gradle 使用插件
        //引入依赖       
        buildscript {
            repositories {
                maven {
                    url uri('../repo')
                }
                jcenter()
            }

           //这里和插件定义要一致,插件中 name 未指定就为默认项目名
            dependencies {
                classpath group: 'com.micky',
                        name: 'mygradleplugin',
                        version: '1.0.1'
            }
        }



        //这个名字一定要对应上.properties 文件名
        apply plugin: 'com.micky.mycustom'
  • 最后 先 clean project(很重要!),然后再 make project.从 messages 窗口打印如下信息

新增 Gradle Transform

监听文件编译结束,通过 javasist 实现字节码修改,实现代码插入,通过这种插件化的 AOP 和代码中使用 Aspectj 区别就是,避免代码碎片化,添加一个功能修改多处代码,即使用了 Aspectj 也许要在修改的地方添加注解,当修改处很多的时候很不方便,通过 transform 和 javassist 可以遍历整个工程,按照满足条件的一次性修改,并且以后我们可以写成通用性的组建,比如自动注册一些组建在所有 Activity,里面 Javassist 用了反射原理,但是这是编译器,不损失效率,Javassist 非常强大,需要仔细学习

1.新建一个 MyTransform 再新建一个插件 MyPlugin 注册这个 Transform

Mytransform 重写 transform 方法 里面要将输入内容复制给输出,否者报错,这是第一步,其实就是相当于在运行我们给拦截了,必须再把内容输出出去才能打包成 dex

里面遍历

// Transform 的 inputs 有两种类型,一种是目录,一种是 jar 包,要分开遍历 inputs.each { TransformInput input -> //对类型为“文件夹”的 input 进行遍历 input.directoryInputs.each { DirectoryInput directoryInput -> //文件夹里面包含的是我们手写的类以及 R.class、BuildConfig.class 以及 R$XXX.class 等

            /**
             * 这里就统一处理一些逻辑,避免代码分散,碎片化
             */

            println("transform transformsalkfjdl;kajf#####################*********")
            println(directoryInput.file.absolutePath)
            MyInject.injectDir(directoryInput.file.absolutePath,"com\\wangpos\\test",project)

            // 获取 output 目录
            def dest = outputProvider.getContentLocation(directoryInput.name,
                    directoryInput.contentTypes, directoryInput.scopes,
                    Format.DIRECTORY)

            // 将 input 的目录复制到 output 指定目录
            FileUtils.copyDirectory(directoryInput.file, dest)
        }
        //对类型为 jar 文件的 input 进行遍历
        input.jarInputs.each { JarInput jarInput ->

            //jar 文件一般是第三方依赖库 jar 文件

            // 重命名输出文件(同目录 copyFile 会冲突)
            def jarName = jarInput.name
            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }
            //生成输出路径
            def dest = outputProvider.getContentLocation(jarName + md5Name,
                    jarInput.contentTypes, jarInput.scopes, Format.JAR)
            //将输入内容复制到输出
            FileUtils.copyFile(jarInput.file, dest)
        }

}

里面都是 groovy 代码,也可以使用 java 代码,看哪个方便吧

问题总结

1.找不到依赖库,需要在 repositories 种添加 jcenter() 2.还有 javassist 找不到 jar 包,就是需要 javassist 引入 jar 包 3.发现生成的 apk 没有变化,删除了 build 目录重新 build,仍然无变化,点击 Android Studio setting 清理缓存,重新启动

Gradle 介绍

  • 1.删除无用资源 shrinkResources true 一般在 release 里配置

  • 2.zipalign 优化 zipAlignEnabled true zipalign 优化的最根本目的是帮助操作系统更高效率的根据请求索引资源 一般在 release 里配置 -

  • 3.混淆 minifyEnabled false

  • 4.pseudoLocalesEnabled true //

  • 如果没有提供混淆规则文件,则设置默认的混淆规则文件 (SDK/tools/proguard/proguard-android.txt)

  • 5.proguardFiles getDefaultProguardFile('proguard-Android.txt'), 'proguard-rules.pro'

  • 6.配置签名 signingConfig signingConfigs.debug

  • 7.配置多渠道打包 productFlavors

    1)为什么要多渠道打包?

    安卓应用商店(一个商店也叫做一个渠道,如 360,baidu,xiaomi)众多,大大小小几百个,我们发布应用之后需要统计各个渠道的用户下载量,所以才有了多渠道打包。 现在有比较成熟的第三方应用帮我们实现统计功能(比如友盟),统计的本质就是收集用户信息传输到后台,后台生成报表,帮助我们跟踪分析并完善 app。通过系统的方法已经可以获取到, 版本号,版本名称,系统版本,机型,地区等各种信息,唯独应用商店(渠道)的信息我们是没有办法从系统获取到的,所以我们就人为的在 apk 里面添加渠道信息(其实就用一个字段进行标识,如 360,baidu), 我们只要把这些信息打包到 apk 文件并将信息传输到后台,后台根据这个标识,可以统计各个渠道的下载量了,并没有多么的高大上。

    说了那么多,其实多渠道打包只需要关注两件事情:

    将渠道信息写入 apk 文件

    将 apk 中的渠道信息传输到统计后台


    添加配置,以友盟的方式,传到友盟来统计,他需要 UMENG_CHANNEL_VALUE 来区分


    android {
        productFlavors {
            xiaomi {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
            }
            _360 {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
            }
            baidu {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
            }
            wandoujia {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
            }
        }
    }

    然后在 Android 左下角可以通过 Build Variants 选择构建不同渠道应用
  • 8.添加不同 buildType,默认我们只有 debug 和 Realse

  • 9.每种渠道都对应这些 buildType 在选择 buildVarient 时候会全部显示出来

  • 10.通过不同的打包渠道,我们还可以将框架层抽离,比如我们的开发两个 app 图标和应用名字不一样,应用也不一样,可以通过这种,来实现多个 apk

      oea {
              applicationId "com.janus.oea.advancedgradledemo"
              applicationIdSuffix ".plus"
              //这样最终的包名是 com.janus.oea.advancedgradledemo.plus
              versionCode 1
              versionName "1.0.0"
              manifestPlaceholders = [APP_NAME: "APP_OEA"]
              buildConfigField 'String', 'OEM', '"OEA"'
          }
      oeb {
              applicationId "com.janus.oeb.advancedgradledemo"
              versionCode 2
              versionName "1.2.0"
              manifestPlaceholders = [APP_NAME: "APP_OEB"]
              buildConfigField 'String', 'OEM', '"OEB"'
          }
    

    依赖一下

    oeaCompile project(':oea') oebCompile project(':oeb')

  • 11.gradle 动态注入属性

通过${name},使得你可以在你的 Manifest 插入一个占位符。看下面的例子:

<activity android:name=".Main">
    <intent-filter>
        <action android:name="${applicationId}.foo">
        </action>
    </intent-filter>
</activity>

通过上面的代码,${applicationId}会被替换成真实的 applicationId,例如对于 branchOne 这个 variant,它会变成:

<action android:name="com.example.branchOne.foo">

这是非常有用的,因为我们要根据 variant 用不同的 applicationId 填充 Manifest.

如果你想创建自己的占位符,你可以在 manifestPlaceholders 定义,语法是:

productFlavors {
    branchOne {
        manifestPlaceholders = [branchCustoName :"defaultName"]
    }
    branchTwo {
        manifestPlaceholders = [branchCustoName :"otherName"]
    }
}
  • 12.如果你想做更复杂的事情,你可以 applicationVariants.all 这个 task 中添加代码进行执行。

假设,我想设置一个 applicationId 给 branchTwo 和 distrib 结合的 variant,我可以在 build.gradle 里面这样写:

applicationVariants.all { variant ->
    def mergedFlavor = variant.mergedFlavor
    switch (variant.flavorName) {
        case "brancheTwoDistrib":
            mergedFlavor.setApplicationId("com.example.oldNameBranchTwo")
            break
    }
}

有时某些 buildTypes-flavor 结合没有意义,我们想告诉 Gradle 不要生成这些 variants,只需要用 variant filter 就可以做到

variantFilter { variant ->
    if (variant.buildType.name.equals('release')) {
        variant.setIgnore(!variant.getFlavors().get(1).name.equals('distrib'));
    }
    if (variant.buildType.name.equals('debug')) {
        variant.setIgnore(variant.getFlavors().get(1).name.equals('distrib'));
    }
}

在上面的代码中,我们告诉 Gradle buildType=debug 不要和 flavor=distrib 结合而 buildType=release 只和 flavor=distrib 结合,生成的 Variants

Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea