打造最實(shí)用的Android持續(xù)集成(Continuous Integration以下簡(jiǎn)稱CI)系統(tǒng)
什么是CI
縱覽全局(打破職責(zé)界限)
軟件開(kāi)發(fā)仪媒、運(yùn)維和質(zhì)量保證三個(gè)部門之間的溝通辐马、協(xié)作和集成所采用的流程员辩、方法和體系的一個(gè)集合挥转。
打破目前的rd->qa->op流水線的流程,而是將三者緊密的結(jié)合在一起潜沦。從實(shí)踐的結(jié)果來(lái)看干花,rd每次提交代碼都會(huì)觸發(fā)一系列的自動(dòng)化步驟,包括編譯揍鸟,單元測(cè)試兄裂,代碼覆蓋率,功能測(cè)試阳藻,部署測(cè)試懦窘,性能/容量測(cè)試(注:后兩者受限與時(shí)間要求,實(shí)際實(shí)施不會(huì)每次提交代碼都觸發(fā))稚配。Rd,qa港华,op都在過(guò)程中做質(zhì)量保障道川。
代碼樹(shù)被管理起來(lái)——主干開(kāi)發(fā)
主干開(kāi)發(fā)的好處是每個(gè)rd都知曉整體的變更,所有的feature作為一個(gè)整體發(fā)布立宜,對(duì)OP的現(xiàn)實(shí)意義就是上線變得更有規(guī)律冒萄,非計(jì)劃的、臨時(shí)的上線最后消失橙数。
代碼和周邊(配置尊流,數(shù)據(jù),構(gòu)建腳本灯帮,單元測(cè)試崖技,測(cè)試用例)統(tǒng)一作為產(chǎn)品被管理起來(lái)——一鍵式產(chǎn)構(gòu)建,測(cè)試钟哥,部署迎献,完成產(chǎn)品的最終發(fā)布。
大家都在一個(gè)平臺(tái)上工作腻贰,所有的任務(wù)都在這個(gè)平臺(tái)下吁恍,各角色間對(duì)互相的工作有更深入的了解,并且,工作狀態(tài)也可以共享冀瓦。
少就是多伴奥,簡(jiǎn)潔就是美(用簡(jiǎn)單的方法解決問(wèn)題)
持續(xù)集成的解決方案是簡(jiǎn)潔的。產(chǎn)品由SVN去管理翼闽,構(gòu)建過(guò)程由CI server負(fù)責(zé)拾徙,而構(gòu)建過(guò)程包含了編譯,測(cè)試肄程,發(fā)布锣吼,部署過(guò)程
容量測(cè)量(Capacity management)
容量的變化體現(xiàn)在用戶行為(流量)系統(tǒng)變更(軟件性能)和資源(服務(wù)器數(shù)量,冗余度計(jì)劃)等幾個(gè)因素的變化上蓝厌,將容量和這些變化掛鉤玄叠,在每一個(gè)因素變化下重新得到系統(tǒng)的容量,從而在變更中控制容量不足造成的風(fēng)險(xiǎn)拓提。有一個(gè)要點(diǎn)读恃,我們需要的是系統(tǒng)的容量而不是單個(gè)模塊的性能。
質(zhì)量反饋(Quality feedback)
變更會(huì)導(dǎo)致質(zhì)量變化代态,而質(zhì)量變化體現(xiàn)在各種指標(biāo)上寺惫,而測(cè)量這些指標(biāo)(包括應(yīng)用指標(biāo):平響,處理效率等和系統(tǒng)指標(biāo):負(fù)載蹦疑,網(wǎng)絡(luò)流量)西雀,發(fā)現(xiàn)指標(biāo)之間的規(guī)律,將指標(biāo)share給整個(gè)團(tuán)隊(duì)歉摧,從而有效的達(dá)成質(zhì)量的反饋艇肴,控制變更(包括內(nèi)部變更和外部條件的變化)造成的質(zhì)量下降的風(fēng)險(xiǎn)。
Jenkins能做什么
在實(shí)施持續(xù)集成的過(guò)程中叁温,并行實(shí)施的三個(gè)項(xiàng)目:
持續(xù)部署/一鍵式部署(continuous deployment/one step deploy)再悼,
容量測(cè)試/管理(Capacity Test/Management)
質(zhì)量反饋(Quality feedback)
Jenkins將完成上面3個(gè)并行的項(xiàng)目工作
安裝與配置Jenkins
gradle代碼
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
signingConfigs {
mySign {
keyAlias 'xxx'
keyPassword 'xxx'
storeFile file('hqyx.keystore')
storePassword 'xxx'
v2SigningEnabled false
}
}
compileSdkVersion 26
defaultConfig {
applicationId "com.hqyxjy.word"
minSdkVersion 19
targetSdkVersion 21
versionCode 7
versionName "1.1.0"
multiDexEnabled = true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resValue('string', 'git_last_commit', 'git log --pretty=format:"%H:%ad:%an:%s" --date=short -1'.execute().text)
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.mySign
}
debug {
signingConfig signingConfigs.mySign
}
}
productFlavors {
flavorDimensions 'channel', 'host', 'area'
// 渠道
hqyx {
dimension 'channel'
manifestPlaceholders.put('PACKAGE_CHANNEL_VALUE', 'hqyx')
}
develop {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
}
// host的值
ttest {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.test'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.test'])
}
sim {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.sim'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.sim'])
}
beta {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.beta'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.beta'])
}
online {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.online'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.online'])
}
develop {
dimension 'host'
buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
}
// area
outside {
dimension 'area'
buildConfigField('boolean', 'IS_INSIDE', 'false')
}
inside {
dimension 'area'
buildConfigField('boolean', 'IS_INSIDE', 'true')
}
}
// 控制打包時(shí)輸出的文件名
android.applicationVariants.all { variant ->
variant.outputs.all {
if (!variant.buildType.debuggable) {
// 計(jì)算出新的文件名格式:NewName_v1.3.0_02051437_develop.apk
def fileName = "NewName_${defaultConfig.versionName}_${new Date().format("MMddHHmm")}_${variant.flavorName}.apk"
fileName = fileName.replace("_z_", "_")
outputFileName = fileName
}
}
}
// 配置多渠道時(shí),去除沒(méi)用的Variants
android.variantFilter { variant ->
if (variant.buildType.name.contains('debug')) {
variant.getFlavors().each() { flavor ->
if (flavor.name.contains('z_')) {
variant.setIgnore(true)
}
}
} else {
variant.getFlavors().each() { flavor ->
if (flavor.name.contains('develop')) {
variant.setIgnore(true)
}
}
}
}
lintOptions {
//發(fā)生錯(cuò)誤時(shí)停止構(gòu)建
abortOnError true
//警告都提升為錯(cuò)誤
warningsAsErrors true
//build release 版本 時(shí) 開(kāi)啟lint 檢測(cè)
checkReleaseBuilds true
//規(guī)則位置
lintConfig file("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/lint.xml")
//不生成xml報(bào)告
xmlReport false
//忽略
disable 'OldTargetApi'
//設(shè)置報(bào)告位置
htmlOutput file("${project.rootDir}/ci/report/app/lint_report.html")
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.google.android.gms:play-services-plus:11.6.2'
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testImplementation files('powermocklibs/byte-buddy-1.2.1.jar')
testImplementation files('powermocklibs/cglib-nodep-2.2.2.jar')
testImplementation files('powermocklibs/hamcrest-core-1.3.jar')
testImplementation files('powermocklibs/javassist-3.21.0-GA.jar')
testImplementation files('powermocklibs/mockito-core-2.0.42-beta.jar')
testImplementation files('powermocklibs/objenesis-2.4.jar')
testImplementation files('powermocklibs/powermock-api-mockito-common-1.6.6.jar')
testImplementation files('powermocklibs/powermock-mockito2-1.6.6-full.jar')
debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'
implementation project(':AndroidCoreLibrary:core')
implementation project(path: ':svprogresshud')
testImplementation 'junit:junit:4.12'
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:multidex:1.0.1'
implementation 'com.jakewharton:butterknife:8.4.0'
implementation 'com.android.support:gridlayout-v7:26.1.0'
implementation 'cn.yipianfengye.android:zxing-library:2.1'
testCompile 'junit:junit:4.12'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'com.github.traex.rippleeffect:library:1.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
//友盟統(tǒng)計(jì)
implementation 'com.umeng.analytics:analytics:latest.integration'
}
repositories {
mavenCentral()
}
/*------------------------------------------------------------------------------------------------*/
/* CI TASK
/*------------------------------------------------------------------------------------------------*/
apply plugin: 'pmd'
task pmd(type: Pmd) {
ruleSetFiles = files("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_library_ruleset.xml",
"${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_custom_ruleset.xml",
"${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_single_ignore_ruleset_naming_VariableNamingConventions.xml")
ignoreFailures = false
ruleSets = []
source 'src'
include '**/*.java'
exclude '**/gen/**'
reports {
xml.enabled = false
html.enabled = true
html {
destination "${project.rootDir}/ci/report/app/pmd_report.html"
}
}
}
apply plugin: 'findbugs'
task findbugs(type: FindBugs) {
ignoreFailures = false
effort = "default"
reportLevel = "medium"
//過(guò)濾器
excludeFilter = new File("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/findbug_filter.xml")
//這里填寫(xiě)項(xiàng)目classes目錄
classes = files("${project.buildDir}/intermediates/classes")
source = fileTree('src/main/java')
classpath = files()
reports {
//只能開(kāi)啟一個(gè)
xml.enabled = false
html.enabled = true
html {
destination "${project.rootDir}/ci/report/app/find_bugs_report.html"
}
}
}
apply plugin: 'checkstyle'
checkstyle {
toolVersion '7.1.2'
showViolations false
}
task checkstyle(type: Checkstyle) {
ignoreFailures = true
def config = file("${project.rootDir}/ci/ruleset/checkstyle.xml")
if (config.exists()) {
ignoreFailures = false
configFile = config
classpath = files()
source = android.sourceSets.main.java.srcDirs
include '**/*.java'
include '**/*.xml'
reports {
xml.enabled = false
html.enabled = true
html {
destination "${project.rootDir}/ci/report/app/check_style_report.html"
}
}
}
}