作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/09/02/android-install-xposed-framework/

简介

提到Xposed框架时,人们总会用到一个词“神器”。
是的,安装Xposed后,我们似乎脑洞大开,以前不能干的事件,现在都能干了。
对此,我的理解是:hook android,hook everything

Xposed框架是什么???

官方对此的解释是这样的:
“Xposed是一个适用于Android的框架。基于这个框架开发的模块可以改变系统和app应用的行为,而不需要修改APK。这是一个很棒的特性,意味着Xposed模块可以不经过任何修改,安装在各种不同的ROM上。Xposed模块可以很容易的开启和关闭。你只需要激活或者禁用Xposed模块,然后重启手机即可。”

[xposed1]

在手机发烧友的眼中,Xposed是这样子的:

修改手机主题,权限控制,阻止广告,禁用各种APP滥用权限,微信,游戏等相关的各种外挂…

在开发者的眼中,Xposed是这样子的:

渗透测试,测试数据构造,环境监控,动态埋点,热补丁,自动化录制…

关于Xposed框架的基本原理以及更多介绍,请参考文末链接,或者自行百度。

风险声明

[xposed2]

在安装Xposed框架之前,我必须把风险告诉你:

  1. 软变砖
  2. 无限重启

简单解释下:

  1. 软砖: 手机能启动,但是进不去桌面
  2. 硬砖/黑砖: 手机在按电源键,或者连接电脑没反应,一直黑屏。
  3. 软砖可以救 硬砖只能修。
  4. 无限重启: 就是手机快要进入桌面的时候,又自动重启。周而复始,无限重启。

根据官方的警示和网友的反馈, 三星的手机,以及索尼,戴尔的部分手机 容易导致以上风险。

安装

Xposed框架的安装需要经过root,安装第三方Recovery,安装Xposed框架,安装Xposed Installer等几个步骤。这些步骤都是依次进行的,任何步骤的失败,都会导致Xposed框架的安装过程中止。

因此,建议在 国际国内的主流Android机型 上进行安装。

Root

根据我的个人实践,这里我推荐使用 KingRoot 这款工具进行Root。

官方网址: https://kingroot.net/?myLocale=zh_CN

Root之前,我建议你查询下,你的机型是否被支持: https://kingroot.net/model

TWRP

对于Android 5.0以上的手机,官方提示,必须要先刷入第三方Recovery, 比如: TWRP

官方网址: https://twrp.me/

刷机之前,请先查询下,你的机型是否被支持:
https://twrp.me/Devices/

以Nexus 5 为例, 网站有详细的操作指南。https://twrp.me/devices/lgnexus5.html

当然有些非主流手机,也可以在相关论坛找到TWRP的修改版本。

比如我的手机,中兴 Blade A1(C880U) 16G 灵动白 移动4G手机 双卡双待

我就是参考:中兴小鲜3中兴Blade a1移动版全网通版本TWRP刷写教程@root

按照 TWRP for ZTE Blade Apex 2 强行刷入的。

刷机完成后,重启可以进入Recovery界面。

Xposed Framework

下载

Xposed Framework下载地址:http://dl-xda.xposed.info/framework/

其中,sdk21,sdk22,sdk23,分别对应Android 5.0,5.1, 6.0.
根据,手机ROM版本和处理器类型选择Xposed Framework刷机包。

比如,中兴Blade a1移动版(5.1, arm64),我选择了刷机包xposed-v86-sdk22-arm64.zip 和卸载包xposed-uninstaller-20150831-arm64.zip

下载之后,将这两个压缩包,拷贝到SD卡根目录下。

安装

  1. 重启手机,进入Recovery界面。(adb reboot recovery)
  2. 选择【安装刷机包】进入下级页面,选择【从SD卡选择ZIP文件】
  3. 在SD卡根目录找到Xposed Framework刷机包(xposed-v86-sdk22-arm64.zip),并选择。
  4. 滑动底部的滑动条,确认刷入,等待提示刷机完成。
  5. 重启手机,等待进入桌面。

卸载

如果刷入Xposed Framework刷机包之后,无限重启,进不去桌面怎么办?
那就按照下面提示,卸载掉Xposed Framework。

  1. 重启手机,进入Recovery界面。(adb reboot recovery)
  2. 选择【安装刷机包】进入下级页面,选择【从SD卡选择ZIP文件】
  3. 在SD卡根目录找到Xposed Framework卸载刷机包(xposed-uninstaller-20150831-arm64.zip),并选择。
  4. 滑动底部的滑动条,确认刷入,等待提示刷机完成。
  5. 重启手机,等待进入桌面。

Xposed Installer

这是一个管理Xposed模块的官方应用。通过它,你可以随时禁用或者启用Xposed模块,然后重启手机。

对于Android 5.0以上的手机,请前往XDA论坛主题贴下载附件 XposedInstaller_3.0_alpha4.apk,并安装。

下载地址:http://forum.xda-developers.com/showthread.php?t=3034811

如果你看到以下界面,恭喜你,Xposed Framework安装完成。

[xposed3]

FAQ

  1. Xposed FAQ / Known issues
  2. Xposed in zhihu
  3. Xposed in Stackoverflow

参考

  1. Xposed 官网
  2. Xposed XDA论坛
  3. [OFFICIAL] Xposed for Lollipop/Marshmallow [Android 5.0/5.1/6.0, v86, 2016/07/08]
  4. Xposed framework 作者rovo89 原文(xda)介绍大译
  5. Xposed:不得不说的 Android 神器
  6. Android 系统上的 Xposed 框架中都有哪些值得推荐的模块?
  7. xposed模块整理
  8. 基于Xposed修改微信运动步数
  9. 用黑客思维做测试——神器 Xposed 框架介绍
  10. 安卓注入框架Xposed分析与简单应用
  11. Xposed框架初体验

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/08/24/android-incremental-update-solutions-flowchart/

今天,我对Android 应用增量升级方案的流程进行了一个梳理,简单画了一个流程图。

流程图源文件: [update.graphml]

请使用yEd工具打开浏览。https://www.yworks.com/products/yed/download

[Android 应用增量升级方案之流程图]

作者:snowdream
Email:yanghui1986527#gmail.com
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/08/23/android-incremental-update-solutions/

名词解释

全量升级

每次下载完整的新安装包,进行覆盖安装。

增量升级

将新安装包和已经安装的旧安装包进行比对,生成一个差分升级包(Patch包)。用户下载patch包后,和已经安装的旧安装包进行合并,生成新安装包,再进行覆盖安装。

背景

在早期的Android应用开发中,由于android应用普遍比较小,因此,普遍采用了全量升级方案。简单粗暴,却行之有效。
但是,随着Android的发展,Android应用功能越来越多,体积越来越大,再综合以下几个因素考虑,全量升级方案逐渐无法满足我们的需求。

  1. 在国内,随着2G,3G,4G的逐步演进,手机网络越来越快,但有一点事实仍然没有改变:流量很贵,非常不够用。(这个因素不适合WIFI用户和土豪用户) [北京移动流量套餐] 以北京移动为例,最基础套餐,5元30M流量。而最新的微信APK安装包35M。也就是说,如果你选了最基础套餐,你一个月内使用全量升级方案,升级一次微信,流量都不够用。
  2. 在敏捷开发大行其道的今天,开发者希望尽快将新开发的功能推送到用户面前,并及时得到用户的反馈。恨不能三天一小版,一周一大版。

综合以上因素,开发者必须为用户考虑:省流量,省流量,省流量。

显然,全量升级这种土豪做法已经不再适用,于是,增量升级应运而生。

增量升级原理

首先,两句话简单概括增量升级原理:

  1. 服务端通过比对最新升级包,和当前应用包,生成差分升级包;
  2. 客户端将差分升级包和当前应用包合并,生成最新升级包。

接下来,简单介绍下增量升级的原理:

  1. 首先,客户端获取当前应用的Version Code和应用APK文件的MD5值,发送给服务器;
  2. 服务器根据既定策略,给用户返回更新包信息。
    1. 通过MD5值没有查询到旧有APK应用信息,返回全量升级包网址,全量升级包MD5值;
    2. 需要返回patch包,但还没有生成patch包时,后台去生成patch包,并返回全量升级包网址,全量升级包MD5值;
    3. 需要返回patch包,并且已经生成patch包时,返回patch包网址,patch包MD5值,全量升级包网址,全量升级包MD5值;
    4. 不需要返回patch包,则返回全量升级包网址,全量升级包MD5值;
  3. 客户端根据返回信息进行更新操作。
    1. 如果只有全量升级包相关信息,则下载全量升级包,并在校验MD5值后,安装更新包;
    2. 如果有差分升级包(patch包),则下载差分升级包。校验差分升级包的MD5值。如果校验失败,走上面一个步骤。如果校验成功,则将差分升级包和当前版本的APK进行合并操作,生成新的应用包。校验新的应用包MD5值。校验通过,这安装这个生成的新应用包。如果校验失败,则走上面一个步骤。

以上只是简单介绍了增量升级的原理,实际应用中还需要细化,考虑更多的场景。

注意: 下载过程中,必须支持断点续传策略。防止网络不畅时,不断重试,造成的流量浪费。

增量升级方案

增量升级方案的核心就是使用Diff/Patch算法来对新旧APK进行diff/patch操作。
目前主流的Diff/Patch算法是bsdiff/bspatch,来自:http://www.daemonology.net/bsdiff/

另外,我了解到,国内有人开源了另外一种Diff/Patch算法,名字叫做HDiffPatch,来自:https://github.com/sisong/HDiffPatch

据说,比bsdiff/bspatch更高效呢?详见《HDiff1.0和BSDiff4.3的对比测试》

我将bsdiff/bspatch和HDiffPatch,使用jni封装成so库,供android调用。项目地址: https://github.com/snowdream/android-diffpatch
在封装HDiffPatch过程中遇到问题,得到作者@sisong的支持和帮助,在此表示感谢。

bsdiff/bspatch和HDiffPatch算法都是开源的,服务端可以根据源文件来进行编译集成。
这里我主要在Android客户端的角度,介绍下bsdiff/bspatch和HDiffPatch怎么使用。

BsDiffPatch

  1. 在build.gradle文件中自定义jnilib目录

    1
    2
    3
    4
    5
    sourceSets {
    main {
    jniLibs.srcDirs = ['libs']
    }
    }
  2. app/libs/armeabi-v7a/libbsdiffpatch.so 拷贝到你的工程对应目录下。

  3. app/src/main/java/com/github/snowdream/bsdiffpatchapp/src/main/java/com/github/snowdream/diffpatch 拷贝到你的工程对应目录下,包名和文件名都不能改变。
  4. 在Java文件中参考以下代码进行调用。
    1
    2
    3
    4
    5
    6
    IDiffPatch bsDiffPatch = new BSDiffPatch();
    bsDiffPatch.init(getApplicationContext());
    //diff
    bsDiffPatch.diff(oldFilePath, newFilePath, diffFilePath);
    //patch
    bsDiffPatch.patch(oldFilePath, diffFilePath, gennewFilePath);

HDiffPatch

  1. 在build.gradle文件中自定义jnilib目录

    1
    2
    3
    4
    5
    sourceSets {
    main {
    jniLibs.srcDirs = ['libs']
    }
    }
  2. app/libs/armeabi-v7a/libhdiffpatch.so 拷贝到你的工程对应目录下。

  3. app/src/main/java/com/github/snowdream/hdiffpatchapp/src/main/java/com/github/snowdream/diffpatch 拷贝到你的工程对应目录下,包名和文件名都不能改变。
  4. 在Java文件中参考以下代码进行调用。
    1
    2
    3
    4
    5
    6
    IDiffPatch hDiffPatch = new HDiffPatch();
    hDiffPatch.init(getApplicationContext());
    //diff
    hDiffPatch.diff(oldFilePath, newFilePath, diffFilePath);
    //patch
    hDiffPatch.patch(oldFilePath, diffFilePath, gennewFilePath);

BsDiffPatch vs HDiffPatch

这里我选择高德地图Android客户端的两个版本来进行测试。

  • 测试来源:http://www.autonavi.com/
  • 测试版本: Amap_Android_V7.7.4.2128_GuanWang.apk 和 Amap_Android_V7.3.0.2036_GuanWang.apk (注:版本跨度大,差异大)
  • 对比算法: BsDiffPatch vs HDiffPatch
  • 测试结果:(详见下图)
    1. BsDiffPatch生成的patch包稍小。
    2. 两者的diff操作都非常耗资源,耗时间,无法忍受。(当然diff操作一般在服务端进行)
    3. 两者的patch操作都比较快。通过大概五次测试,BsDiffPatch的patch操作需要13s左右,而HDiffPatch的patch操作仅仅需要1s左右。

以上结果仅供参考。

  • 测试结论:
    1. BsDiffPatch应用更广泛
    2. HDiffPatch看起来更高效
[test]

扩展

以上算是比较成熟的增量升级方案了,但是仔细想想,可能还存在一些问题:

  1. 由于多渠道,多版本造成非常多Patch包
  2. bs diff/patch算法性能和内存开销太高
    第一个问题可以通过服务器策略进行限制。比如,只有最新版5个版本内的升级采用增量升级,其他的仍然采用全量升级。
    据说,还有一种更强大的算法,可以解决以上问题。大家有兴趣的话,可以自己去了解。
    crsync-基于rsync rolling算法的文件增量更新.md

参考

  1. 友盟增量更新的原理是什么
  2. Android应用增量更新库(Smart App Updates)
  3. Android实现应用的增量更新\升级
  4. https://github.com/smuyyh/IncrementallyUpdate
  5. 浅析android应用增量升级
  6. https://github.com/cundong/SmartAppUpdates
  7. crsync-基于rsync rolling算法的文件增量更新.md
  8. https://github.com/sisong/HDiffPatch
  9. http://www.daemonology.net/bsdiff/
  10. https://github.com/snowdream/android-diffpatch

作者:snowdream
Email:yanghui1986527#gmail.com
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/08/13/android-develop-with-kotlin/

目标

本文旨在引导开发者使用Kotlin来开发Android应用。

至于Kotlin语言的语法和教程等,不在本文讨论范围,请参考以下官网文档和网上的开发教程。

  1. kotlin-android
  2. 《Kotlin for android Developers》中文翻译
  3. Kotlin-in-Chinese
  4. Kotlin 官方参考文档 中文版
  5. Kotlin 官方文档中文翻译版

简介

名词解释

Kotlin

Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。
Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
JetBrains,作为目前广受欢迎的Java IDE IntelliJ 的提供商,在 Apache 许可下已经开源其Kotlin 编程语言。

官方网站:http://kotlinlang.org/

Github仓库: https://github.com/JetBrains/kotlin

教程

本节介绍如何使用Kotlin开发android应用。

以下几点需要谨记:

  1. 所有Kotlin类文件,以.kt为后缀。
  2. Kotlin的源码目录规则和默认的是一样的。分别放在src/main/kotlin, src/test/kotlin, src/androidTest/kotlin 和任意的src/${buildVariant}/kotlin。

Kotlin and Java

使用Kotlin来开发android,需要经过以下几个步骤进行配置。
1.在项目根目录下的build.gradle文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
buildscript {
ext.kotlin_version = '1.0.1-2'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

2.在模块目录下的build.gradle文件中添加以下代码:

1
2
3
4
5
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

3.配置完成,你可以在src/main/kotlin目录下愉快地使用Kotlin来写Android应用了。

实例展示:

  1. https://github.com/JetBrains/kotlin-examples
  2. https://github.com/snowdream/test/tree/master/android/kotlin/HelloWorld

Java 2 Kotlin

上面是手动给android项目增加kotlin支持。
其实还有一种自动转换的方法,也可以添加kotlin支持。

  1. 通过菜单“ Help | Find Action”或者快捷键“Ctrl+Shift+A”调出动作查询窗口
  2. 输入”Configure Kotlin in Project”,回车,按照提示操作,即可添加Kotlin配置。
  3. 重复第一步,调出动作查询窗口。输入“Convert Java File to Kotlin File”。即可将现有的Java文件自动转换成Kotlin文件。当然,如果只想转换某一个java文件,方法就是,打开改Java文件,然后选择菜单“ Code | Convert Java File to Kotlin File”,即可将当前打开的Java文件自动转换成Kotlin文件。
  4. 转换完成。

总结

根据Kotlin官网描述,Kotlin是一种适用于JVM,Android
根据个人的开发实践,总结出使用Kotlin开发Android应用的优缺点:

优点

  1. 和Java相比,更简洁,更安全。
  2. 和Java无缝集成,官网宣称kotlin可以100%和java混合使用。
  3. 由jetbrains推出,Idea可以更好的进行支持。

缺点

  1. 会将支持kotlin的相关jar包打散,打包到apk中。这部分内容最终会给apk增加700k左右的大小。这个和前面的groovy相比,情况要好很多,勉强还是可以接受的。
  2. 和java相比,使用Kotlin的开发者还太少。
  3. 诞生时间较晚,有待时间的检验。

结论

  1. 使用Kotlin是可以更快,更有效地开发Android应用的。
  2. 在应用于生产实践之前,还需要更多的评估,包括稳定性,运行效率,耗电量,兼容性,研发的接受程度等。

参考

  1. Kotlin名词解释
  2. Kotlin官网
  3. kotlins-android-roadmap
  4. Getting started with Android and Kotlin
  5. Kotlin Android Extensions
  6. kotlin-examples
  7. HelloWorld
  8. 如何评价 Kotlin 语言
  9. Kotlin:Android世界的Swift
  10. 初见Kotlin
  11. Android开发中使用kotlin你遇到过哪些坑?

作者:snowdream
Email:yanghui1986527#gmail.com
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/08/12/android-develop-with-groovy/

目标

本文旨在引导开发者使用Groovy来开发Android应用。

简介

名词解释

Groovy

Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。

官方网站:http://www.groovy-lang.org/

Github仓库:https://github.com/apache/groovy

Gradle

Gradle是一个开源的自动化构建工具,主要用于持续发布(Continuous Delivery)。
Gradle主要基于Java和Groovy开发,用于支持多种开发语言和开发平台的自动化构建,包括Java, Scala, Android, C/C++, 和 Groovy, 并且可以无缝集成在Eclipse, IntelliJ, and Jenkins等开发工具和持续集成服务中。

官方网站:https://gradle.org/

Github仓库:https://github.com/gradle/gradle

Groovy with Android

groovy-android-gradle-plugin是一款由groovy推出的插件,主要用于支持使用groovy来开发android应用。

Github仓库: groovy-android-gradle-plugin

实例展示:
https://github.com/snowdream/test/tree/master/android/groovy/HelloWorld

教程

本节介绍使用Groovy开发android应用,主要分两种:
一种是只使用Groovy来开发,另外一种是混合使用Groovy和Java来开发。

至于Groovy语言的语法和教程等,不在本文讨论范围,请参考官网文档和网上的开发教程。

以下几点需要谨记:

  1. 所有Groovy类文件,以.groovy为后缀。
  2. 任何新建的Groovy类,请在类的头部加上注解:@CompileStatic .具体原因请移步:Melix’s blog here for more technical details
  3. Groovy的源码目录规则和默认的是一样的。分别放在src/main/groovy, src/test/groovy, src/androidTest/groovy 和任意的src/${buildVariant}/groovy。
  4. Groovy的源码目录可以在build.gradle文件中自定义,定义规则如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    androidGroovy {
    sourceSets {
    main {
    groovy {
    srcDirs += 'src/main/java'
    }
    }
    }
    }

为了让Android Studio识别这些自定义目录为源码目录,可能还需要在android插件的sourceSets中再添加一遍。

Groovy

仅仅使用Groovy来开发android,步骤比较简单。
1.在项目根目录下的build.gradle文件中添加以下代码:

1
2
3
4
5
6
7
8
9
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
classpath 'org.codehaus.groovy:groovy-android-gradle-plugin:1.0.0'
}
}

2.在模块目录下的build.gradle文件中添加以下代码:

1
2
3
4
apply plugin: 'groovyx.android'
dependencies {
compile 'org.codehaus.groovy:groovy:2.4.6:grooid'
}

3.配置完成,你可以在src/main/groovy目录下愉快地使用Groovy来写Android应用了。

实例展示:https://github.com/groovy/groovy-android-gradle-plugin/tree/master/groovy-android-sample-app

Groovy and Java

仅仅使用Groovy来开发android,步骤也比较简单。

1.这一步,和上面的一样。
2.在模块目录下的build.gradle文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apply plugin: 'groovyx.android'
androidGroovy {
skipJavaC = true
sourceSets {
main {
groovy {
srcDirs += 'src/main/java'
}
}
}
options {
configure(groovyOptions) {
encoding = 'UTF-8'
forkOptions.jvmArgs = ['-noverify'] // maybe necessary if you use Google Play Services
javaAnnotationProcessing = true
}
sourceCompatibility = '1.7' // as of 0.3.9 these are automatically set based off the android plugin's
targetCompatibility = '1.7'
}
}
dependencies {
compile 'org.codehaus.groovy:groovy:2.4.6:grooid'
}

3.配置完成,你既可以在src/main/groovy下写Groovy类,也可以在src/main/java下写Java类。

实例展示:https://github.com/snowdream/test/tree/master/android/groovy/HelloWorld

注意事项

1.引用使用groovy开发的lib时,需要排除groovy的jar包。

例如:引用groovy-xml库,操作如下:

1
2
3
compile ('org.codehaus.groovy:groovy-xml:2.4.3') {
exclude group: 'org.codehaus.groovy'
}

2.Groovy编译选项在androidGroovy块下的options块进行编写。

1
2
3
4
5
6
7
8
9
10
androidGroovy {
options {
configure(groovyOptions) {
encoding = 'UTF-8'
forkOptions.jvmArgs = ['-noverify'] // maybe necessary if you use Google Play Services
}
sourceCompatibility = '1.7' // as of 0.3.9 these are automatically set based off the android plugin's
targetCompatibility = '1.7'
}
}

3.如果需要在java文件中引用Groovy文件内容,需要将所有源码文件使用groovyc来编译,而不要通过javac编译。

1
2
3
androidGroovy {
skipJavaC = true
}

4.如果需要用到注解(annoation),还需要作如下设置:

1
2
3
4
5
6
7
androidGroovy {
options {
configure(groovyOptions) {
javaAnnotationProcessing = true
}
}
}

注: 这一步,首先要确保第三步已经设置skipJavaC = true.

5.如果需要用到Data Binding,需要用到一个插件https://bitbucket.org/hvisser/android-apt

更多配置,请参考: groovy-android-gradle-plugin

总结

根据个人的开发实践,总结出使用Groovy开发Android应用的优缺点:

优点

  1. 引入Groovy的诸多特性,包括闭包,函数式编程,静态编译,DSL等。
  2. 和Java无缝集成,可以平滑过渡。

缺点

  1. 会将支持Groovy的相关jar包打散,打包到apk中。这部分内容最终会给apk增加2~3M的大小。这个目前看来是硬伤,希望以后能够精简一下。
  2. 会将一些License文件打包到apk中,这个可以通过android开发插件的packagingOptions进行过滤,问题不大。
  3. 和java相比,使用Groovy的开发者还太少。
  4. 中文文档少,主要是官方英文文档。

结论

  1. 使用Groovy是可以开发Android应用的。
  2. 在应用于生产实践之前,还需要更多的评估,包括稳定性,运行效率,耗电量,兼容性,研发的接受程度等。

参考

  1. Groovy现在可运行于Android平台
  2. groovy-android-gradle-plugin
  3. groovy-android-helloworld
  4. Groovy官网
  5. Groovy名词解释
  6. Java之外,选择Scala还是Groovy?
  7. http://www.groovy-lang.org/
  8. https://gradle.org/

大家在用Gradle开发Android项目的时候,想必都知道构建过程是由一个一个的任务(Task)组成的。
那么项目中到底有那些Task呢?

1
gradle tasks --all

执行上面的命令,你会看到项目中定义的所有Task。

这些Task是怎样依赖的,构建过程中又是怎样的流程?

在实践过程中,我找到两个gradle插件,可以帮助我们实现流程的可视化。

gradle-task-tree

gradle-task-tree可以将项目下的task依赖关系以树的形式,输出到终端。

引入

1
2
3
4
5
6
7
8
9
10
11
12
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.2.2"
}
}
apply plugin: "com.dorongold.task-tree"

使用

gradle taskTree –no-repeat

输出

结果会打印在终端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:build
+--- :assemble
| \--- :jar
| \--- :classes
| +--- :compileJava
| \--- :processResources
\--- :check
\--- :test
+--- :classes
| +--- :compileJava
| \--- :processResources
\--- :testClasses
+--- :compileTestJava
| \--- :classes
| +--- :compileJava
| \--- :processResources
\--- :processTestResources

gradle-visteg

gradle-visteg可以将项目下的task依赖关系以图的形式,输出到文件。
默认是输出到.dot文件中,通过Graphviz工具可以将.dot文件转换成.png文件。

引入

1
2
3
4
5
6
7
8
9
10
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'cz.malohlava:visteg:1.0.0'
}
}
apply plugin: 'cz.malohlava.visteg'

配置

1
2
3
4
5
6
7
8
9
10
11
visteg {
enabled = true
colouredNodes = true
colouredEdges = true
destination = 'build/reports/visteg.dot'
exporter = 'dot'
colorscheme = 'spectral11'
nodeShape = 'box'
startNodeShape = 'hexagon'
endNodeShape = 'doubleoctagon'
}

详细配置请参考:https://github.com/mmalohlava/gradle-visteg

使用

执行正常的task。
默认会生成build/reports/visteg.dot文件。
在ubuntu下,可以通过xdot,直接打开该文件。

通过以下命令可以转换成png图片

1
2
cd build/reports/
dot -Tpng ./visteg.dot -o ./visteg.dot.png

输出

task流程图
[task流程图]

参考

  1. https://github.com/dorongold/gradle-task-tree
  2. https://github.com/mmalohlava/gradle-visteg

本文旨在从实践出发,引导开发者在Android项目中进行Mock单元测试。

什么是单元测试

单元测试由一组独立的测试构成,每个测试针对软件中的一个单独的程序单元。单元测试并非检查程序单元之间是否能够合作良好,而是检查单个程序单元行为是否正确。

为什么要进行单元测试

在敏捷开发大行其道的今天,由于时间紧,任务重,过分依赖测试工程师以及下列原因,导致单元测试不被重视,在开发流程中处于一个可有可无的尴尬境地。

  1. 浪费的时间太多
  2. 软件开发人员不应参与单元测试
  3. 我是很棒的程序员,不需要进行单元测试
  4. 不管怎样,集成测试将会抓住所有的Bug
  5. 单元测试效率不高

那么单元测试是否正的可有可无呢?No! No! No!

  1. 作为android客户端研发,在一个开发周期内,你负责的需求需要Web服务(API),和本地代码(JNI,Native Code)的支持,而你们的工作是同时进行的。
  2. 你的需求开发完成了,但是由于需要在特定条件下才能触发,而这些条件在开发过程中很难去模拟,导致需求无法在所有场景下进行充分测试。举个例子,假设你在室内开发一个地图导航的Android应用,你需要在导航过程中,前方出现车祸,积水,施工等多种状况,怎么办?
  3. 总结你过去的BUG,你会发现有些你以为写的很完善的逻辑,却在最后被发现有场景未覆盖,或者逻辑错误等问题。
  4. 测试工程师给你报了一个BUG,你改完提交了,但是之后由于Merge失误导致代码丢失,或者其他人的修改导致你的BUG再次出现。直到测试工程师再次发现该BUG,并再次给你提出。
  5. 你的开发进度很快,但是开发完成后,你会被BUG淹没。你持续不断的修改BUG,持续不断的加班,直至发布版本,身心俱疲。
  6. 以前明明很正常的功能,在本次开发周期内,突然不能正常使用了。

如果你也经常碰到以上问题,或者困扰,那么你需要持续不断的对项目进行单元测试

Android单元测试简介

Android的单元测试分为两大类:

1.Instrumentation

通过Android系统的Instrumentation测试框架,我们可以编写测试代码,并且打包成APK,运行在Android手机上。

优点: 逼真
缺点: 很慢

代表框架:JUnit(Android自带),espresso

2.JUnit / Mock

通过JUnit,以及第三方测试框架,我们可以编写测试代码,生成class文件,直接运行在JVM虚拟机中。

优点: 很快。使用简单,方便。
缺点: 不够逼真。比如有些硬件相关的问题,无法通过这些测试出来。

代表框架: JUnit(标准),Robolectric, mockito, powermock

Android最佳Mock单元测试方案

我通过对比前辈们对各种单元测试框架的实践,总结出Android最佳Mock单元测试方案: Junit + Mockito + Powermock.(自己认证的…)

Junit + Mockito + Powermock 简介

众所周知,Junit是一个简单的单元测试框架。
Mockito,则是一个简单的用于Mock的单元测试框架。

那么为什么还需要Powermock呢?
EasyMock和Mockito等框架,对static, final, private方法均是不能mock的。
这些框架普遍是通过创建Proxy的方式来实现的mock。 而PowerMock是使用CGLib来操纵字节码而实现的mock,所以它能实现对上面方法的mock。

Junit + Mockito + Powermock 引入

由于PowerMock对Mockito有较强依赖,因此需要按照以下表格采用对应的版本。

Mockito PowerMock
2.0.0-beta - 2.0.42-beta 1.6.5+
1.10.8 - 1.10.x 1.6.2+
1.9.5-rc1 - 1.9.5 1.5.0 - 1.5.6
1.9.0-rc1 & 1.9.0 1.4.10 - 1.4.12
1.8.5 1.3.9 - 1.4.9
1.8.4 1.3.7 & 1.3.8
1.8.3 1.3.6
1.8.1 & 1.8.2 1.3.5
1.8 1.3
1.7 1.2.5

建议方案:
在项目依赖文件build.gradle中添加以下依赖。

1
2
3
4
5
6
7
testCompile 'junit:junit:4.11'
// required if you want to use Mockito for unit tests
testCompile 'org.mockito:mockito-core:1.9.5'
// required if you want to use Powermock for unit tests
testCompile 'org.powermock:powermock-module-junit4:1.5.6'
testCompile 'org.powermock:powermock-module-junit4-rule:1.5.6'
testCompile 'org.powermock:powermock-api-mockito:1.5.6'

Junit + Mockito + Powermock 配置

  1. 默认的测试代码位置
    对于通过gradle构建的android项目,在默认的项目结构中,Instrumentation的测试代码放在
    src/androidTest/ 目录,而JUnit / Mock的测试代码放在 src/test/ 目录。
  2. 自定义测试代码位置
    有些项目是由Eclipse构建迁移到由Gradle构建,需要自定义测试代码位置。
    举个例子,androidTest和test目录都在项目的根文件夹下。我们需要这样配置:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    android {
    sourceSets {
    test {
    java.srcDir 'test'
    }
    androidTest {
    java.srcDir 'androidTest'
    }
    }
    }

如果在单元测试中遇到类似”Method … not mocked.”的问题,请添加以下设置:

1
2
3
4
5
6
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}

Junit + Mockito + Powermock 使用

强烈建议你熟读以下内容,来熟悉Junit + Mockito + Powermock的使用。

  1. Mockito 中文文档 ( 2.0.26 beta )
  2. Mockito reference documentation
  3. powermock wiki
  4. Unit tests with Mockito - Tutorial

下面通过举例来简单说明Junit + Mockito + Powermock 使用,更多详情清参考Demo项目:
https://github.com/snowdream/test/tree/master/android/test/mocktest

源码: https://github.com/snowdream/test/blob/master/android/test/mocktest/app/src/main/java/snowdream/github/com/mocktest/Calc.java

测试代码:https://github.com/snowdream/test/blob/master/android/test/mocktest/app/src/test/java/snowdream/github/com/mocktest/CalcUnitTest.java

1.验证某些行为,主要是验证某些函数是否被调用,以及被调用的具体次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//following two verifications work exactly the same - times(1) is used by default
// 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//exact number of invocations verification
// 验证具体的执行次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
// 使用never()进行验证,never相当于times(0)
verify(mockedList, never()).add("never happened");
//verification using atLeast()/atMost()
// 使用atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");

2.验证执行顺序,主要验证某些函数是否按照预定顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// A. Single mock whose methods must be invoked in a particular order
// A. 验证mock一个对象的函数执行顺序
List singleMock = mock(List.class);
//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");
//create an inOrder verifier for a single mock
// 为该mock对象创建一个inOrder对象
InOrder inOrder = inOrder(singleMock);
//following will make sure that add is first called with "was added first, then with "was added second"
// 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
// B .验证多个mock对象的函数执行顺序
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//using mocks
firstMock.add("was called first");
secondMock.add("was called second");
//create inOrder object passing any mocks that need to be verified in order
// 为这两个Mock对象创建inOrder对象
InOrder inOrder = inOrder(firstMock, secondMock);
//following will make sure that firstMock was called before secondMock
// 验证它们的执行顺序
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
// Oh, and A + B can be mixed together at will

3.使用powermock必须使用两个annotation:

1
2
3
4
5
@RunWith(PowerMockRunner.class)
@PrepareForTest({Calc.class})
public class CalcUnitTest {
}
//PrepareForTest 后面要加准备被mock或stub的类,单个class直接()起来即可,多个用{},并用逗号隔开。

4.测试公开成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testPublicField() {
assertEquals(mCalc.mPublicField, 0);
assertEquals(mCalc.mPublicFinalField, 0);
assertEquals(Calc.mPublicStaticField, 0);
assertEquals(Calc.mPublicStaticFinalField, 0);
mCalc.mPublicField = 1;
Calc.mPublicStaticField = 2;
assertEquals(mCalc.mPublicField, 1);
assertEquals(mCalc.mPublicFinalField, 0);
assertEquals(Calc.mPublicStaticField, 2);
}

5.测试公开成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testAddPublicMethod() {
//when
when(mCalc.addPublic(anyInt(), anyInt()))
.thenReturn(0)
.thenReturn(1)
.thenReturn(2)
.thenReturn(3)
.thenReturn(4)
.thenReturn(5);
//call method
for (int i = 0; i < 6; i++) {
//verify
assertEquals(mCalc.addPublic(i, i), i);
}
//verify
verify(mCalc, times(6)).addPublic(anyInt(), anyInt());
verify(mCalc, atLeast(1)).addPublic(anyInt(), anyInt());
verify(mCalc, atLeastOnce()).addPublic(anyInt(), anyInt());
verify(mCalc, atMost(6)).addPublic(anyInt(), anyInt());
}

6.测试公开无返回值成员方法

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testAddPublicVoidMethod() {
//when
doNothing().when(mCalc).voidPublic(anyInt(), anyInt());
mCalc.voidPublic(anyInt(), anyInt());
mCalc.voidPublic(anyInt(), anyInt());
verify(mCalc, atLeastOnce()).voidPublic(anyInt(), anyInt());
verify(mCalc, atLeast(2)).voidPublic(anyInt(), anyInt());
}

7.测试公开静态成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void testAddPublicStaicMethod() throws Exception {
PowerMockito.mockStatic(Calc.class);
PowerMockito.when(Calc.class, "addPublicStatic", anyInt(), anyInt())
.thenReturn(0)
.thenReturn(1)
.thenReturn(2)
.thenReturn(3)
.thenReturn(4)
.thenReturn(5);
//call method
for (int i = 0; i < 6; i++) {
//verify
assertEquals(Calc.addPublicStatic(i, i), i);
}
//verify static
PowerMockito.verifyStatic(times(6));
}

8.测试私有成员变量
Powermock提供了一个Whitebox的class,可以方便的绕开权限限制,可以get/set private属性,实现注入。也可以调用private方法。也可以处理static的属性/方法,根据不同需求选择不同参数的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testPrivateField() throws IllegalAccessException {
PowerMockito.mockStatic(Calc.class);
assertEquals(Whitebox.getField(Calc.class, "mPrivateField").getInt(mCalc), 0);
assertEquals(Whitebox.getField(Calc.class, "mPrivateFinalField").getInt(mCalc), 0);
assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticField").getInt(null), 0);
assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticFinalField").getInt(null), 0);
Whitebox.setInternalState(mCalc, "mPrivateField", 1);
Whitebox.setInternalState(Calc.class, "mPrivateStaticField", 1, Calc.class);
assertEquals(Whitebox.getField(Calc.class, "mPrivateField").getInt(mCalc), 1);
assertEquals(Whitebox.getField(Calc.class, "mPrivateFinalField").getInt(mCalc), 0);
assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticField").getInt(null), 1);
assertEquals(Whitebox.getField(Calc.class, "mPrivateStaticFinalField").getInt(null), 0);
}

9.测试私有成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testAddPrivateMethod() throws Exception {
PowerMockito.mockStatic(Calc.class);
//when
PowerMockito.when(mCalc,"addPrivate",anyInt(),anyInt())
.thenReturn(0)
.thenReturn(1)
.thenReturn(2)
.thenReturn(3)
.thenReturn(4)
.thenReturn(5);
//call method
for (int i = 0; i < 6; i++) {
//verify
assertEquals(Whitebox.invokeMethod(mCalc,"addPrivate",i,i), i);
}
//verify static
PowerMockito.verifyPrivate(mCalc,times(6)).invoke("addPrivate",anyInt(),anyInt());
PowerMockito.verifyPrivate(mCalc,atLeast(1)).invoke("addPrivate",anyInt(),anyInt());
}

10.测试私有静态成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testAddPrivateStaicMethod() throws Exception {
PowerMockito.mockStatic(Calc.class);
PowerMockito.when(Calc.class, "addPrivateStatic", anyInt(), anyInt())
.thenReturn(0)
.thenReturn(1)
.thenReturn(2)
.thenReturn(3)
.thenReturn(4)
.thenReturn(5);
//call method
for (int i = 0; i < 6; i++) {
//verify
assertEquals(Whitebox.invokeMethod(Calc.class,"addPrivateStatic",i, i), i);
}
//verify static
PowerMockito.verifyStatic(times(6));
}

通过以上介绍,相信你对Android项目的Mock单元测试有一定的了解。
如果你有任何相关疑问,请通过以下方式联系我:

Email:yanghui1986527#gmail.com
QQ 群: 529327615

参考

  1. 详细讲解单元测试的内容
  2. 浅谈单元测试的意义
  3. 敏捷开发之测试
  4. Unit testing support
  5. junit4
  6. mockito
  7. powermock
  8. mocktest
  9. 在Android Studio中进行单元测试和UI测试
  10. whats-the-best-mock-framework-for-java
  11. mock测试
  12. 使用PowerMock来Mock静态函数
  13. PowerMock介绍
  14. Sharing code between unit tests and instrumentation tests on Android
  15. Unit tests with Mockito - Tutorial
  16. Android单元测试之Mockito浅析
  17. Mockito 简明教程
  18. mockito简单教程

一. Gradle源仓库(repositories)是什么东西,有什么用?

Gradle 源仓库(repositories)实际上复用了Maven的 源仓库(repositories)。

源仓库,主要用于托管项目构建输出和依赖组件的一个软件仓库。

这样说可能太抽象了。。。

举个例子:
开发者A,通过一个Android 库项目,构建出一个依赖aar包,然后上传至源仓库。
开发者B,通过在一个Android应用项目的build.gradle文件中声明依赖开发者A的库项目。构建过程中,gradle会自动去源仓库拉取改aar依赖包到本地,参与构建。(本地有缓存机制。如果上次拉取了,这次会使用本地缓存。)

二. 默认的仓库不可用?

对于Android应用,以前默认的官方仓库是mavenCentral(),后来修改成了jcenter()。

这两个官方仓库默认是通过https来访问的。很可能在国内无法正常使用。

如果你无法正常从源仓库拉取依赖包,有几个办法:
1.改用http访问
maven { url “http://oss.sonatype.org/content/repositories/snapshots“ }
jcenter { url “http://jcenter.bintray.com/"}
maven { url “http://repo1.maven.org/maven2"}
maven { url “https://jitpack.io“ }
其中,
jcenter { url “http://jcenter.bintray.com/"} 替换 jcenter()
maven { url “http://repo1.maven.org/maven2"} 替换 mavenCentral()
如果你需要用到snapshots包,建议添加 maven { url “http://oss.sonatype.org/content/repositories/snapshots“ }
而另外一些包被托管在jitpack.io网站,你可以通过maven { url “https://jitpack.io“ }仓库进行访问。

2.采用国内Maven仓库镜像
最早是oschina做了maven镜像,不过很遗憾,稳定性太差,目前处于基本不可用的状态。
目前的做法是:
推荐阿里云的maven镜像:
maven { url ‘http://maven.aliyun.com/mvn/repository/‘ }

3.通过vpn或者其他工具fq

三. Maven 搜索查询

比如:android插件
classpath ‘com.android.tools.build:gradle:1.3.1’
有哪些版本,最新版本号是多少?

你可以通过以下Maven网站/镜像进行查询。
http://maven.aliyun.com/nexus/#welcome
http://search.maven.org/
https://oss.sonatype.org/

接上一篇 Android项目持续集成实践之Gitlab CI.

在我看来,.gitlab-ci.yml 配置还是有些复杂,写的脚本还是有点多,有没有办法更精简一点呢?

有,那就是Android环境Docker化。(注:对Docker感兴趣的同学,请参考这本书《Docker —— 从入门到实践》)。

我在这本书的指导下封装了一个包含Android开发环境的Docker镜像。

  1. https://github.com/snowdream/docker-android
  2. https://hub.docker.com/r/snowdream/docker-android/

现在有了合适的Docker镜像,.gitlab-ci.yml 将会变得非常简单:

1
2
3
4
5
6
7
8
image: snowdream/docker-android:1.0
build:
script:
- gradle assembleRelease
artifacts:
paths:
- app/build/outputs/

第一行的意思是,采用标签为1.0,名称为snowdream/docker-android的Docker镜像,用于本工程的CI环境。

是不是很简单呢?

详细的构建过程日志太长,我就不贴了。链接如下:
https://gitlab.com/snowdream/Citest/builds/2155883

如果你在使用过程中,碰到什么问题,可以通过以下方式联系我:

  • Email:yanghui1986527#gmail.com
  • QQ Group: 529327615

简介

持续集成(Continuous integration)是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

入门

下面我们来简单介绍,如果通过Gitlab CI来对Android项目持续集成。

一言不合,先甩给你一个项目链接:
https://gitlab.com/snowdream/Citest

项目很简单,就是一个默认创建的Android项目,然后上传至Gitlab。

如果给项目添加持续集成功能呢?
按照文档的说法,你需要给项目添加一个名称为.gitlab-ci.yml的配置文件。

.gitlab-ci.yml文件怎么写??此处省略108个字。
通读下面两篇文章,大概就清楚了。
http://doc.gitlab.com/ce/ci/quick_start/README.html
http://doc.gitlab.com/ce/ci/yaml/README.html

当然,也许你读完了,还是感觉蒙了。那你还需要参考下别人怎么实践的。

  1. http://doc.gitlab.com/ce/ci/quick_start/README.html
  2. http://doc.gitlab.com/ce/ci/yaml/README.html
  3. http://www.greysonparrelli.com/setting-up-android-builds-in-gitlab-ci/
  4. https://github.com/asura-app/android/blob/master/.gitlab-ci.yml
  5. https://github.com/lfuelling/android-sdk-docker
  6. https://hub.docker.com/r/jangrewe/gitlab-ci-android/
  7. http://blog.goddchen.de/2016/04/configuration-for-gitlab-ci-android-projects/
  8. http://stackoverflow.com/questions/35916233/gitlab-com-ci-shared-runner-for-android-projects

实践

下面是重点:
基本流程是:

  1. Gitlab Ci通过Docker来拉取包括openjdk-8-jdk的容器
  2. 下载Android SDK
  3. 通过Gradle Wrapper运行编译工程

下面是主菜:
适用于Android项目的 .gitlab-ci.yml 文件
当然,在实际过程中,你可以需要做一些调整,比如android sdk 中的版本号等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
image: java:openjdk-8-jdk
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
- wget --quiet --output-document=android-sdk.tgz https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
- tar --extract --gzip --file=android-sdk.tgz
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-23
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-23.0.3
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services
- echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository
- export ANDROID_HOME=$PWD/android-sdk-linux
- chmod u+x ./gradlew
build:
script:
- ./gradlew assembleRelease
artifacts:
paths:
- app/build/outputs/

好了。将.gitlab-ci.yml 添加到你的Android项目中,然后上传至Gitlab系列的Git服务器,就开始持续集成了。

详细的构建过程日志太长,我就不贴了。链接如下:
https://gitlab.com/snowdream/Citest/builds/2140420

总结

与Travis Ci相比,Gitlab CI更灵活,可定制性高,但也意味着用起来并不是那么容易。
Travis Ci 更倾向于提供一个开箱即用的 CI服务。
而 Gitlab CI 更倾向于提供一个定制化的CI服务,比如支持Docker。
以上只是对于通过Gitlab CI对Android项目进行持续集成的简单实践。
如果感兴趣,大家可以思考下下面的问题:

  1. 怎么通过Gitlab CI进行持续发布?
  2. 怎么在Gitlab CI 加密字符串和文件,比如keystore文件?
  3. 怎么在Gitlab CI中进行交互性操作,比如输入密码?
  4. 怎么在过Gitlab CI中使用缓存?

Fork me on GitHub