【更新:2022年10月26日Blgly停止升级服务,此blog仅做记录,也可用来参考做升级UI】
升级使用的Bugly,由于Bugly最新sdk1.5.23未适配android12,当targetSDK设置为31时,点击升级会报错无法升级,反馈一个多月了未见改动,难道开发人员被裁了?
只能借助Bugly的后台和接口,自己撸一个简单的升级功能。
大概效果图如下
一. 禁止Bugly自带的检测升级并弹窗功能
Beta.autoCheckUpgrade = false
Beta.init(this, isDebug)
二. 自己实现检测升级和弹窗功能
- 检测升级
//手动调用checkUpgrade接口
Beta.checkUpgrade(isManual, true)
delay(2000).run {
//延时2s后读取结果,注意getUpgradeInfo api是读取本地的缓存结果,不会发起请求,所以要延时一下再读取会更好
var result = Beta.getUpgradeInfo()
}
- 制作升级弹窗
layout布局而已,直接贴出来,可以自己diy
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary_blue"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现新版本"
android:textColor="@color/white"
android:textSize="20dp" />
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:lineSpacingMultiplier="1.2"
android:text="版本号[0.0.0.8]\n发布日期[2022-06-01]"
android:textColor="@color/white"
android:textSize="16dp" />
</LinearLayout>
<TextView
android:id="@+id/tv_feature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:padding="20dp"
android:text="版本特性:xxx"
android:textSize="16dp" />
<LinearLayout
android:id="@+id/fl_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp"
android:visibility="gone"
tools:visibility="visible">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:min="0"
android:padding="@dimen/dp10"
android:value="0" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="0%" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<TextView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="下次再说"
android:textColor="@color/text_color2"
android:textSize="16dp" />
<TextView
android:id="@+id/tv_ignore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="忽略此版本"
android:textColor="@color/text_color2"
android:textSize="16dp" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_upgrade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="立即更新"
android:textColor="@color/primary_blue"
android:textSize="16dp" />
</LinearLayout>
</LinearLayout>
</layout>
- 下载安装包
这个地方,使用系统DownloadManager下载apk
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(Uri.parse(result.apkUrl))
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "labApks")
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
request.setMimeType("application/vnd.android.package-archive")
val downloadId = downloadManager.enqueue(request)
downloadManger.enqueue(request)就会发起下载任务,但下载任务不是立即执行的需要等待系统调用。另外没有回调去接收下载进度,需要我们自己读取状态和进度
suspend fun checkDownloadProgress(
manager: DownloadManager,
downloadId: Long,
progressBar: ProgressBar,
progressText: TextView
): Boolean {
while (true) {
val q = DownloadManager.Query()
q.setFilterById(downloadId)
val cursor = manager.query(q)
cursor.moveToFirst()
val bytes_downloaded =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val bytes_total =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val dl_progress = (bytes_downloaded * 100 / bytes_total)
progressBar.post {
progressBar.progress = dl_progress
progressText.text = "${dl_progress}%"
}
when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
DownloadManager.STATUS_SUCCESSFUL -> {
return true
}
DownloadManager.STATUS_RUNNING, DownloadManager.STATUS_PENDING -> {
delay(500)
}
else -> {
manager.remove(downloadId)
return false
}
}
}
}
读取的时候用while循环去读取,发现任务状态时Pending或Running时更新一下UI,500ms后再读取,直到状态变成Fail或Success
- 安装apk
android apk安装需要用到下载文件的uri ,DownloadManager通过downloadId即可获取
val uri = downloadManager.getUriForDownloadedFile(downloadId)
然后是通过uri安装
private fun installAPK(context: Context, apkFile: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
} else {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
intent.setDataAndType(apkFile, "application/vnd.android.package-archive")
context.startActivity(intent)
}
注意一下申请文件存储权限
三. 添加调用时机
方法封装上,添加一个是否手动调用的参数
fun checkNewVersion(context: Context, isManual: Boolean)
- MainActivity的onResume()中自动调用此方法检测更新
- 当有设计检测更新的功能时,手动调用,bugly的接口调用手动调用的参数设置为true(bugly会自动显示一个toast)
完整代码
本来想在github上找个完整替代的库,但发现基本上都没有适配android12,要么错误百出。。。
贴一下代码吧,有需要的可以自己diy,商业生产的话需要调试一下bug
import android.annotation.SuppressLint
import android.app.Dialog
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.view.LayoutInflater
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import com.jxit.manufacture.activity.MainActivity
import com.jxit.manufacture.databinding.DialogUpgradeBinding
import com.tencent.bugly.beta.Beta
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
class UpgradeUtil {
companion object {
var isIgnoreNewVersion = false
private const val mTag = "UpgradeUtil"
fun checkNewVersion(context: Context, isManual: Boolean) {
if (!isManual && isIgnoreNewVersion) return
MainScope().launch(Dispatchers.IO) {
Beta.checkUpgrade(isManual, true)
delay(2000).run {
var result = Beta.getUpgradeInfo()
if (result == null) {
MLog.d(mTag, "没有新版本")
} else {
MainScope().launch {
var dialog = Dialog(context)
val bd = DialogUpgradeBinding.inflate(LayoutInflater.from(context))
bd.tvVersion.text = "版本号:${result.versionName}\n发布时间:${
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(result.publishTime)
}"
bd.tvFeature.text = result.newFeature
bd.tvIgnore.setOnClickListener {
isIgnoreNewVersion = true
dialog.dismiss()
}
bd.tvCancel.setOnClickListener {
dialog.dismiss()
}
bd.tvUpgrade.setOnClickListener {
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(Uri.parse(result.apkUrl))
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"labApks"
)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
request.setMimeType("application/vnd.android.package-archive")
val downloadId = downloadManager.enqueue(request)
bd.llActions.visibility = View.GONE
bd.tvProgress.text = "0%"
bd.progressBar.progress = 0
bd.flProgress.visibility = View.VISIBLE
MainScope().launch(Dispatchers.IO) {
var success = checkDownloadProgress(
downloadManager,
downloadId,
bd.progressBar,
bd.tvProgress
)
MainScope().launch {
dialog.dismiss()
if (success) {
val uri =
downloadManager.getUriForDownloadedFile(downloadId)
installAPK(context, uri)
} else {
(context as MainActivity).toast("下载失败,请重试")
bd.flProgress.visibility = View.GONE
bd.llActions.visibility = View.VISIBLE
}
}
}
}
dialog.setContentView(bd.root)
dialog.show()
}
}
}
}
}
private fun installAPK(context: Context, apkFile: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
} else {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
intent.setDataAndType(apkFile, "application/vnd.android.package-archive")
context.startActivity(intent)
}
@SuppressLint("Range")
suspend fun checkDownloadProgress(
manager: DownloadManager,
downloadId: Long,
progressBar: ProgressBar, progressText: TextView
): Boolean {
while (true) {
val q = DownloadManager.Query()
q.setFilterById(downloadId)
val cursor = manager.query(q)
cursor.moveToFirst()
val bytes_downloaded =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val bytes_total =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val dl_progress = (bytes_downloaded * 100 / bytes_total)
progressBar.post {
progressBar.progress = dl_progress
progressText.text = "${dl_progress}%"
}
when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
DownloadManager.STATUS_SUCCESSFUL -> {
return true
}
DownloadManager.STATUS_RUNNING, DownloadManager.STATUS_PENDING -> {
delay(500)
}
else -> {
manager.remove(downloadId)
return false
}
}
}
}
}
}