基于Jenkins的Android持续集成(自动化打包)

首先给大家推荐一下我老师大神的人工智能教学网站。教学不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵黄段子!点这里可以跳转到网站

一、环境要求

本帖针对的是Windows环境,Linux或其他系统请另寻他贴。具体只讲述Jenkins配置以及整个流程的实现。
1.JDK(或JRE)及Java环境变量配置,我用的是JDK8。这个配置方法做开发的都懂,不懂的网上帖子也很多,不赘述。
2.现成Android项目及SVN(GIT或本地路径也行)地址,Android SDK。
3.Gradle环境变量配置,已经实现Gradle打包及多渠道打包,目前只会用工具Eclipse或者AS打包的,可以网上搜索Gradle脚本打包具体实现,这里也不赘述。
4.下载解压Tomcat,我用的是Tomcat7
5.下载Jenkins war包,我用的版本是2.7.6,下载地址:https://jenkins.io/download/
6.如果需要打包后自动上传蒲公英的,还需下载curl工具,如果不需要可以忽略此条。网址https://curl.haxx.se/download.html最下方,我下的版本是Win64 x86_64 7zip    7.53.1  binary  SSL     Darren Owen
7.后续可能加入自动加固的功能

二、环境检查

1.Java环境变量配置检查:打开cmd,输入java -version,可以看到打印的Java版本信息。如果没有,请配置java环境变量。
2.Gradle环境变量配置检查:打开cmd,输入gradle -v,可以看到打印的Gradle版本信息。如果没有,请配置Gradle环境变量。

三、Jenkins配置

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

1.放置war包
将下载的Jenkins war包放到Tomcat的webapps目录下,比如我的是F:\DE\apache-tomcat-7.0.67\webapps
2.启动
双击运行Tomcat的bin目录下的startup.bat脚本将Tomcat运行起来,然后在浏览器访问:http://localhost:8080/jenkins/。conf目录下的server.xml文件可以配置端口号,默认是8080,如果跟其他项目有冲突可以自行修改。
3.插件安装
1)如图,打开插件管理界面

2)插件管理界面如下

需要安装的插件有:Android Lint Plugin,Gradle Plugin,Subversion Plug-in(如果代码不是SVN托管的可以不选)
其实用到的还有Git plugin,用于项目代码托管在Git的情况,我用的Jenkins版本已经集成了Git插件,如果项目源码就在本地目录,可以不用安装SVN插件。
插件安装完毕后需要重启Jenkins,直接关掉Tomcat再启动就可以了。
3.全局工具配置
重新登录Jenkins后,回到主界面,如图,打开全局工具配置界面

不同版本的Jenkins的界面可能进入的方式不一样,所以找不到图中的菜单不用担心,只需关注后续配置的内容可以自己找到对应的菜单来访问,反正Jenkins的系统管理界面菜单项也不多。
1)首先是JDK的配置,如图

别名自取,路径将自己电脑上jdk所在的路径复制粘贴上来。
2)Git的配置,如图

由于我的项目代码托管在Git上,所以需要配置Git。同样需要将自己电脑上git.exe所在的路径复制粘贴上来,注意这里是git.exe的全路径,和JDK有所区别。
3)Gradle的配置,如图

路径以自己电脑上为准,截图仅供参考路径最深到哪一级目录,Gradle只需指定主目录即可。
4.Android SDK路径配置
有些Jenkins版本配置Android SDK的方法和上面JDK那些一样,我这个版本是在系统配置中单独配置的,如图

配置内容如下:

这里的和操作系统上配置的环境变量一致,通过勾选的名称也知道,指的就是系统环境变量。
到这里整个打包环境就搭建完毕了。

四、创建打包任务

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

接下来针对某个Android APP项目创建专门的打包任务,在主界面点击“新建”,如图

1.创建任务
如图,输入任务名,我这里以项目简写为名,选择“构建一个自由风格的软件项目”,然后点击最下方OK按钮。

2.任务配置
创建完任务,就来到了任务的配置界面,如图所示

1)参数化构建
相信我们Android开发人员在实际的项目开发过程中肯定有过,针对开发环境、测试环境、线上环境,甚至还有仿真环境,分别打过包进行自测或者交付给测试这种情况。在这里我们可以针对这种实际情况,有很好的解决方案。比如当有人需要包的时候,我们把这打包服务的地址给他后,让他自己选择自己想要的环境出包。此外选择APP版本、打包的渠道、签名以及第三方工具的key,都可以定制。我们所要做的就是就是把选择提供出来,然后根据打包时的选择,来对应出包。
如图,Jenkins提供了“参数化构建过程的选择”

各种选择可以通过参数的形式传递给打包服务,然后选择性打包。通过上图的下拉列表的选择,可以看到支持的各种参数类型。这里只提供不同环境(开发、测试、线上)选择性打包的实现方式,如有其它需求,可以参考,思路大体都是一样的。
如下图所示,我这里提供了三种打包方案的选择,可以支持在打包的时候选择不同环境的接口服务所对应的Android包。这里的API_ENVIRONMENT其实就是参数名,可的值就是三项:DEV、TEST、ONLINE(后面我把这里修改成了四个值,DEV、TEST、PRE、PRO、RELEASE)。不知道怎么填的点击输入框后面的蓝色问号按钮,可以看到提示,再点一下关闭提示。

2)workspace配置
配置完上一步后,点击右下角的“高级”按钮,配置我们的工作空间。以后Android项目代码以及打出的包都会在这个路径下。

3)源码管理
这一步是配置我们的Android源码,我这个项目是托管在SVN的,所以这里以SVN为例

这里我导入了三个模块,第一个是APP的主目录,第二个是library,第三个是静态资源文件。我们这个项目是MUI项目,所以外壳是单独的SVN地址,MUI代码是单独的SVN地址,然后还引用了一个也是单独SVN地址的第三方library(这句话看不懂的可以略过)。所以这里可以给项目源码分管在多个SVN地址的同学一个很好的参考。
“Repository URL”里面填的东西不用多说。“Credentials”其实就是SVN账号名和密码的配置,点击那个“Add”按钮添加账号和密码,如下图

“Kind”选择“Username with password”,然后填写账号和密码就OK了。回到源码配置界面的时候选择刚才配置的账户就可以了,然后会自动验证的。
“Local module directory”,源码配置界面的这个模块本地目录很重要,“./”指向的是上面第2)步的工作空间的目录,然后我的项目名是jm_information,所以到时APP的代码就会被导在工作空间下的jm_information目录里,jm_information文件夹就是整个APP的目录了。
第三方library的目录和APP项目的目录是平级的,然后MUI的代码是要放到assets目录下。不知道MUI为何物的可以不用管第三个配置,项目没有引用第三方library的可以不用管第二个配置。
然后是SVN的命令配置,推荐选择如下(让自动打包系统,每次打包任务执行前,先还原对代码的修改,再更新,保证打出来的包代码是最新的):

4)构建配置
设置定时出包的配置很简单,Android出包没那么频繁,不是很常用,所以这里就跳过不说了。这里是重头戏了,创建打包任务最开始,“四-2-1)”那里我们选择了“参数化构建过程的选择”,配置了一个参数“API_ENVIRONMENT”来作为打包时对API服务的选择。如图

选择“Invoke Gradle script”选择,填写图中的打包命令。有的Jenkins版本有下图这个选项,我用的版本没有,所以可以通过上图中的命令格式实现这个效果。这个命令的意思等同于在APP主目录执行 gradle clean build然后把API_ENVIRONMENT参数传递给你的build.gradle脚本。意味着你APP主目录下的build目录会被删除,然后build.gradle中的代码可以接到一个变量API_ENVIRONMENT及其值。

然后截图里面还有“Excute Windows batch command”这个选项,这里的命令做的是将打出的包上传到蒲公英。这里用到了curl工具,文章一开头提到过的需要下载的工具。我没有给curl配置系统环境变量,所以这里是通过绝对路径来调用的。如果没有打完包后自动上传到蒲公英的需求,这一步可以省略。整个命令不作具体解释了,蒲公英官网有很详细的教程,浓缩下来就是这一串命令,有基础的人理解起来应该没难度。

五、build.gradle

其实以上的整个步骤下来,已经可以打出APP包了,但是文章之前提到了根据构建任务的选择,可以自动打出不同API服务的包这个功能还没有实现。所以,到这里还需要配合build.gradle打包脚本来实现这个功能。
我这个项目的接口服务的参数是写在一个js文件里,因为是混合APP内部是js+html实现的,原生APP可以把接口服务的参数放在配置文件中。比如build.gradle脚本可以读取同目录下的gradle.properties中配置到的参数。下面是我这个项目所用的build.gradle脚本的代码:
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.12.+'
    }
}
apply plugin: 'android'

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

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    // API环境
    def apiEnvironment = API_ENVIRONMENT ? API_ENVIRONMENT : project.API_ENVIRONMENT
    // 修改mui项目中的API环境配置
    appFile = new File("assets/apps/H537C8863/www/js/conf.js")
    source = appFile.getText()
    appFile.withPrintWriter { printWriter ->  
        printWriter.println('var apiEnvironment = "' + apiEnvironment + '";')  
    }
    appFile.append(source, "UTF-8")

    productFlavors {
        if (apiEnvironment == "RELEASE") {
            official {}
            baidu {}
            _360 {}
            xiaomi {}
            huawei {}
            _91 {}
            appchina {}
            anzhi {}
            aliapp {}
            qq {}
            google {}
            oppo {}
        } else if (apiEnvironment == "PRO") {
            official {}
        } else {
            dev {}
        }
    }

    productFlavors.all {
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] 
    }

    defaultConfig {
        versionCode project.VERSION_CODE as int
        versionName project.VERSION_NAME
        minSdkVersion project.ANDROID_BUILD_MIN_SDK_VERSION as int

        //配置各种key
        switch (apiEnvironment) {
            case "DEV":
            case "TEST":
            case "PRE":
                manifestPlaceholders = [
                    PUSH_APPID_VALUE: project.DEV_PUSH_APPID,
                    PUSH_APPKEY_VALUE: project.DEV_PUSH_APPKEY,
                    PUSH_APPSECRET_VALUE: project.DEV_PUSH_APPSECRET
                ]
            break
            case "PRO":
                manifestPlaceholders = [
                    PUSH_APPID_VALUE: project.PRO_PUSH_APPID,
                    PUSH_APPKEY_VALUE: project.PRO_PUSH_APPKEY,
                    PUSH_APPSECRET_VALUE: project.PRO_PUSH_APPSECRET
                ]
            break
            case "RELEASE":
                manifestPlaceholders = [
                    PUSH_APPID_VALUE: project.PRO_PUSH_APPID,
                    PUSH_APPKEY_VALUE: project.PRO_PUSH_APPKEY,
                    PUSH_APPSECRET_VALUE: project.PRO_PUSH_APPSECRET
                ]
            break
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }

    //签名    
    signingConfigs {    
        release {
            //设置release的签名信息 
            storeFile file(project.STORE_FILE)      //签名文件    
            storePassword project.STORE_PASSWORD        
            keyAlias project.KEY_ALIAS
            keyPassword project.KEY_PASSWORD  //签名密码    
        }
    }

    lintOptions {  

        abortOnError false  

        // if true, only report errors  
        ignoreWarnings true  
     }

     //加载so文件      
     task copyNativeLibs(type: Copy) {     
          from fileTree(dir: 'libs', include: '**/*.so' )  into  'build/native-libs'    
     }    
     tasks.withType(Compile) { compileTask -> compileTask.dependsOn copyNativeLibs }    

     clean.dependsOn 'cleanCopyNativeLibs'    

     tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask ->    
            pkgTask.jniFolders = new HashSet()    
        pkgTask.jniFolders.add(new File(projectDir, 'build/native-libs'))    
    }

    buildTypes {
        release {
            if (apiEnvironment != "RELEASE") {
                signingConfig signingConfigs.release
            }
        }
    }
}
这个是groovy语音,和java语法大体是类似的,看不懂的可以网上找找groovy的语法教程简单看一下,理解起来就不难了。这个脚本从上到下做了:
1)buildscript 引入gradle依赖;
2)dependencies 声明本地依赖的jar包、lib文件、第三方library
3)android 指定编译、构建用的sdk版本,`def apiEnvironment = API_ENVIRONMENT ? API_ENVIRONMENT : project.API_ENVIRONMENT`这行代码很关键,API_ENVIRONMENT就是“参数化构建过程”这一步配置的参数“API_ENVIRONMENT”传递进来的值——DEV、TEST、PRE、PRO、RELEASE其中的一个,project.API_ENVIRONMENT是取的我在gradle.properties中配置的参数,如下图
这里写图片描述


所以上面那行代码的目的是,如果收到Jenkins传递的API_ENVIRONMENT,就以Jenkins传递的API_ENVIRONMENT的值为准,否则就取gradle.properties中配置的接口服务类型来打包。因为有些时候还是需要手动打包,所以这里做了个区分。

 // 修改mui项目中的API环境配置
    appFile = new File("assets/apps/H537C8863/www/js/conf.js")
    source = appFile.getText()
    appFile.withPrintWriter { printWriter ->  
        printWriter.println('var apiEnvironment = "' + apiEnvironment + '";')  
    }
    appFile.append(source, "UTF-8")

上面这几行代码是根据打包时选择的接口服务类型(开发环境、测试环境还是线上环境等等),来修改项目源码中的配置。原生项目就只用把配置写在gradle.properties中就行了,不用像我这么麻烦。
productFlavors 里面的是根据接口服务类型来设置友盟统计的渠道,这里只有发布包是多渠道打包。
4)defaultConfig 前三行是将gradle.properties中的版本号、版本名称、APP最小支持的SDK版本写入到AndroidManifest.xml中。switch语句做的是根据不同的接口环境,给AndroidManifest.xml中的推送服务配置相应的key,线上环境和其他环境用的是两套key。
5)compileOptions JDK的版本
6)sourceSets 指定资源文件的目录
7)signingConfigs 签名配置
8)lintOptions 静态检查的相关配置
9)后面三个task做的是加载so文件
10)buildTypes 当打发布包的时候不进行签名(发布包,也就是上传到应用商店的包需要加固后再签名)

六、开始打包

这里写图片描述
这里写图片描述


上图中有两个任务,一个是我创建好的,一个是文章开始做截图用的,点击进去。

选择”Build with Parameters“,可以看到上图右边的界面,可以自主选择出什么类型的包。
自动化打包教程到此结束。

点这里可以跳转到人工智能网站

发表评论