在移动应用开发和运营的过程中,版本管理是一个老生常谈的基础问题,一些版本的基本概念也常常会困扰我们的研发和运营人员。同时,手动管理软件版本,也常常会因为不小心导致后续的发布和更新问题。
这里,我准备了一些 iOS 和 Android 版本的基础知识,以及如何在应用中获取版本信息和如何使用 Xcode 和 Android Studio 自动管理版本号。
版本号解释
无论是 iOS 还是 Android 都定义了两个版本属性:
-
iOS
在Info.plist
中定义-
CFBundleShortVersionString
在Xcode中解释为Version
,这个就是我们常说的版本号,一般用户可见,通常由<主版本号>.<次版本号>.<维护号>
三部分组成,主要用来识别不同时期不同功能的产品。 -
CFBundleVersion
在Xcode中解释为Build
,一般用于应用市场和程序内部识别版本,作为更新判断的依据,通常是一个递增的 INT 类型。这两个值可以在 Xcode 的项目信息里面进行管理,
当然,你也可以直接编辑Info.plist
。
-
-
Android
在AndroidManifest.xml
中定义-
android:versionName
对应 iOS 中的CFBundleShortVersionString
版本号,用作产品管理。 -
android:versionCode
对应 iOS 中的CFBundleVersion
编译号,作为内部识别。在使用 Eclipse 开发时,可以通过直接编辑
AndroidManifest.xml
文件修改,在使用 Android Studio 时,这些信息由 Gradle 脚本管理,找到并打开项目目录下 app 目录内的build.gradle
文件,版本信息在defaultConfig
段,
-
程序内获取版本信息
一般在应用的关于页面,我们都会显示应用的版本,方便客服定位问题,在应用检查更新时,也常常需要用到版本信息。其实,无论是在 iOS 上,还是 Android 平台,获取笨笨信息都比较简便:
-
iOS
索引 Bundle 信息中的相关字段:NSBundle *bundle = [NSBundle mainBundle]; NSString *name = [[bundle localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"]; NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; NSString *build = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; NSString *fullVersion = [NSString stringWithFormat:@"version: %@ (%@)", version, build];
上述的代码中,
name
为本地化的程序名称,version
为版本号,build
为编译号,'fullVersion' 则将版本号和编译号组成一个完整的版本信息。 -
Android
获取PackageInfo
中的相关信息:PackageInfo pi = sContext.getPackageManager().getPackageInfo(sContext.getPackageName(), 0); String versionName = pi.versionName; int versionCode = pi.versionCode; String fullVersion = String.format("version: %s (%d)", versionName, versionCode);
同样,
versionName
为版本号,versionCode
为编译号,不同的是,在 Android 中versionCode
使用 int 类型存储。
Xcode 和 Android Studio 编译号自增
一般应用的 版本号 都会由 产品经理 或 项目经理 决定,根据产品所处的阶段和功能决定修改版本号的哪一个段。但 编译号 更多时候是根据每次发布递增,有时候还会遇到同一个版本号对应多个编译号的情况,如,紧急修复了一个关键 Bug 并更新发布一个版本,但如果每次发布都要手动去修改编译号回很繁琐,也很容易忘记和出错,而不管是 Xcode 还是 Android Studio 都没有提供自增编译号的功能,我们只有手动添加编译脚本来达到自增的目的。
-
Xcode
添加编译过程,读取并修改Info.plist
中的版本信息:-
打开工程,选择编译目标,点击
Build Phases
选项卡。
-
点击左上角的 + 添加,选择
New Run Script Phase
添加一个编译脚本。
-
在脚本编辑其中输入下面的脚本:
#!/bin/bash buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE") buildNumber=$(($buildNumber + 1)) /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"
上述脚本使用 PlistBuddy工具,读取
Info.plist
中的CFBundleVersion
值,+1 后写回Info.plist
中。 -
我们需要让该段代码在应用打包前执行,以便将修改应用到App包内,因此我们将该编译过程重命名为
Increase Version Code
并移动到Copy Bundle Resources
之前。
-
这样,当我们每次编译该 Target 时 ,就会执行改脚本将
CFBundleVersion
值增加1,但没次调试运行时,该值都会加1,这并不是我们想要的,我们只需要在每次打包发布时增加1就行,因此,我们将Run script only wehn installing
前的勾打上,这样就只会在打包应用时执行改脚本。
-
-
Android Studio
Android Studio 的 versionCode 自增原理和 Xcode 类似,不同的是我们不能让编译脚本修改自身,而是通过一个额外的 Java Properties 来文件存储版本信息:-
打开工程,选择
Module: app
的编译脚本build.gradle
。
-
找到
defaultConfig
段,替换为下面的脚本:def versionPropsFile = file('version.properties') if (versionPropsFile.canRead()) { Properties versionProps = new Properties() versionProps.load(new FileInputStream(versionPropsFile)) def verCode = versionProps['VERSION_CODE'].toInteger() versionProps['VERSION_CODE'] = (++verCode).toString() versionProps.store(versionPropsFile.newWriter(), null) defaultConfig { applicationId "com.example.versionexample" minSdkVersion 19 targetSdkVersion 23 versionCode verCode versionName "1.0.1" } } else { throw new GradleException("Could not read version.properties!") }
这段脚本会打开 app 目录下的
version.properties
配置文件,读取VERSION_CODE
字段并增加1后写回,同时将值赋给defaultConfig
中的versionCode
。 -
此时,点击 Sync 会报错
Could not read version.properties!
,这是我们刚刚在脚本中抛出的,原因是找不到version.properties
文件,我们需要新建一个。打开命令行定为到项目目录下:$ cd app/ $ echo "VERSION_CODE=1" > version.properties
再次点击 Sync 就不会报错了。查看
version.properties
文件,$ cat version.properties #Mon Nov 02 15:18:49 CST 2015 VERSION_CODE=3
发现 VERSION_CODE 已经增加到 3 了,说明该脚本被执行了两次,但这并不符合我们的预期。
-
同 Xcode 中一样,我们期望仅仅在应用打包时将
versionCode
增加 1 。因此我们需要获取编译参数,仅当release
时才将versionCode
增加 1 。修改后的脚本如下:def versionPropsFile = file('version.properties') if (versionPropsFile.canRead()) { Properties versionProps = new Properties() versionProps.load(new FileInputStream(versionPropsFile)) def verCode = versionProps['VERSION_CODE'].toInteger() def runTasks = gradle.startParameter.taskNames if (':app:assembleRelease' in runTasks) { versionProps['VERSION_CODE'] = (++verCode).toString() versionProps.store(versionPropsFile.newWriter(), null) } defaultConfig { applicationId "com.example.versionexample" minSdkVersion 19 targetSdkVersion 23 versionCode verCode versionName "1.0.1" } } else { throw new GradleException("Could not read version.properties!") }
这里获取当前task的名称,仅当task包含
:app:assembleRelease
时才会将versionCode
加 1 ,否则直接使用读取到的值。
-
小结
版本管理是产品和项目管理中非常重要的一环,但在开发初期也常常容易被忽略,等到遇到问题时候才感到头痛。其实,产品经理和开发人员在产品的立项阶段就应该对产品版本有一个一致的理解,我个人通常在项目框架建立之初就将这些规则脚本添加到项目文件中,以期降低后续版本维护的成本。