欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Gradle学习记录6 代码记录(持续补充)

程序员文章站 2022-05-11 12:05:42
...

本篇博客主要记录一些,自己觉得比较有价值的Gradle代码片段。


1、生成doc文档:

ext {
    sdkDocOption = ['encoding' : 'utf-8',
                    'locale' : 'en_US',
                    'docEncoding' : 'utf-8',
                    'charSet' : 'utf-8',
                    'header' : 'AVLSDK API',
                    'footer' : 'AVLSDK API',
                    'windowTitle' : 'AVLSDK API',
                    'title' : 'AVLSDK API',
                    'include' : 'com/avl/engine/*.java',
                    'destinationDir' : 'sdk/doc']
}

//继承Javadoc类型的Task
//为source对应的文件生成Java Doc
task buildSdkDoc(type: Javadoc, dependsOn: 'copyClasses') {
    source = android.sourceSets.main.java.srcDirs
        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
        options.encoding = sdkDocOption.get('encoding')
        options.locale = sdkDocOption.get('locale')
        options.docEncoding = sdkDocOption.get('docEncoding')
        options.charSet = sdkDocOption.get('charSet')
        options.header = sdkDocOption.get('header')
        options.footer = sdkDocOption.get('footer')
        options.windowTitle = sdkDocOption.get('windowTitle')
        title = sdkDocOption.get('title')
        include(sdkDocOption.get('include'))
        destinationDir = reporting.file(sdkDocOption.get('destinationDir'))
        failOnError false
}

2、生成Jar包并混淆

ext {
    //Decide class files that will not be added into jar
    excludeFilesWhenBuildJar = [
            'com/example/**/BuildConfig.class',
            'com/example/**/R.class',
            'com/example/**/R$*.class'] as String[]

    //proguard file
    configurationFile = [file('proguard.txt'),] as File[]

    needPackageObfuscation = false

    sdkJarName = 'ExampleSDK.jar'
    proguardSdkJarName = 'ExampleSDK-proguard.jar'
}

//继承Jar类型的Task
//将build/sdk/classes下的文件,打包成Jar
//除去excludeFilesWhenBuildJar中的文件
task buildSdkJar(type: Jar, dependsOn: ['copyClasses', 'buildSdkDoc'], description: "build sdk jar") {
    from 'build/sdk/classes'
    for (String fileName : excludeFilesWhenBuildJar) {
        exclude(fileName)
    }
    includeEmptyDirs false
    archiveName sdkJarName
}

//继承ProGuardTask类型的Task
//根据混淆文件,混淆Jar包
task buildProGuardJar(type: ProGuardTask, dependsOn: buildSdkJar) {
    doFirst {
        def proguardDir = file("$buildDir/libs/proguard")
        proguardDir.mkdirs()
    }

    //未混淆的Jar包
    injars file("${buildDir}/libs/${sdkJarName}")

    //混淆后Jar的存储地址
    outjars file("${buildDir}/libs/${proguardSdkJarName}")

    //指定其它需要文件的地址
    libraryjars android.getBootClasspath() + ";" + file("libs/android-support-v4.jar")
    printseeds file("${buildDir}/libs/proguard/seed.txt")
    printusage file("${buildDir}/libs/proguard/usage.txt")
    printmapping file("${buildDir}/libs/proguard/mapping.txt")
    dump file("${buildDir}/libs/proguard/dump.txt")

    //指定混淆需要的配置文件
    //可以指定多个文件
    configuration file(rootProject.getRootDir().getAbsolutePath() + '/SDKConfig/proguard-base.txt')
    configuration configurationFile

    if (needPackageObfuscation) {
        //指定package的模糊字典
        packageobfuscationdictionary file("packageobfuscationdictionary.txt")
    }

    //NOTE:proguard has bug,dump file not generated in specified folder
    doLast {
        copy {
            from 'build/libs/dump.txt'
            into 'build/libs/proguard'
        }
    }
}

3、copy闭包
普通的Task,即使不继承CopyTask,也可以使用copy闭包。

def copySdkDoc

task test() {
    doLast {
    copySdkDoc()
    }
}

copySdkDoc = {
    ........
    //定义好目的地址
    def userDir = new File(user_path)
    if (userDir.exists()) {
        delete userDir
    }
    userDir.mkdirs()

    copy {
        //指定源文件地址
        from("$projectDir/build/reports/sdk") {
            //指定包含的文件类型
            include('**/*')
        }
        into(user_path)
    }
}

copy的同时,可以重命名文件:

 copyJarAndRepackage = {
    def sdkVer = getSdkVersionFromXml();
    copy {
        from("$projectDir/build/libs")
        into(user_path)
        //sdkJarName和proguardSdkJarName
        //均是"$projectDir/build/libs"下的文件名
        rename(sdkJarName, "${outputName}_unproguard.jar")
        rename(proguardSdkJarName, "${outputName}_" + sdkVer + '.jar')
    }
}

4、解压zip包

//File表示源文件
//newPath对应解压后的存储路径
def static void unzipFile(File file, String newPath) {
    def zipFile = new ZipFile(file)

    def entries = zipFile.entries()
    while (entries.hasMoreElements()) {
        def entry = (ZipEntry) entries.nextElement()
        def zipEntryName = entry.getName()

    //必要是将路径中的"\"替换为"/"
        def outPath = (newPath + File.separator + zipEntryName).replaceAll("\\\\", "/")
        def outDir = new File(outPath.substring(0, outPath.lastIndexOf('/')))

        if (!outDir.exists()) {
            outDir.mkdirs()
        }
        if (new File(outPath).isDirectory()) {
            continue
        }

        def input = zipFile.getInputStream(entry)
        def output = new FileOutputStream(outPath)
        def buf = new byte[1024]
        def len
        while ((len = input.read(buf)) > 0) {
            output.write(buf, 0, len)
        }
        input.close()
        output.close()
    }
}

5、计算文件的md5

def static String generateMd5BaseFile(File file) {
    def md5 = MessageDigest.getInstance("MD5")

    def buffer = new byte[1024]
    def numRead
    def fis = new FileInputStream(file)
    while ((numRead = fis.read(buffer)) > 0) {
        md5.update(buffer, 0, numRead)
    }

    def HEX_DIGITS = ['0', '1', '2', '3', '4', '5',
                      '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']

    def bArray = md5.digest()
    def sb = new StringBuilder(bArray.length * 2)

    for (byte b : bArray) {
        sb.append(HEX_DIGITS[(b & 0xf0) >>> 4])
        sb.append(HEX_DIGITS[b & 0x0f])
    }

    fis.close()

    return sb.toString()
}

6、解析Json字符串

//info对应json字符串
//key为需查找的key
def String getValueFromJsonString(String key, String info) {
    def parsedJson = new groovy.json.JsonSlurper().parseText(info)
    return parsedJson.get(key)
}

参考:
http://groovys.readthedocs.io/zh/latest/Groovy-module-guides/Parsing-and-producing-JSON.html
http://docs.groovy-lang.org/latest/html/gapi/groovy/json/JsonBuilder.html#call(java.util.Map)

7、使用Jar包
在Gradle中,通过命令使用Jar包时,需要注意这是一个异步操作,
即命令将在单独的线程中执行。

def void encryptBaseConfAseJar(String srcFilePath, String outFilePath, String flagPath) {
    ..............
    //这种方式是靠控制台命令来执行jar包的
    //指定jar路径,并提供参数即可
    def cmd = 'java -jar ' + jarPath + ' -encrypt '+
            srcFilePath + ' ' + outFilePath + ' ' + keyDir + ' ' + flagPath
    cmd.execute()
}

8、正则表达式

def static String getVersionBasePattern(String path) {
    def file = new File(path)
    def input = new FileInputStream(file)
    def bytes = new byte[input.available()]
    input.read(bytes)
    input.close()

    def info = new String(bytes, "GB2312")

    def p = Pattern.compile("Build \\d+")
    def m = p.matcher(info)
    def target = []
    while (m.find()) {
        target.add(m.group())
    }

    p = Pattern.compile("[^0-9]")
    def ret
    for (String str : target) {
        m = p.matcher(str)
        ret = m.replaceAll('').trim()
        if (ret != '') {
            return ret
        }
    }

    return ''
}

9、AES加解密
加密过程如下:

//srcFilePath源文件地址
//destFilePath加密后的文件地址
def void aesEncrypt(String srcFilePath, String destFilePath) {
    //16进制的AES_KEY
    def AES_KEY = ".........."
    def ALGORITHM = "AES"
    def TRANSFORMATION = "AES/ECB/PKCS5Padding"

    def sKeySpec = new SecretKeySpec(parseHexStr2Byte(AES_KEY), ALGORITHM)
    def encryptCipher = Cipher.getInstance(TRANSFORMATION)
    encryptCipher.init(Cipher.ENCRYPT_MODE, sKeySpec)

    FileInputStream inStream = new FileInputStream(new File(srcFilePath))
    CipherOutputStream cipherOutputStream =
            new CipherOutputStream(new FileOutputStream(new File(destFilePath)), encryptCipher)
    def read
    while ((read = inStream.read()) != -1) {
        cipherOutputStream.write((byte) read)
        cipherOutputStream.flush()
    }

    inStream.close();
    cipherOutputStream.close();
}

//将16进程变为byte数组
def static byte[] parseHexStr2Byte(String hexStr) {
    if (hexStr.length() < 1) {
        return null
    }

    def len = hexStr.length() / 2
    def result = new byte[len]
    for (int i = 0; i < len; i++) {
        def high = Integer.parseInt(hexStr.substring(i << 1, (i << 1) + 1), 16)
        def low = Integer.parseInt(hexStr.substring((i << 1) + 1, (i << 1) + 2), 16)
        result[i] = (byte) (high * 16 + low)
    }
    return result;
}

解密过程如下:

def static String decrypt(byte[] rawByte) {
    //加(解)密时使用的key
    def KEY = [.......] as byte[]
    //处理KEY
    byte[] key = messKey(KEY)

    SecretKeySpec sKey = new SecretKeySpec(key, "AES")

    //加(解)密时使用的AES
    def AES = [.......] as byte[]

    Cipher cipher = Cipher.getInstance(new String(messKey(AES)))
    cipher.init(Cipher.DECRYPT_MODE, sKey)

    return new String(cipher.doFinal(rawByte))
}

def static byte[] messKey(byte[] bArray) {
    def ret = new byte[bArray.length]

    //定义key,作用类似于混淆,转换原始的key
    byte key = ....
    for (int i = 0; i < bArray.length; ++i) {
        ret[i] = (byte) (bArray[i] ^ key)
    }

    return ret
}

10、Zip压缩

//inputPath源文件路径
//outPath目的文件路径
def void zipFile(String inputPath, String outPath) {
    def inputFile = new File(inputPath)
    if (!inputFile.exists()) {
        println 'inputFile not exist: ' + inputPath
    }

    def outFile = new File(outPath)
    if (outFile.exists()) {
        delete outFile
    }
    outFile.createNewFile()

    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outFile))
    zipUtil(out, inputFile, '')
    out.close()
}

//递归
def void zipUtil(ZipOutputStream out, File f, String baseName) {
    if (f.name.startsWith('.')) {
        return
    }

    if (f.isDirectory()) {
        def fl = f.listFiles()

        if (fl.length == 0) {
            out.putNextEntry(new ZipEntry(baseName + "/"))
            out.closeEntry()
            return
        }

        def pre = baseName
        if (baseName != '') {
            pre = baseName + "/"
        }

        for (File file : fl) {
            zipUtil(out, file, pre + file.name)
        }
    } else {
        out.putNextEntry(new ZipEntry(baseName))

        BufferedInputStream bi = new BufferedInputStream(new FileInputStream(f))
        int num
        while ((num = bi.read()) != -1) {
            out.write(num)
        }
        bi.close()

        out.closeEntry()
    }
}

11、向Zip包中添加文件

//srcFilePath代表待添加文件的路径
//targetPath代表Zip包的路径
//dirName为Zip中的目录,srcFilePath对应的文件将被加入到dirName下
def void zipAdd(String[] srcFilePath, String targetPath, String dirName) {
    def destFile = new File(targetPath)
    if (!destFile.exists()) {
        println 'can not find file: ' + targetPath
        return
    }

    def targetFile = []
    for (String src : srcFilePath) {
        def file = new File(src)
        if (file.exists()) {
            targetFile.add(file)
        }
    }

    zipAddUtil(targetFile, destFile, dirName)
}

def void zipAddUtil(ArrayList<File> srcFiles, File destFile, String dirName) {
    def tempFile = File.createTempFile(destFile.getName(), null, destFile.getParentFile())
    if (tempFile.exists()) {
        delete tempFile
    }
    destFile.renameTo(tempFile)

    byte[] buf = new byte[1024]
    def zin = new ZipInputStream(new FileInputStream(tempFile))
    def out = new ZipOutputStream(new FileOutputStream(destFile))

    //First copy old files
    def entry = zin.getNextEntry()
    def name
    def len
    while (entry != null) {
        name = entry.getName()
        out.putNextEntry(new ZipEntry(name))
        while ((len = zin.read(buf)) > 0) {
            out.write(buf, 0, len)
        }
        out.closeEntry()
        entry = zin.getNextEntry()
    }
    zin.close()

    //Then insert new files
    for (File srcFile : srcFiles) {
        InputStream input = new FileInputStream(srcFile)
        out.putNextEntry(new ZipEntry(dirName + "/" + srcFile.name))
        while ((len = input.read(buf)) > 0) {
            out.write(buf, 0, len)
        }
        out.closeEntry()
        input.close()
    }

    out.close()
    delete tempFile
}

12、获取当前的软件版本和TAG

def static int getGitVersionCode(String projectPath) {
    //其实就是调用控制台命令
    def cmd = 'git -C ' + projectPath + ' rev-list HEAD --first-parent --count'
    def versionCodeStr = cmd.execute().text.trim()

    if ("" == versionCodeStr) {
        return 1
    }

    return versionCodeStr.toInteger()
}

def static String getGitVersionTag(String projectPath) {
    def cmd = 'git -C ' + projectPath + ' describe --tags'
    def version = cmd.execute().text.trim()
    def pattern = "-(\\d+)-g"
    def matcher = (version =~ pattern)

    if (matcher) {
        version = version.substring(0, matcher.start())
    }

    return version
}

13、代码检查
Gradle中可以直接通过集成的方式,定义代码检查需要的task。
例如:

lint:

android {
    //android中直接支持lint检查
    lintOptions {
        abortOnError false
        //定义输出格式为xml
        xmlReport true
        htmlReport false
        //定义lint的检测规则
        lintConfig file("...../lint.xml")
        //定义lint结果输出路径
        xmlOutput file(".../lint_result.xml")
    }
}

checkStyle:

//为了进行代码检查,需要导入对应的插件
apply plugin: 'checkstyle'

task checkstyle(type: Checkstyle) {
    ignoreFailures = true
    //定义checkStyle的配置文件
    configFile file("..../checkstyle.xml")
    //定义checkStyle的忽略配置
    //可以指定某些文件,忽略特定的checkStyle规则
    configProperties.checkstyleSuppressionsPath = file("......./suppressions.xml").absolutePath
    source = [android.sourceSets.main.java.srcDirs, 'src']
    include '**/*.java'
    exclude '**/gen/**', '**/test/**'
    classpath = files()
    reports {
        //只能开启一个
        xml.enabled = true
        html.enabled = false
        xml.destination "..../checkStyle_result.xml"
    }
}

checkStyle的忽略文件类似于:

<suppressions>
    //R.java中的变量,不需要满足仅由小写字符、大写字符和数字构成
    <suppress checks="[a-zA-Z0-9]*" files="R.java" />
    ..........
<suppressions>

findbugs:

apply plugin: "findbugs"

task findBugs(type: FindBugs) {
    ignoreFailures = true
    effort = "max"
    //findBugs可以指定报告的输出等级
    reportLevel = "low"
    //这里填写项目classes目录
    classes = files("........./classes")
    source = [android.sourceSets.main.java.srcDirs, 'src']
    classpath = files()
    includeFilter = file("......../findbugs-infilter.xml")
    reports {
        //只能开启一个
        xml.enabled = true
        html.enabled = false
        xml.destination "..../findBugs_result.xml"
    }
}

pmd:

apply plugin: 'pmd'

task pmd(type: Pmd) {
    description 'Run pmd'
    group 'verification'

    ignoreFailures = true
    ruleSetFiles = files("......./pmd-ruleset.xml")
    ruleSets = []
    source = [android.sourceSets.main.java.srcDirs, 'src']
    include '**/*.java'
    exclude '**/gen/**', '**/test/**'

    reports {
        xml.enabled = true
        html.enabled = false
        xml.destination "...../pmd_result.xml"
    }
}

14、defaultTasks
Gradle允许在脚本中定义一个或多个默认任务,如下:

defaultTasks 'clean', 'buildSdk'

task clean << {
    println 'Default Cleaning!'
}

task run << {
    println 'Default build sdk!'
}

task other << {
    println "I'm not a default task!"
}

此时直接调用gradle命令,不指定task,执行结果类似于:

Default Cleaning!
Default build sdk!

需要注意的是:
必须要在定义defaultTasks的gradle文件对应Project的目录下,执行gradle命令。
defaultTasks可以为当前Project或子Project定义的task。

举例来说就是:
假设有个Project A,对应的gradle文件为buildA.gradle;
Project A有个子Project B,对应的gradle文件为buildB.gradle。
如果在buildA.gradle中定义defaultTasks,那么task可以定义在buildA.gradle、buildB.gradle中。
但执行gradle时,必须在Project A的目录下。

15、多任务调用及排除任务的命令

//依次执行多个任务
gradle task1 task2 [...]

//-x后接的任务将不被执行
//例如,defaultTasks指定了task1, task2
//此时,task1和task2不会执行了
gradle -x task1 task2 [...]

16、文件树
文件树可以代表一个目录树结构或一个ZIP压缩文件的内容。
FileTree继承自FileCollection,我们可以像处理文件集合一样处理文件树。

FileTree的使用示例如下:

//以一个基准目录创建一个文件树
FileTree tree = fileTree(dir: 'src/main')

// 添加包含和排除规则
tree.include '**/*.java'
tree.exclude '**/Abstract*'

// 使用路径创建一个树
tree = fileTree('src').include('**/*.java')

// 使用闭包创建一个树
tree = fileTree('src') {
    include '**/*.java'
}

// 使用map创建一个树
tree = fileTree(dir: 'src', include: '**/*.java')
tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')

// 遍历文件树
tree.each {File file ->
    println file
}

// 过滤文件树
FileTree filtered = tree.matching {
    include 'org/gradle/api/**'
}

// 合并文件树A
FileTree sum = tree + fileTree(dir: 'src/test')

// 访问文件树的元素
tree.visit {element ->
    println "$element.relativePath ------ $element.file"
}

我们还可以使用ZIP或TAR等压缩文件的内容作为文件树,
Project.zipTree()和Project.tarTree()方法可以返回Project对应文件的FileTree实例。

示例用法如下:

// 使用路径创建一个ZIP文件
FileTree zip = zipTree('someFile.zip')

// 使用路径创建一个TAR文件
FileTree tar = tarTree('someFile.tar')

//TarTree可以根据文件扩展名得到压缩方式
//如果我们想明确的指定压缩方式则可以如下操作
FileTree someTar = tarTree(resources.gzip('someTar.ext'))

17、文件同步任务
同步任务(Sync)继承自复制任务(Copy)。
当执行时会复制源文件到目标目录,然后从目标目录删除所有非复制文件。
如下:

//将libs中的文件复制到test
//最终test目录中的内容与libs一模一样
task syncTask(type: Sync) {
    from 'libs'
    into 'test'
}
相关标签: gradle