目的
app開(kāi)發(fā)測(cè)試過(guò)程,可能會(huì)發(fā)生以下各種情況:
- 測(cè)試機(jī)器上,app會(huì)被不斷替換,需要信息來(lái)定位當(dāng)前app的狀態(tài)。
- apk信息僅附加在命名上,安裝后無(wú)法跟蹤。
- 版本號(hào)在開(kāi)發(fā)過(guò)程沒(méi)有遞增,并不清楚當(dāng)前測(cè)試的是哪個(gè)版本,也不知道當(dāng)前出現(xiàn)了bug的是哪個(gè)版本。
- 雖然版本號(hào)遞增了,但并不清楚版本號(hào)對(duì)應(yīng)的功能點(diǎn)或時(shí)間節(jié)點(diǎn),需要查看構(gòu)建歷史才能定位,如果機(jī)器遠(yuǎn)離辦公室,定位過(guò)程尤為繁瑣,甚至可能需要VPN。
- 版本號(hào)需要和commit-id綁定,才能跟蹤當(dāng)前版本號(hào)對(duì)應(yīng)的提交節(jié)點(diǎn)。
- commit-id仍舊不夠直觀,在外需要依賴VPN。
以上問(wèn)題,都會(huì)導(dǎo)致測(cè)試過(guò)程出現(xiàn)問(wèn)題但無(wú)法立即定位問(wèn)題,有時(shí)是因?yàn)槭褂昧伺f的版本;有時(shí)是因?yàn)橹型景姹颈惶鎿Q了;有時(shí)它確實(shí)是個(gè)Bug。但無(wú)論如何,定位問(wèn)題不僅浪費(fèi)了測(cè)試的時(shí)間也降低了開(kāi)發(fā)的效率。
給app埋入構(gòu)建信息
在app構(gòu)建時(shí),埋入構(gòu)建的時(shí)間/地點(diǎn)/參數(shù)/版本信息能有助于分析問(wèn)題。
比如,本地構(gòu)建與Jenkins上的構(gòu)建,不同的構(gòu)建使用的環(huán)境和參數(shù)不一樣,構(gòu)建出來(lái)的apk也很有可能不一樣。具體可以有:
- Debug/Release
- 非混淆/混淆
- 本地構(gòu)建/Jenkins構(gòu)建
- 構(gòu)建時(shí)間戳
- 版本號(hào)
- 渠道號(hào)
給app埋入提交日志
除了埋入commit-id,埋入適當(dāng)?shù)娜罩疽彩强梢愿涌旖莸亩ㄎ粏?wèn)題。
比如,看到提交日志信息和最近提交的不一致,則可以立即斷定版本是舊的。通過(guò)commit-id/版本號(hào)等無(wú)法做到立即定位。
提交日志具體可以有:
- 當(dāng)前構(gòu)建對(duì)應(yīng)的commit的 id/hash
- 當(dāng)前構(gòu)建對(duì)應(yīng)的commit的 提交人(建議拼音)
- 當(dāng)前構(gòu)建對(duì)應(yīng)的commit的 提交時(shí)間戳
- 當(dāng)前構(gòu)建對(duì)應(yīng)的commit的 具體日志信息
安全意識(shí),提交人建議使用拼音,不要添加郵件。
對(duì)于git,對(duì)應(yīng)的命令行:
git rev-list --abbrev-commit --max-count=1 --format=%an_%ar_%ai_%n%B%n HEAD
git rev-list HEAD 查看log列表
--abbrev-commit 使用短hash
--max-count 顯示commit數(shù)量
--format 格式化輸出(具體%an %ar %B 等參考git log --help)
輸出:
commit b1ab959
jokin_18 hours ago_2018-01-05 17:21:39 +0800_
Merge branch 'f/add_app_info' into 'develop'
[feature] 添加Trick來(lái)顯示App詳細(xì)信息
使用gradle給app埋入自定義信息
- 在gradle.properties里
MAJOR_VERSION=0
MINOR_VERSION=1
- 然后在build.gradle里
def genVersionName() {
String versionName = System.getenv("MH_VERSION_NAME")
if (versionName != null) {
return versionName
} else {
return "U."+MAJOR_VERSION+"."+MINOR_VERSION
}
}
def genVersionCode() {
String versionCode = System.getenv("MH_VERSION_CODE")
if (versionCode != null) {
return Integer.parseInt(versionCode)
} else {
return 1
}
}
def getBuildDate() {
String date = new Date().format('yyyy-MM-dd HH:mm:ss')
println date
return date
}
def getBuildFrom() {
String tag = System.getenv("BUILD_TAG")
boolean inJenkins = tag != null && tag.contains("jenkins")
if (inJenkins) {
return "Jenkins"
} else {
return "Local"
}
}
def getGitCommit() {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--abbrev-commit', '--max-count=1', '--format=%an_%ar_%ai_%n%B%n', 'HEAD'
standardOutput = stdout
}
// 轉(zhuǎn)義字符轉(zhuǎn)換
// String commit = "'\'\\a\\sb\\c\\bdfasdf\\/f/d/e\"\n\n\n\n\r\r\r\r\b\b\t\t\f\f\\'''''''\''"
String commit = stdout.toString().trim();
commit = commit.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
.replace('\b', '\\b').replace('\t', '\\t').replace('\f', '\\f').replace('"', '\\"')
println commit
return commit
}
- 然后在defaultConfig里
defaultConfig {
versionCode genVersionCode()
versionName genVersionName()
buildConfigField "String", "BuildDate", "\"${getBuildDate()}\""
buildConfigField "String", "BuildFrom", "\"${getBuildFrom()}\""
buildConfigField "String", "GitCommit", "\"${getGitCommit()}\""
}
- 同步后,將會(huì)生成BuildConfig.java
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "platform";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "U.0.1";
// Fields from default config.
public static final String BuildDate = "2018-01-06 09:53:37";
public static final String BuildFrom = "Local";
public static final String GitCommit = "commit b1ab959\njokin_17 hours ago_2018-01-05 17:21:39 +0800_\nMerge branch 'f/add_app_info' into 'develop'\n\n[feature] 添加Trick來(lái)顯示App詳細(xì)信息\n\n";
}
- 使用時(shí),直接BuildConfig.GitCommit訪問(wèn)
private void showAppInfo() {
String appInfo = "Version: "+BuildConfig.VERSION_NAME
+ "\nBuild At: "+BuildConfig.BuildDate
+ "\nBuild From: "+BuildConfig.BuildFrom
+ "\n\nLatest Commit: "+BuildConfig.GitCommit;
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
釋義
- 字符串轉(zhuǎn)換為gradle屬性值時(shí)進(jìn)行了轉(zhuǎn)義字符的替換。
原因是,gradle的屬性值是直接將字符串打印結(jié)果作為內(nèi)容,直接寫(xiě)到BuildConfig.java里面。舉個(gè)例子。
String commit = "abc\ndef";
轉(zhuǎn)換為gradle屬性,得到的是
"abc
def"
這是字符串的打印結(jié)果,并非原始的字符串。字符串的換行在BuildConfig.java里是
public final class BuildConfig {
public static final String GitCommit = "abc
def";
}
這是非法的字符串定義!將導(dǎo)致編譯失??!
所以,我們需要將打印出的換行符轉(zhuǎn)換為常量定義:
"abc\ndef"
所以,將字符串變量轉(zhuǎn)換為常量字符串的格式,才是正確的。問(wèn)題就變成了:知道一個(gè)字符串變量,如何輸出其常量定義?只需要對(duì)轉(zhuǎn)義字符進(jìn)行轉(zhuǎn)換即可。
java中轉(zhuǎn)義字符
\a
\b
\f
\n
\r
\t
\v
\\
\' (java中常量輸出 ' 不需要做轉(zhuǎn)義)
\"
\0
\ddd
\xhh
字符轉(zhuǎn)換為對(duì)應(yīng)的轉(zhuǎn)義字符串
'\r' -> "\\r" // 要輸出\r,是\\r
'\n' -> "\\n" // 要輸出\n,是\\n
'\' -> "\\" // 要輸出\,常量是\\(怎么表示\腳本語(yǔ)言中是'\\')
'"' -> "\\\"" // 常量字符串\",輸出的是",要輸出\",是"\\\"",對(duì)于腳本語(yǔ)言,可以是'\\"'
'\'' -> "\\'" // 要輸出\',常量是"\\'"
以此類(lèi)推。
注意的是,腳本語(yǔ)言中,' 與 " 都是表示字符串。但兩者需要轉(zhuǎn)義的符號(hào)不同。