使用Gradle与Ant实现可配置不同环境的自动打包

Posted mqy1023

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Gradle与Ant实现可配置不同环境的自动打包相关的知识,希望对你有一定的参考价值。

一、搭建jenkins环境和配置gradle环境
  网上搭建jenkins的教程很多,这里不再赘述,主要说下jenkins中配置gradle环境
  点击“Manage Jenkins”
    1、 在“Manage Plugins”中搜索并安装gradle插件
    2、 在“Configure System”中配置指向你电脑中gradle所在路径(这步前当然要下载gradle)
    

二、新建jenkins项目并设置构建gradle配置项
  1、点击new item新建一个jenkins项目
  2、设置构建配置项
    a、配置svn代码所在目录
    
    b、配置执行gradle命令(【注意】只有在jenkins中下载安装 了gradle插件后,在”Build”构建项中点击”Add build step”增加构建步骤中看到“Invoke Gradle script”调用gradle脚本)
    

如上几步后能实现gradle最基本的打包(输出apk的路径app/build/outputs/apk/xxxx.apk)



三、配置ant脚本修改与环境相关的配置项代码(修改jenkins的项目源码)
  根据网上搜索的资料,可以在build.gradle脚本的task中修改项目中.java代码,但并没看到大量修改源码并很方便的做法;对于目前所在项目组打包中修改多次环境相关的源代码,还是用ant来专门处理修改源码,两种方式:
  1、jenkin的”Add build step”中可以先调用”Invoke Ant”然后”Invoke Gradle script”
  2、build.gradle中导入ant脚本(此方法缺点就是不能在”Invoke Ant”的Properties中动态修改源码)
ant.importBuild '../power-gradle.xml'

ant脚修改源码步骤如下:
  a、AppConfig .java源码

package com.demo.project;
public class AppConfig {
    /**
     * 
     * 是否打开调试
     */
    public static boolean isDebug = true;
    /**
     * 配置是否用于测试
     */
     public static boolean isTest = true;
}

  b、before-gradle.xml脚本

<?xml version="1.0" encoding="UTF-8"?>
<project name="DemoProject" basedir="." default="release">

    <!--《一》、 start 项目源代码目录 -->
    <property name="src" value="app/src" />
    <property name="res" value="app/src/main/res" />
    <!--《一》、end 项目源代码目录 -->


    <!--《二》、start 文件修改涉及到的文件名称及路径声明 -->
    <property name="AppConfig" value="${src}/main/java/com/demo/project/AppConfig.java" />
    <!--《二》、 end 文件修改涉及到的文件名称及路径声明 -->


    <!--《三》、start 在Jenkins中Ant属性Properties根据需求来配置不同值 -->
    <!-- 1、AppConfig文件 -->
    <!-- isDebug,默认false-->
    <property name="isDebug" value="false" />
    <property name="isDebug-pattern" value="isDebug( )*=( )*.*"></property>
    <property name="replace-isDebug-pattern" value="isDebug = ${isDebug};" />

    <!-- isTest,默认true -->
    <property name="isTest" value="true" />
    <property name="isTest-pattern" value="isTest( )*=( )*.*"></property>
    <property name="replace-isTest-pattern" value="isTest = ${isTest};" />
    <!--《三》、end 在Jenkins中Ant属性Properties根据需求来配置不同值 -->

    <target name="init">
        <echo> 1、setting AppConfig file accord ant Properties</echo>
        <!-- 关闭debug -->
        <echo>replace isDebug to ${isDebug}</echo>
        <replaceregexp file="${AppConfig}" match="${isDebug-pattern}" replace="${replace-isDebug-pattern}" byline="true" flags="gs" encoding="utf-8" />
        <!-- 是否可以选择环境-->
        <echo>replace isTest to ${isTest}</echo>
        <replaceregexp file="${AppConfig}" match="${isTest-pattern}" replace="${replace-isTest-pattern}" byline="true" flags="gs" encoding="utf-8" />
    </target>

    <target name="release" depends="init">
        <echo>ant prepare for release</echo>
    </target>

</project>

  c、jenkins中配置。
  

==> 以上几步可以将isTest设为false,isDebug设为false执行构建后能出来相应代码配置的apk

五、外部导入签名文件
  专门新建一个文件存放签名有关的keystore所在路径和签名密码,据说酱紫比较安全。。
1、build.gradle中

signingConfigs {
        //外部文件方式导入签名
        def Properties props = new Properties()
        def propFile = new File('signing.properties')
        if (propsFile.exists() && propFile.canRead()) {
            props.load(new FileInputStream(propFile))

            signingConfigs {
                release {
                    storeFile file(props['storeFile'])
                    storePassword props['storePassword']
                    keyAlias props['keyAlias']
                    keyPassword props['keyPassword']
                }
            }
        }
    }

2、signing.properties中。项目目录下新建该文件

//keystore的绝对路径
storeFile=C:\\\\demo\\\\project\\\\demo.keystore
storePassword=123456
keyPassword=123456
keyAlias=demo

六、多渠道打包
1、androidManifest.xml

<meta-data android:name="CHANNEL_ID" android:value="${CHANNEL_ID_VALUE}"/>

2、build.gradle
  a、方式一:

productFlavors {//多渠道打包,命令行打包:gradle assembleRelease
    xiaomi {
//        applicationId 'xiaomiRelease' //注释的话,默认xiaomiRelease和xiaomiDebug
          manifestPlaceholders.put("CHANNEL_ID_VALUE", 'xiaomi')
     }
    googlepaly {
//        applicationId 'googlepalyRelease'  //默认googlepalyRelease和googlepalyDebug
          manifestPlaceholders.put("CHANNEL_ID_VALUE", 'googlepaly')
     }
}

  a、方式二:

productFlavors {
    xiaomi{}
    googleplay{} 

    productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [CHANNEL_ID_VALUE: name]//name的值是xiaomi/googleplay
    }

}

3、打包命令
 电脑中配置了gradle环境变量
  1、gradle clean build -b ./build.gradle构建所有包
    
  2、gradle assembleRelease构建所有渠道发布包
  3、gradle assemble[xxx]Release构建单个渠道发布包。
    如:gradle assembleXiaomiRelease或gradle assembleGooglepalyRelease等和渠道名相关的release命令

4、查看渠道是否随配置更改。查看在下图目录中AndroidManifest.xml中CHANNEL_ID的值xiaomi / googleplay
这里写图片描述

5、复制apk到目录下apks目录。因为上面release出来的包层级过深,不方便测试人员在jenkins上查找下载apk,所有此步将./app/build/outputs/apk下所有的xx.apk放到DemoProject下的apks目录

//复制输出apk到项目目录下的apks目录
task copyFiles(type: Copy) {
    description = 'Copy apks to project directory ./apks '
    from 'build/outputs/apk'
    into './../apks'  //因为默认./在app目录
    include('**/*')  //复制所有
}
//根据命令行是构建所有渠道gradle assembleRelease、单个渠道包(如:gradle assembleXiaomiRelease)
//还是build构建包。即copyFiles任务是依赖assembleRelease/assembleXiaomiRelease/build
assembleRelease.dependsOn copyFiles

这里写图片描述

完整的build.gradle脚本

apply plugin: 'com.android.application'

import java.util.regex.Pattern
获取详细时间
def buildTime() {
    return new Date().format("yyyyMMdd'-'HHmm", TimeZone.getDefault())
}
//获取日期
def buildDate() {
    return new Date().format("yyyyMMdd", TimeZone.getDefault())
}

android {
    compileSdkVersion 21
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.demo.project"
        minSdkVersion 8
        targetSdkVersion 8
        multiDexEnabled true
    }

    sourceSets {
        main {
//            manifest.srcFile 'AndroidManifest.xml'
//            java.srcDirs = ['src']
//            resources.srcDirs = ['src']
//            aidl.srcDirs = ['src']
//            renderscript.srcDirs = ['src']
//            res.srcDirs = ['res']
//            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs'] // 指定jni路径
        }
    }
//    签名
    signingConfigs {
        release {
//            storeFile file('release.keystore')
//            storePassword '123456'
//            keyAlias 'demo'
//            keyPassword '111111'
        }
    }
    buildTypes {
        release {
            //是否混淆开关
            minifyEnabled false
            //是否zip对齐
            zipAlignEnabled true
            //是否打开debuggable开关
            debuggable false
            //是否打开jniDebuggable开关
            jniDebuggable false
            //混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            //签名配置
//            signingConfig signingConfigs.release

//            //release发布版本时针对多渠道。相当于省略多个manifestPlaceholders.put("CHANNEL_ID_VALUE", 'xiaomi')
//            productFlavors.all { flavor ->
//                manifestPlaceholders.put("CHANNEL_ID_VALUE", name) //name等于xiaomi / googlepaly
//            }
        }
    }
    lintOptions {//lint检查,有任何的错误或者警告提示,都会终止构建。false是关闭lint
        //在打包Release版本的时候进行检测,可以打开,这样报错还会显示出来
        checkReleaseBuilds false
        //abortOnError一定要设为false,这样即使有报错也不会停止打包了
        abortOnError false
    }
    productFlavors {//多渠道打包,命令行打包:gradlew assembleRelease
        xiaomi {
//            applicationId 'xiaomiRelease' //注释的话,默认xiaomiRelease和xiaomiDebug
            manifestPlaceholders.put("CHANNEL_ID_VALUE", 'xiaomi')
        }
        googlepaly {
//            applicationId 'googlepalyRelease'  //默认googlepalyRelease和googlepalyDebug
            manifestPlaceholders.put("CHANNEL_ID_VALUE", 'googlepaly')
        }
    }
    android.applicationVariants.all { variant ->
        def manifestFile = file("src/main/AndroidManifest.xml")
        def pattern = Pattern.compile("versionName=\\"(.+)\\"")
        def manifestText = manifestFile.getText()
        def matcher = pattern.matcher(manifestText)
        matcher.find()
        //获取versionName
        def versionName = matcher.group(1)
        pattern = Pattern.compile("versionCode=\\"(.+)\\"")
        matcher = pattern.matcher(manifestText)
        matcher.find()
        //获取versionCode
        def versionCode = matcher.group(1)

        if (variant.buildType.name.equals('release')) {
            variant.outputs.each { output ->
                def outputFile = output.outputFile
                if (outputFile != null && outputFile.name.endsWith('.apk')) {
//                    def fileName = "gradle4android_v${defaultConfig.versionName}_${releaseTime()}_${variant.flavorName}.apk"
                    def fileName = buildDate() + "/" + applicationId + "-v" + versionName + "-" + variant.name + "-" + buildTime() + "-" + variant.flavorName + ".apk"
                    output.outputFile = new File(outputFile.parent, fileName)
                }
            }
        } else {
            variant.outputs.each { output ->
                def outputFile = output.outputFile
                if (outputFile != null && outputFile.name.endsWith('.apk')) {
                    def fileName = buildDate() + "/" + applicationId + "-v" + versionName + "-" + variant.name + "-" + buildTime() + "-unaligned.apk"
                    output.outputFile = new File(outputFile.parent, fileName)
                }
            }
        }
    }

}

//复制输出apk到项目目录下的apks目录
task copyFiles(type: Copy) {
    description = 'Copy apks to project directory ./apks '
    from 'build/outputs/apk'
    into './../apks'  //因为默认'./'在app目录
    include('**/*')  //复制所有
}
//根据命令行是构建所有渠道gradle assembleRelease,单个渠道包(如:gradle assembleXiaomiRelease)
//还是build构建包。即copyFiles任务是依赖assembleRelease/assembleXiaomiRelease/build
assembleRelease.dependsOn copyFiles

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
}

  gradle在多渠道打包下超有优势,完爆ant;如上脚本gradle打了2个包在哥mac上只需要35-50s,纯ant脚本打一个包2min。。。
  



问题解决:
1、

Could not resolve all dependencies for configuration ‘:classpath’.
Could not resolve com.android.tools.build:gradle:1.5.0.
Required by:
:DemoProject: unspecified
Could not GET ‘https://jcenter.bintray.com/com/android/tools/build/gradle/1.5.0/gradle-1.5.0.pom‘.
Connection to https://jcenter.bintray.com refused
解决方案就是:降低代码中要求的版本classpath ‘com.android.tools.build:gradle:1.3.0’

2、gradle编译速度超慢

命令行中加–offline。如:gradle assemble - -offline,gradle clean - -offline
  gradle-tool是执行gradle的bin目录下gradle工具(window是gradle.bat,unix系统的是gradle)

  <target name="clean" depends="xxx">
       <echo>gradle clean --offline</echo>
       <exec executable="${gradle-tool}" failonerror="true">
           <arg value="clean" />
        <arg value="--offline" />
       </exec>
   </target>


参考:
1、http://doc.okbase.net/tanlon/archive/125036.html
2、http://www.jianshu.com/p/aa7f93d29805
3、多渠道打包:http://mp.weixin.qq.com/s?__biz=MzI4MzE2MTQ5Mw==&mid=402123825&idx=1&sn=404bdcfd65b6da9a9058260a753b6b55#rd
4、完整脚本:https://github.com/WuXiaolong/Gradle4Android
5、gradle系列:http://gold.xitu.io/#/tag/gradle
6、http://blog.csdn.net/shanlianting/article/details/49820607
7、http://blog.csdn.net/changemyself/article/details/39927381
8、《Gradle构建编译速度太慢的解决方法》http://www.52codes.net/article/658.html

以上是关于使用Gradle与Ant实现可配置不同环境的自动打包的主要内容,如果未能解决你的问题,请参考以下文章

GroovyGradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )

Android自动化构建之Ant多渠道打包实践(上)

Jenkins+Jmeter+Ant自动化集成环境搭建

Gradle构建SpringBoot并打包可运行的jar配置

Gradle

Gradle打包APK的一些小技巧和productFlavor配置