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 = 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, allGranted: Boolean ) { // 权限同意 if (checkIsHaveNewAPK()) { // 安装 val fileAPK = File(downLoadPath.value, "${versionInfo.versionName}.apk") installApk(context, fileAPK) } else { downloadApk() } } override fun onDenied( permissions: MutableList, 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(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 = "" // 版本名 )