相信各位都用過或聽過使用GitHub做爲遠程代碼倉庫。但GitHub的功能可不只僅是管理存放代碼,你能夠把任何文件放在GitHub上,甚至能夠把它看成網盤來使用。因此,做爲沒有服務器 (沒錢) 的學生和懶得 (不會) 本身動手搭後臺的我,嘗試使用GitHub來實現簡單的App版本更新。java
一個用戶體驗比較好的更新一般有兩個步驟,一個是詢問服務器當前是否有更新,有更新後提醒用戶,再由用戶選擇是否下載更新。git
檢測更新這一步驟,咱們就能夠在咱們的代碼倉庫中放一個文件,文件的內容是當前的版本信息,好比,可使用json格式:程序員
{
"code": 2,
"name": "0.2.0",
"filename": "EveryDownload-release-0.2.0.apk",
"url": "https://raw.githubusercontent.com/SirLYC/EveryDownload/master/update/EveryDownload-release-0.2.0.apk",
"time": 1562244720615,
"des": "1. \u6dfb\u52a0\u68c0\u67e5\u66f4\u65b0\u529f\u80fd\n2. \u7f8e\u5316`\u5173\u4e8e\u9875\u9762`",
"size": 3339869,
"md5": "09283d79f77e9a162b8edc6811ecfe42"
}
複製代碼
怎麼獲取到文件內容?在你的代碼倉庫查看文件處點擊RAW便可查看文件內容。github
如圖所示,上面的url就是你App能夠直接經過HTTP訪問的,從數據流讀取數據後就能夠獲得字符串。能夠把這個理解爲後端提供的API:json
https://raw.githubusercontent.com/${GitHub用戶名}/${項目名}/${分支名}}/${相對於項目根目錄的路徑}}
後端
經過這個接口,不只能訪問文本文件,還能夠訪問你公開倉庫的任何文件,而後就能夠下載到本地了。bash
注意:若是是私有倉庫,沒法用這個API訪問,有一個
token
參數,具體能夠看GitHub的開發者API文檔。服務器
這裏的例子中,code就是build時的versionCode,App讀取json後與當前versionCode比較便可知道是否須要更新(比較方式不惟一,使用versionCode是比較常見的)app
if (info.code > BuildConfig.VERSION_CODE) {
// do update
} else {
// already updated
}
複製代碼
更新就相對來說比較簡單了。既然能夠把GitHub做爲「網盤」,咱們同樣能夠在Git中將打包好的apk文件也commit push上去,而後找到對應路徑就能夠下載下來。ide
按照上面的思路,每次更新都須要修改versionCode,versionName,而後還要處理json文件,apk文件...萬一push弄錯了一次,有強迫症的我又要amend commit -> force push
一鼓作氣了 (逃)。做爲一個智慧 (懶惰) 的程序員,這種任務確定就應該交給程序去作啦~
在閱讀下文以前,但願各位看官先行了解一下gradle中Task、Action的概念。我也是現學現賣,有不對的地方也請各位指正~
咱們日常不論是點擊運行仍是Build或者Clean,實際背後都是由一堆Gradle的Task
來完成這些事,只是Android Studio將這些過程圖形化了,沒去專門瞭解的話可能感覺不到。
能夠簡單的看一下在buildType爲release
時build project執行的gradle任務:
首先,咱們能夠定義一個函數,用於將生成的apk移動到目標文件夾,並生成更新時查詢的json:
// 計算apk的md5
static String generateMD5(File file) {
if (!file.exists() || !file.isFile()) {
return null
}
def digest = MessageDigest.getInstance("MD5")
file.withInputStream() { is ->
byte[] buffer = new byte[8192]
int read
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read)
}
}
return digest.digest().encodeHex().toString()
}
void generateUpdateInfo(String apkName) {
println("------------------ Generating version info ------------------")
// 把apk文件從build目錄複製到根項目的update文件夾下
def apkFile = project.file("build/outputs/apk/release/$apkName")
if (!apkFile.exists()) {
throw new GradleScriptException("apk file not exist!")
}
def toDir = rootProject.file(buildInfo.updatePath)
String apkHash = generateMD5(apkFile)
def updateJsonFile = new File(toDir, buildInfo.updateInfoFilename)
def writeNewFile = true
// 若是有之前的json文件,檢查此次打包是否有改變
if (updateJsonFile.exists()) {
try {
def oldUpdateInfo = new JsonSlurper().parse(updateJsonFile)
if (buildInfo.versionCode <= oldUpdateInfo.code && apkHash == oldUpdateInfo.md5) {
writeNewFile = false
}
} catch (Exception e) {
writeNewFile = true
e.printStackTrace()
updateJsonFile.delete()
}
}
if (writeNewFile) {
def oldFiles = toDir.listFiles()
oldFiles.each {
if (!it.delete()) {
it.deleteOnExit()
}
}
copy {
from(apkFile)
into(toDir)
}
// 建立json的實體類
// Expando能夠簡單理解爲Map
def updateInfo = new Expando(
code: buildInfo.versionCode,
name: buildInfo.versionName,
filename: apkFile.name,
url: "${buildInfo.updateBaseUrl}${apkFile.name}",
time: System.currentTimeMillis(),
des: buildInfo.versionDes,
size: apkFile.length(),
md5: apkHash
)
String newApkHash = generateMD5(new File(toDir, apkName))
println("new apk md5: $newApkHash")
def outputJson = new JsonBuilder(updateInfo).toPrettyString()
println(outputJson)
// 將json寫入文件中,用於查詢更新
updateJsonFile.write(outputJson)
} else {
// 不須要更新
println("This version is already released.\n" +
"VersionCode = ${buildInfo.versionCode}\n" +
"Skip generateUpdateInfo.")
}
println("------------------ Finish Generating version info ------------------")
}
複製代碼
由於只在release包時須要生成版本信息,因此在buildType爲release
時才須要生成版本信息,因此這裏要作一下判斷,只在release時將函數的添加到Task:
applicationVariants.each { variant ->
// 同一App名,方便操做
def apkName = "EveryDownload-${variant.buildType.name}-${defaultConfig.versionName}.apk"
variant.outputs.all {
outputFileName = apkName
}
// 只在release添加
if (variant.buildType.name == "release") {
直接添加到Task的Action隊尾,build執行完成後就能夠執行這個函數
variant.assembleProvider.get().doLast {
generateUpdateInfo(apkName)
}
}
}
複製代碼
使用命令行構建,方便看到輸出結果:
./gradlew app:assembleRelease
複製代碼
能夠看到函數打印結果:
而後根目錄有了這兩個文件:
其餘不動,再build一次,能夠看到沒有從新生成:
到這裏,就成功解放雙手啦~
這個方案直接用在了個人下載器中(項目傳送門),歡迎star~