更优雅的 Android 发布自动版本号方案

以前看到一些自动化版本号打包的文章。如果您的项目是用 Git 管理的,并且恰巧又是使用 Gradle 编译(应该绝大部分都是这样的了吧?),本文试图找到一种更加优雅的自动版本管理方法。

1 背景

我们都知道,Android 应用的版本管理是依赖 AndroidManifest.xml 中的两个属性:

  • android:versionCode:版本号,是一个大于 0 的整数,相当于 Build Number,随着版本的更新,这个必须是递增的。大的版本号,覆盖更新小的版本号;
  • android:versionName:版本名,是一个字符串,例如 "1.2.0",这个是给人看的版本名,系统并不关心这个值,但是合理的版本名,对后期的维护和 bug 修复也非常重要。

在使用了 Android Studio 或者 Gradle 编译以后,我们通常是在 build.gradle 里面定义这两个值,如下:

android {  
    ...
    defaultConfig {
        ...
        versionCode 1
        versionName "1.0"
    }
}

2 自动版本号

在这篇文章中 6 tips to speed up your Gradle build 发现了,可以使用 Git 中 commit 的数量来作为版本号(versionCode)。方案如下:

def cmd = 'git rev-list HEAD --count'  
def gitVersion = cmd.execute().text.trim().toInteger()

android {  
  defaultConfig {
    versionCode gitVersion
  }
}

这里关键是这一行 git 命令 git rev-list HEAD --count,表示获取当前分支的 commit 数量。

注:谢谢 @zhangls 提醒。原文直接使用了 6 tips to speed up your Gradle build 中的命令 git rev-list HEAD --first-parent --count,也就是带了 --first-parent 选项。发现使用此选项并不合理,比如 这篇文章 中提到的,first-parent 可能会出现不稳定的情况,这就可能导致获得的 commit 数量并不保证是递增的。

这是一个绝妙的方案。因为在项目开发中,我们的往 Git 库中提交的 Commit 的数量应该是只增不减的(当然,在极少的情况下有例外),而且对应 Commit 的数量直接对应代码当前的版本状态,只要你做了代码修改,版本号就应该增加。有些解决方案中,每次 Build 就会增加一次版本号,个人感觉并不合适,如果是相同的代码,发布出去版本号应该保持一致,而不在于你编译多少次。

另外,有些人可能会担心,每次版本发布,可能会包含几百个新的 commit,这样的话 versionCode 会不会增长太快了,最后导致不够用了。其实,完全没有必要担心,versionCode 是 int 类型,最大值是 2^31-1,也就是 21 亿多,Android 源码中,改动最活跃的 framework/base 所有分支到目前为止也就 20 万多个 commit,所以完全够用了。

3 自动版本名

前面通过一条简单的命令实现了自动化的 versionCode,现在我们看怎么自动化 versionName

在正常的发布流程中,在发布新版本的时候,都会在版本库中打 tag。一般情况下,tag 名就是版本名,而且也建议这么做,因为如果某个版本出现 bug,也可以正好 checkout 这个 tag 来查看代码。所以,现在的问题就是怎么自动获得 git 库中最新的最新 tag?原来,git 早就提供了命令 git describe,它的功能就是获取从当期 commit 到距离它最近的 tag 的描述。默认都是 annoted tag,如果要指所有的类型的 tag 的话,就加 --tags 参数。

此命令的详细介绍在这里:git-describe。举例一个简单的例子,假如你的当前代码状态如下:

--A--B-...-C-->
     |     |
   v1.0  v1.1

执行 git describe 的结果是:v1.1,如果是如下的情况:

--A--B-...-C--D-->
     |     |
   v1.0  v1.1

执行 git describe 的结果是:v1.1-1-gXXXXXX,其中 1 表示当前代码距离最近的 tag v1.1 一个 commit,最新的 commit 的 id 是 XXXXXX

可见,describe 命令很好的描述了当前的分支的版本状态,我们可以直接使用这个它的输出作为版本号。在 build.gradle 中的使用如下:

def cmd = 'git describe --tags'  
def version = cmd.execute().text.trim()

android {  
  defaultConfig {
    versionName version
  }
}

这样就可以自动抽取 git 中的 tag 为版本名了。有些同学可能接受不了这样版本名字 v1.1-1-gXXXXXX,这里也可以稍微做一些修改,使版本号更好看,如下:

def pattern = "-(\\d+)-g"  
def matcher = version =~ pattern

if (matcher) {  
    version = version.substring(0, matcher.start()) + "." + matcher[0][1]
} else {
    version = version + ".0"
}

这样的话,上面的版本名就变为了 v1.0.0v1.1.1 了。

4 优化

前面的那篇文章中说了,为了尽可能减少 gradle 脚本的运算,提高开发速度,我们可以把这样的自动版本的计算放到 release 编译中去。最后的写法如下:

def gitVersionCode() {  
    def cmd = 'git rev-list HEAD --first-parent --count'
    cmd.execute().text.trim().toInteger()
}

def gitVersionTag() {  
    def cmd = 'git describe --tags'
    def version = cmd.execute().text.trim()

    def pattern = "-(\\d+)-g"
    def matcher = version =~ pattern

    if (matcher) {
        version = version.substring(0, matcher.start()) + "." + matcher[0][1]
    } else {
        version = version + ".0"
    }

    return version
}

android {  
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.race604.example"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName '1.0'
    }
    buildTypes {
        debug {
            // 为了不和 release 版本冲突
            applicationIdSuffix ".debug"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    applicationVariants.all { variant ->
        if (variant.buildType.name.equals('release')) {
            variant.mergedFlavor.versionCode = gitVersionCode()
            variant.mergedFlavor.versionName = gitVersionTag()
        }
    }
}

至此,结合 git 和 gradle 我们就实现了自动版本号。

Read more

Android 上的低功耗蓝牙实践

这是我在 Droidcon Beijing 2016 和 GDG Devfest 2016 上做的分享,以下是正文: Slide 01 我今天分享的主题是 Android 上低功耗蓝牙的实践。这个主题比较小众。我在过去的一年多的时间里,主要是在做低功耗蓝牙相关的开发。接触过程中发现,BLE 的开发和通常的 Android APP 的开发有点不一样,这里需要访问硬件资源,而且涉及到一些协议相关的内容,而且这方面的资料也比较少。今天我从 Android 开发者的角度,来分享一下低功耗蓝牙开发实践。 Slide 02 今天分享的内容,主要包含如下几个部分:首先对蓝牙和低功耗蓝牙做一个简单的介绍;然后介绍 Android 上对低功耗蓝牙的支持;再介绍一下在 Android 平台上可以开发哪些低功耗蓝牙应用;然后是,开发过程中,可以帮助我们调试的工具;最后,总结一下所谓的 “最佳实践”,低功耗蓝牙开发的一些小经验。 Slide

By Race604

React Native 触摸事件处理详解

触控是移动设备的核心功能,也移动应用交互的基础,Android 和 iOS 各自都有完善的触摸事件处理机制。React Native(以下简称 RN)提供了一套统一的处理方式,能够方便的处理界面中组件的触摸事件、用户手势等。本文尝试介绍 RN 中触摸事件处理。 1. RN 基本触摸组件 RN 的组件除了 Text,其他组件默认是不支持点击事件,也不能响应基本触摸事件,所以 RN 中提供了几个直接处理响应事件的组件,基本上能够满大部分的点击处理需求TouchableHighlight, TouchableNativeFeedback, TouchableOpacity 和 TouchableWithoutFeedback。因为这几个组件的功能和使用方法基本类似,只是 Touch 的反馈效果不一样,所以一般我们用 Touchable** 代替。Touchable** 有如下几个回调方法: * onPressIn:点击开始; * onPressOut:点击结束或者离开; * onPress:单击事件回调; * onLongPress:长按事件回调。 它们的基本使用方法如下,

By Race604

React Native 中 ScrollView 性能探究

1 基本使用 ScrollView 是 React Native(后面简称:RN) 中最常见的组件之一。理解 ScrollView 的原理,有利于写出高性能的 RN 应用。 ScrollView 的基本使用也非常简单,如下: <ScrollView> <Child1 /> <Child2 /> ... </ScrollView> 它和 View 组件一样,可以包含一个或者多个子组件。对子组件的布局可以是垂直或者水平的,通过属性 horizontal=true/false 来控制。甚至还默认支持“下拉”刷新操作。另外还有一个特别赞的特性,超出屏幕的 View 会自动被移除,从而节省资源和提高绘制效率。我们来看如下一个例子: class

By Race604

30 天入门 Android 开发, Google 与你一起圆梦

经常会有朋友让我推荐 Android 开发入门的教程或者视频,我一直是推荐看官方的教程。大部分人或者觉得比较迷茫,或者觉得坚持不下去。这次推荐这个《30 天入门 Android 开发》是 Google 亲自发起的免费教学,以学习小组方式,大家可以一起学习和交流。一个好的开始,是成功的一半。让 Google 工程师带领你一起进入多彩的 Android 开发大门。点击这里 报名。 Android 设备已经随处可见,你想尝试一下在 Android 设备上的开发和创新吗?快来跟随 Google 的步伐,一起学习 Android 入门课吧! Google Study Jams 活动介绍 Study Jams 是一个学习 Google 在线课程的活动。该活动由学员自主发起课程学习小组,带领小组成员入门 Android 开发,最终将

By Race604