1.最近公司有项目需要用上在线更新功能 然后我在网上借鉴了很多相关在线更新的案例 发现轮子哥的安卓中台APP的在线更新可借鉴性和实现方式都很简单指的学习一下 后面话不多说先看样子 先给一个弹窗的工具类
class UpdateDialog {
class Builder(context: Context) : BaseDialog.Builder<Builder>(context) {
private val nameView: TextView? by lazy { findViewById(R.id.tv_update_name) }
private val detailsView: TextView? by lazy { findViewById(R.id.tv_update_details) }
private val progressView: ProgressBar? by lazy { findViewById(R.id.pb_update_progress) }
private val updateView: TextView? by lazy { findViewById(R.id.tv_update_update) }
private val closeView: TextView? by lazy { findViewById(R.id.tv_update_close) }
/** Apk 文件 */
private var apkFile: File? = null
/** 下载地址 */
private var downloadUrl: String? = null
/** 文件 MD5 */
private var fileMd5: String? = null
/** 是否强制更新 */
private var forceUpdate = false
/** 当前是否下载中 */
private var downloading = false
/** 当前是否下载完毕 */
private var downloadComplete = false
init {
setContentView(R.layout.update_dialog)
setAnimStyle(AnimAction.ANIM_BOTTOM)
setCancelable(false)
setOnClickListener(updateView, closeView)
// 让 TextView 支持滚动
detailsView?.movementMethod = ScrollingMovementMethod()
}
/**
* 设置版本名
*/
fun setVersionName(name: CharSequence?): Builder = apply {
nameView?.text = name
}
/**
* 设置更新日志
*/
fun setUpdateLog(text: CharSequence?): Builder = apply {
detailsView?.text = text
detailsView?.visibility = if (text == null) View.GONE else View.VISIBLE
}
/**
* 设置强制更新
*/
fun setForceUpdate(force: Boolean): Builder = apply {
forceUpdate = force
closeView?.visibility = if (force) View.GONE else View.VISIBLE
setCancelable(!force)
}
/**
* 设置下载 url
*/
fun setDownloadUrl(url: String?): Builder = apply {
downloadUrl = url
}
/**
* 设置文件 md5
*/
fun setFileMd5(md5: String?): Builder = apply {
fileMd5 = md5
}
@SingleClick
override fun onClick(view: View) {
if (view === closeView) {
dismiss()
} else if (view === updateView) {
// 判断下载状态
if (downloadComplete) {
if (apkFile!!.isFile) {
// 下载完毕,安装 Apk
installApk()
} else {
// 下载失败,重新下载
downloadApk()
}
} else if (!downloading) {
// 没有下载,开启下载
downloadApk()
}
}
}
/**
* 下载 Apk
*/
@CheckNet
@Permissions(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE, Permission.REQUEST_INSTALL_PACKAGES)
private fun downloadApk() {
// 设置对话框不能被取消
setCancelable(false)
val notificationManager = getSystemService(NotificationManager::class.java)
val notificationId = getContext().applicationInfo.uid
var channelId = ""
// 适配 Android 8.0 通知渠道新特性
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(getString(R.string.update_notification_channel_id),
getString(R.string.update_notification_channel_name), NotificationManager.IMPORTANCE_LOW)
channel.enableLights(false)
channel.enableVibration(false)
channel.vibrationPattern = longArrayOf(0)
channel.setSound(null, null)
notificationManager.createNotificationChannel(channel)
channelId = channel.id
}
val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(getContext(), channelId)
// 设置通知时间
.setWhen(System.currentTimeMillis())
// 设置通知标题
.setContentTitle(getString(R.string.app_name))
// 设置通知小图标
.setSmallIcon(R.mipmap.logo)
// 设置通知大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.logo))
// 设置通知静音
.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE)
// 设置震动频率
.setVibrate(longArrayOf(0))
// 设置声音文件
.setSound(null)
// 设置通知的优先级
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// 创建要下载的文件对象
apkFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
getString(R.string.app_name) + "_v" + nameView?.text.toString() + ".apk")
EasyHttp.download(getDialog())
.method(HttpMethod.GET)
.file(apkFile)
.url(downloadUrl)
.md5(fileMd5)
.listener(object : OnDownloadListener {
override fun onStart(file: File?) {
// 标记为下载中
downloading = true
// 标记成未下载完成
downloadComplete = false
// 后台更新
closeView?.visibility = View.GONE
// 显示进度条
progressView?.visibility = View.VISIBLE
updateView?.setText(R.string.update_status_start)
}
override fun onProgress(file: File, progress: Int) {
updateView?.text = String.format(getString(R.string.update_status_running)!!, progress)
progressView?.progress = progress
// 更新下载通知
notificationManager.notify(
notificationId, notificationBuilder
// 设置通知的文本
.setContentText(String.format(getString(R.string.update_status_running)!!, progress))
// 设置下载的进度
.setProgress(100, progress, false)
// 设置点击通知后是否自动消失
.setAutoCancel(false)
// 是否正在交互中
.setOngoing(true)
// 重新创建新的通知对象
.build()
)
}
override fun onComplete(file: File) {
// 显示下载成功通知
notificationManager.notify(
notificationId, notificationBuilder
// 设置通知的文本
.setContentText(String.format(getString(R.string.update_status_successful)!!, 100))
// 设置下载的进度
.setProgress(100, 100, false)
// 设置通知点击之后的意图
.setContentIntent(PendingIntent.getActivity(getContext(), 1, getInstallIntent(), Intent.FILL_IN_ACTION))
// 设置点击通知后是否自动消失
.setAutoCancel(true)
// 是否正在交互中
.setOngoing(false)
.build()
)
updateView?.setText(R.string.update_status_successful)
// 标记成下载完成
downloadComplete = true
// 安装 Apk
installApk()
}
override fun onError(file: File, e: Exception) {
// 清除通知
notificationManager.cancel(notificationId)
updateView?.setText(R.string.update_status_failed)
// 删除下载的文件
file.delete()
}
override fun onEnd(file: File) {
// 更新进度条
progressView?.progress = 0
progressView?.visibility = View.GONE
// 标记当前不是下载中
downloading = false
// 如果当前不是强制更新,对话框就恢复成可取消状态
if (!forceUpdate) {
setCancelable(true)
}
}
}).start()
}
/**
* 安装 Apk
*/
@Permissions(Permission.REQUEST_INSTALL_PACKAGES)
private fun installApk() {
getContext().startActivity(getInstallIntent())
}
/**
* 获取安装意图
*/
private fun getInstallIntent(): Intent {
val intent = Intent()
intent.action = Intent.ACTION_VIEW
val uri: Uri?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", apkFile!!)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
} else {
uri = Uri.fromFile(apkFile)
}
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return intent
}
}
}
页面XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="5dp"
tools:context=".ui.windows.dialog.UpdateDialog">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_m_1"
android:adjustViewBounds="true"
app:srcCompat="@mipmap/update_app_top_bg"
android:layout_marginTop="-15dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="@dimen/dp_30"
android:layout_marginBottom="@dimen/dp_5"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_update_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_5"
android:textColor="@color/white"
android:textSize="@dimen/sp_20"
tools:text="3.2.1" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_20"
android:text="@string/update_content"
android:textColor="@color/black"
android:textSize="@dimen/sp_17"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_update_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_20"
android:layout_marginVertical="@dimen/dp_10"
android:lineSpacingExtra="@dimen/dp_5"
android:maxLines="4"
android:minLines="3"
android:scrollbars="vertical"
android:textColor="@color/black60"
android:textSize="@dimen/sp_15"
tools:text="6\n66\n666\n6666\n66666" />
<ProgressBar
android:id="@+id/pb_update_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginHorizontal="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_3"
android:indeterminate="false"
android:visibility="gone"
tools:progress="50"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_update_close"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/transparent_selector"
android:focusable="true"
android:gravity="center"
android:paddingVertical="@dimen/dp_15"
android:text="@string/update_no"
android:textColor="@color/black40"
android:textSize="@dimen/sp_14" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_update_update"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/transparent_selector"
android:focusable="true"
android:gravity="center"
android:paddingVertical="@dimen/dp_15"
android:text="@string/update_yes"
android:textColor="@color/black60"
android:textSize="@dimen/sp_15" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
有一些是资源文件需要自己替换 自己的照片之类的 还有 很多设置 dp 都是抽出来的 也需要自己主要一下
1.重点讲解 1.这个网络请求用的EasyHttp 需要自己 去找引入依赖
implementation 'com.github.getActivity:EasyHttp:11.2'
2.EasyHttp 使用的时候需要初始化
3.下载完成后 获取安装意图
/**
* 获取安装意图
*/
private fun getInstallIntent(): Intent {
val intent = Intent()
intent.action = Intent.ACTION_VIEW
val uri: Uri?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
/** 这里 AppConfig.getPackageName() 是当前APP 的 包名 如果不正确 会导致安装失败 应用闪退
*/
uri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", apkFile!!)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
} else {
uri = Uri.fromFile(apkFile)
}
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return intent
}
应用还有一些代码没有给完整, 如果需要的话可以查看github 获取完整项目代码 https://github.com/getActivity/AndroidProject-Kotlin