#Android 增量更新实现
原本来腾讯课堂中了解到了一些关于增量更新的知识,特地写一篇博客来记录下。
通过JNI实现差分、合并
object BsDiff {
init {
System.loadLibrary("bsdiff")
}
external fun bsdiff(oldFile: String, newFile: String, patchFile: String): Int
external fun bspatch(oldFile: String, newFile: String, patchFile: String): Int
}
这个是Kotlin代码。那么如何给Kotlin代码实现JNI方法呢?
通过DownloadManager下载差分文件
object HttpUtils {
const val HOST_URL = "http://192.168.18.38:8080"
const val GET_LATEST_VERSION = "/app/latest-version"
const val GET_LATEST_PATCH = "/app/get-apk-patch"
/**
* 获取服务器当前版本信息,JSON格式:
* {"versionCode":2,"versionName":"DiffPatch 1.1"}
*/
fun getLatestVersion(): JSONObject {
val json: JSONObject
val url = URL("$HOST_URL$GET_LATEST_VERSION")
val urlConn: HttpURLConnection = url.openConnection() as HttpURLConnection
urlConn.doOutput = true
urlConn.doInput = true
val reader = BufferedReader(InputStreamReader(urlConn.inputStream))
urlConn.connect()
json = JSONObject(reader.readLine())
Log.i(App.TAG, json.toString())
reader.close()
urlConn.disconnect()
return json
}
/**
* 通过DownloadManager下载Patch文件(注意:URL中需要附加当前版本号),如:
* http://192.168.18.38:8080/app/get-apk-patch?current-version-code=1
*/
fun downloadLatestPatch(context: Context, patchFileName: String): Long {
// 注意:URL中需要附加当前版本号
val request = DownloadManager.Request(Uri.parse("$HOST_URL$GET_LATEST_PATCH?current-version-code=${App.versionCode}"))
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, patchFileName)
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI)
request.setTitle(context.getString(R.string.app_name))
request.setDescription(context.getString(R.string.format_patch_file, patchFileName))
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setVisibleInDownloadsUi(true)
//7.0以上的系统适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
request.setRequiresDeviceIdle(false)
request.setRequiresCharging(false)
}
return (context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager).enqueue(request)
}
}
第一个方法是获取最新版本。
第二个方法是通过Android系统的DownloadManager下载差分文件。
注:下载目录如果改为Context.getExternalCacheDir()可以不用读写权限,参考博客 https://blog.csdn.net/wl724120268/article/details/78275686
合并成最新APK,并安装
合并代码:
class UpdateApkBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.i(App.TAG, "onReceive: ${intent.action}")
when (intent.action) {
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
installApk(context, id)
}
DownloadManager.ACTION_VIEW_DOWNLOADS,
DownloadManager.ACTION_NOTIFICATION_CLICKED -> {
val viewDownloadIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
viewDownloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(viewDownloadIntent)
}
}
}
private fun installApk(context: Context, downloadId: Long) {
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri = downloadManager.getUriForDownloadedFile(downloadId)
val file = App.getRealFilePath(context, uri)
if (uri != null && file != null) {
val newApk = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}${File.separator}DiffPatch-2.apk"
Log.i(App.TAG, "Start Activity: $uri")
Log.i(App.TAG, newApk)
BsDiff.bspatch(context.applicationInfo.sourceDir, newApk, file)
//以下两行代码可以让下载的apk文件被直接安装而不用使用Fileprovider,系统7.0或者以上才启动。
//https://www.jianshu.com/p/6b7bd2a59096
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val localBuilder = StrictMode.VmPolicy.Builder()
StrictMode.setVmPolicy(localBuilder.build())
}
val install = Intent(Intent.ACTION_VIEW)
install.data = Uri.fromFile(File(newApk))
install.setDataAndType(install.data, "application/vnd.android.package-archive")
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(install)
} else {
Log.i(App.TAG, "Start Activity: failure")
}
}
}
安装未知来源权限,需要在Android 8.0上单独请求:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
##服务端实现
具体思路