package com.xw.common.net
|
|
import android.Manifest
|
import android.annotation.SuppressLint
|
import android.content.Context
|
import android.content.Intent
|
import android.content.pm.PackageManager
|
import android.net.Uri
|
import android.os.Build
|
import android.os.Environment
|
import android.os.Handler
|
import android.os.Looper
|
import com.xw.common.utils.LogUtils
|
import android.view.Gravity
|
import android.view.View
|
import android.view.WindowManager
|
import androidx.core.content.ContextCompat
|
import androidx.core.content.FileProvider
|
import androidx.core.view.isVisible
|
import com.blankj.utilcode.util.AppUtils
|
import com.hjq.permissions.OnPermissionCallback
|
import com.hjq.permissions.XXPermissions
|
import com.xw.common.R
|
import com.xw.common.base.BaseDialog
|
import com.xw.common.databinding.DialogUpdateTipsBinding
|
import com.xw.common.utils.ToastUtilCommon
|
import com.xw.common.utils.ToolUtil
|
import java.io.File
|
import java.io.FileOutputStream
|
import java.net.URL
|
|
/**
|
* 检查更新版本工具类
|
*/
|
class UpdateManager {
|
|
companion object {
|
const val TAG = "zmc-UpdateManager"
|
const val TIPS = "请授权访问存储空间权限,否则App无法更新"
|
|
@SuppressLint("StaticFieldLeak")
|
var updateManager: UpdateManager? = null
|
val instance: UpdateManager?
|
get() {
|
if (updateManager == null) {
|
updateManager = UpdateManager()
|
}
|
return updateManager
|
}
|
}
|
|
private val downLoadPath = lazy {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) {
|
(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath
|
?: "") + "/download/"
|
} else {
|
val packageName = context.packageName
|
Environment.getExternalStorageDirectory().absolutePath + "/" + packageName + "/download/"
|
}
|
}
|
private val versionInfo = VersionInfo()
|
|
private val oldApkName by lazy { "${AppUtils.getAppVersionName()}.apk" }
|
lateinit var context: Context
|
|
fun setContext(context: Context): UpdateManager {
|
this.context = context
|
return this
|
}
|
|
fun apkUrl(apkUrl: String): UpdateManager {
|
versionInfo.apk = apkUrl
|
return this
|
}
|
|
fun updateContent(content: String): UpdateManager {
|
versionInfo.content = content
|
return this
|
}
|
|
fun updateVersionName(version: String): UpdateManager {
|
versionInfo.versionName = version
|
return this
|
}
|
|
fun updateVersionCode(version: Int): UpdateManager {
|
versionInfo.versionCode = version
|
return this
|
}
|
fun updateForceUpdateFlag(forceUpdateFlag: Int): UpdateManager {
|
versionInfo.forceUpdateFlag = forceUpdateFlag
|
return this
|
}
|
|
fun show() {
|
val versionCode = AppUtils.getAppVersionCode()
|
if (versionCode < versionInfo.versionCode) showUpdateDialog()
|
|
}
|
|
private var updateDialog: UpdateDialog? = null
|
|
/**
|
* 版本更新提示框
|
*/
|
private fun showUpdateDialog() {
|
if (updateDialog != null && updateDialog!!.isShowing) return
|
if (updateDialog == null) {
|
updateDialog = UpdateDialog(context)
|
|
updateDialog!!.binding!!.desc.text = versionInfo.content
|
updateDialog!!.binding!!.cancel.isVisible = versionInfo.forceUpdateFlag != 1
|
|
updateDialog!!.binding!!.cancel.setOnClickListener {
|
updateDialog?.dismiss()
|
}
|
updateDialog!!.binding!!.download.setOnClickListener {
|
updateApk()
|
}
|
}
|
updateDialog!!.show()
|
}
|
|
/**
|
* 检查有没有最新的APK
|
*/
|
private fun checkIsHaveNewAPK(): Boolean {
|
val file = File(downLoadPath.value)
|
if (!file.exists()) {
|
file.mkdirs()
|
}
|
val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk")
|
return fileAPK.exists()
|
}
|
|
/**
|
* 删除旧的apk
|
*/
|
private fun delOldApk() {
|
val fileAPK = File(downLoadPath.value, oldApkName)
|
if (fileAPK.exists()) {
|
fileAPK.delete()
|
}
|
}
|
|
/**
|
* 删除下载状态的包
|
*/
|
private fun delDownloadStatusFile() {
|
val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk.download")
|
if (fileAPK.exists()) {
|
fileAPK.delete()
|
}
|
}
|
|
/**
|
* 下载APK
|
*/
|
private fun downloadApk() {
|
updateDialog!!.binding!!.horizontalProgress.visibility = View.VISIBLE
|
updateDialog!!.binding!!.download.visibility = View.GONE
|
updateDialog!!.binding!!.cancel.visibility = View.GONE
|
Thread {
|
try {
|
val startTime = System.currentTimeMillis()
|
LogUtils.i(TAG, "startTime=$startTime")
|
//获取文件名
|
val myURL = URL(versionInfo.apk)
|
val conn = myURL.openConnection()
|
conn.connect()
|
val `is` = conn.getInputStream()
|
val fileSize = conn.contentLength //根据响应获取文件大小
|
LogUtils.i(TAG, "总大小=$fileSize")
|
if (fileSize <= 0) throw RuntimeException("无法获知文件大小 ")
|
if (`is` == null) throw RuntimeException("stream is null")
|
val file1 = File(downLoadPath.value)
|
if (!file1.exists()) {
|
file1.mkdirs()
|
}
|
//把数据存入路径+文件名
|
val fos =
|
FileOutputStream("${downLoadPath.value}/${versionInfo.versionName}.apk.download")
|
val buf = ByteArray(1024)
|
var downLoadFileSize = 0
|
do {
|
//循环读取
|
val numread = `is`.read(buf)
|
if (numread == -1) {
|
break
|
}
|
fos.write(buf, 0, numread)
|
downLoadFileSize += numread
|
LogUtils.i(TAG, "downLoadFileSize=$downLoadFileSize")
|
LogUtils.i(TAG, "fileSize=$fileSize")
|
LogUtils.i(
|
TAG,
|
"download downLoadFileSize=" + (downLoadFileSize.toFloat() * 100 / fileSize).toInt()
|
)
|
Handler(Looper.getMainLooper()).post {
|
//更新进度条
|
updateDialog!!.binding!!.horizontalProgress.setProgress((downLoadFileSize.toFloat() * 100 / fileSize))
|
}
|
} while (true)
|
ToolUtil.copyFile(
|
"$downLoadPath/${versionInfo.versionName}.apk.download",
|
"$downLoadPath/${versionInfo.versionName}.apk"
|
)
|
delDownloadStatusFile()
|
delOldApk()
|
val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk")
|
installApk(context, fileAPK)
|
LogUtils.i(TAG, "download success")
|
LogUtils.i(TAG, "totalTime=" + (System.currentTimeMillis() - startTime))
|
`is`.close()
|
} catch (ex: Exception) {
|
LogUtils.e(TAG, "error: " + ex.message)
|
}
|
}.start()
|
}
|
|
/**
|
* 安装apk
|
*/
|
private fun installApk(context: Context, file: File) {
|
val intent = Intent(Intent.ACTION_VIEW)
|
//判断是否是AndroidN以及更高的版本
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
val contentUri =
|
FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
|
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
|
} else {
|
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
|
}
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
context.startActivity(intent)
|
}
|
|
private fun updateApk() {
|
//权限判断是否有访问外部存储空间权限
|
if (Build.VERSION.SDK_INT < 23) {
|
if (checkIsHaveNewAPK()) {
|
// 安装
|
val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk")
|
installApk(context, fileAPK)
|
} else {
|
downloadApk()
|
}
|
} else {
|
var permission =
|
if (Build.VERSION.SDK_INT < 30) arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
else arrayOf(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
val permissionList: MutableList<String> = ArrayList()
|
for (permission in permission) {
|
if (ContextCompat.checkSelfPermission(
|
context, permission
|
) != PackageManager.PERMISSION_GRANTED
|
) {
|
permissionList.add(permission)
|
}
|
}
|
if (permissionList.size > 0) {
|
XXPermissions.with(context).permission(permission)
|
.request(object : OnPermissionCallback {
|
override fun onGranted(
|
permissions: MutableList<String>, allGranted: Boolean
|
) { // 权限同意
|
if (checkIsHaveNewAPK()) {
|
// 安装
|
val fileAPK =
|
File(downLoadPath.value, "${versionInfo.versionName}.apk")
|
installApk(context, fileAPK)
|
} else {
|
downloadApk()
|
}
|
}
|
|
override fun onDenied(
|
permissions: MutableList<String>, doNotAskAgain: Boolean
|
) {
|
super.onDenied(permissions, doNotAskAgain)
|
//权限禁止
|
ToastUtilCommon.show(TIPS)
|
}
|
})
|
} else { // 有权限
|
if (checkIsHaveNewAPK()) {
|
// 安装
|
val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk")
|
installApk(context, fileAPK)
|
} else {
|
downloadApk()
|
}
|
}
|
}
|
}
|
}
|
|
class UpdateDialog(context: Context) :
|
BaseDialog<DialogUpdateTipsBinding>(context, R.style.LoadingDialog) {
|
override fun getLayoutId() = R.layout.dialog_update_tips
|
|
override fun initData() {
|
window!!.setGravity(Gravity.CENTER)
|
window!!.setLayout(
|
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT
|
)
|
setCancelable(false)
|
setCanceledOnTouchOutside(false)
|
}
|
}
|
|
data class VersionInfo(
|
var apk: String = "", // 下载地址
|
var content: String = "", // 版本描述
|
var forceUpdateFlag: Int = 0, // 是否强制升级
|
var versionCode: Int = 0, // 版本号
|
var versionName: String = "" // 版本名
|
)
|