0 x01 preface
I’m sure you’ve all used or heard of GitHub as a remote repository. But GitHub does more than just manage your code. You can put any file on GitHub and even use it as a web disk. So, as a student with no server (no money) and too lazy to build my own backend, I tried using GitHub to implement simple App version updates.
0x02 Update Process
An update with good user experience usually has two steps. One is to ask the server if there is an update, and then remind the user if there is an update, and then the user chooses whether to download the update.
Detection of update
To check the update step, we can put a file in our repository that contains the current version information, for example, in json format:
{
"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"
}
Copy the code
How to obtain file contents? Click RAW in your repository to view the file.
As shown, the url above is one that your App can access directly through HTTP, reading data from the data stream to get the string. You can think of this as the API provided by the back end:
https://raw.githubusercontent.com/$/ ${making user name} {item name} / ${branch name}} / ${relative to the project root directory path}}
Through this interface, you can access not only text files, but also any files in your public repository, which can then be downloaded locally.
Note: If the repository is private and cannot be accessed using this API, there is a token argument. See GitHub developer API documentation for details.
In this example, code is the versionCode of build. The App reads JSON and compares it with the current versionCode to know whether it needs to be updated (the comparison method is not unique, it is common to use versionCode).
if (info.code > BuildConfig.VERSION_CODE) {
// do update
} else {
// already updated
}
Copy the code
Download the update
Updating is relatively simple. Since GitHub can be used as a “web disk”, we can also commit push the packaged APK file in Git, and then find the corresponding path to download it.
0x03 Version Information is generated during Packaging
In accordance with the above ideas, each update needs to change the versionCode, versionName, and then need to deal with json files, APK files… In case I make a mistake in push, I have obsessive-compulsive disorder and have to make a commitment -> force push. As an intelligent (and lazy) programmer, this is the kind of task that should be left to the program
Before reading this, I want you to understand the concept of Gradle Task and Action first. I also learn to sell now, there are wrong places please correct ~
Task&Actio
When you click Run, Build, or Clean, you’re actually running a bunch of Gradle tasks behind the scenes, but Android Studio makes these processes graphical, so you don’t know what they are.
We can simply look at the Gradle task executed by build Project when buildType is release:
A function that generates the necessary information
First, we can define a function that moves the generated APK to the target folder and generates the JSON for the update query:
// Calculate the MD5 of apK
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 ------------------")
// Copy the apk file from the build directory to the update folder of the root project
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
// If there is a previous JSON file, check whether the packaging has changed this time
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) }// Create a json entity class
// Expando can be simply defined as a 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)
// Write json to a file to query for updates
updateJsonFile.write(outputJson)
} else {
// No update required
println("This version is already released.\n" +
"VersionCode = ${buildInfo.versionCode}\n" +
"Skip generateUpdateInfo.")
}
println("------------------ Finish Generating version info ------------------")}Copy the code
Add to the Task assembleRelease
Since build information needs to be generated only when the package is released, build information needs to be generated when buildType is release, so we need to make a decision here and only add the function to Task at release:
Variants. Each {variant -> def apkName ="EveryDownload-${variant.buildType.name}-${defaultConfig.versionName}.apk"Variable.outputs. All {outputFileName = apkName} // add only in releaseif (variant.buildType.name == "release") {directly added to the Action of the Task, build execution can perform this function after the completion of the variant. AssembleProvider. The get (). DoLast {generateUpdateInfo (apkName)}}}Copy the code
The results
Use the command line build to see the output:
./gradlew app:assembleRelease
Copy the code
You can see the function print:
Then the root directory has these two files:
Leave everything else untouched, build again, and you can see that there is no rebuilding:
Here, the successful liberation of hands ~
0x04 Implementation of the update
This solution is directly used in my downloader (project portal), welcome star ~