1.自定义监听类,用来返回下载结果
interface DownLoadListener {/*** 下载成功之后的文件*/fun onDownloadSuccess(file: File)/*** 下载进度*/fun onDownloading(progress: Int)/*** 下载异常信息*/fun onDownloadFailed(e:Exception)
}
- 进行文件下载
/*** @param destFileDir 文件下载目录* @param response okHttp的返回值* @param downLoadListener 监听事件,用于返回当前下载进度等*/private fun downLoad(destFileDir: String,response: Response,downLoadListener: DownLoadListener) {val byte = ByteArray(2048)var len: Intval fileOutputStream: FileOutputStreamval file = File(destFileDir)if (!file.exists()) {file.mkdirs()}val apkFile = File(file, GeneralUtil.apkName)val input = response.body?.byteStream()val apkSize: Long = response.body?.contentLength() ?: 0Lprintln("获取到的apk大小:$apkSize")fileOutputStream = FileOutputStream(apkFile)var sum = 0.0if (apkSize != 0L) {while ((input?.read(byte).also { len = it!! }) != -1) {fileOutputStream.write(byte, 0, len)sum += len//返回当前的下载进度downLoadListener.onDownloading((sum / apkSize * 100).toInt())}}//刷新fileOutputStream.flush()//返回结果:当前已经下载成功downLoadListener.onDownloadSuccess(file)//关闭流input?.close()fileOutputStream.close()}
3.在Activity中监听下载成功或者失败的返回结果
override fun onDownloadSuccess(file: File) {//下载新版本apk完成manager.cancel(1)//跳转到新的activity,这个activity用来做安装apk的操作OpenApkFile.startOpenApkFile(this)}//上一次更新通知栏的时间private var lastTime: Long = 0Loverride fun onDownloading(progress: Int) {//动态更新进度if (lastTime == 0L) {lastTime = System.currentTimeMillis()}//与上一次更新通知栏相隔大于1s再进行更新,否则压力过大if (System.currentTimeMillis() - lastTime > 1000) { lastTime = System.currentTimeMillis()//notificationView为自定义的通知栏的布局notificationView.setProgressBar(R.id.progress, 100, progress, false)notificationView.setTextViewText(R.id.content, "$progress%")//manager为NotificationManagermanager.notify(1, notification)}}override fun onDownloadFailed(e: Exception) {//下载失败,需要删除下载失败后的文件val file = File(GeneralUtil.getDownLoadApkPathWithApkName(this))file.deleteOnExit()sendMessage(handler, HANDLERTYPE.FAIL.type, "新版本下载失败")manager.cancel(1)}
4.下载完成之后跳转到安装apk的界面
class OpenApkFile : AppCompatActivity() {private var apkFileIntent: Intent? = nullcompanion object {fun startOpenApkFile(context: Context) {context.startActivity(Intent(context, OpenApkFile::class.java))}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)apkFileIntent = getApkFileIntent(GeneralUtil.getDownLoadApkPathWithApkName(this), this)if (apkFileIntent != null) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//版本高于26需要申请权限来安装apkif (!this.packageManager.canRequestPackageInstalls()) {AlertDialog.Builder(this).setTitle("提示").setMessage("暂未开启权限,需要您开启权限安装最新版本,以获取更好的体验!").setNeutralButton("确定") { dialogInterface, _ ->run {val parse = Uri.parse("package:$packageName")val intent =Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, parse)startActivityForResult(intent, 1)dialogInterface.dismiss()}}.setNegativeButton("取消") { dialogInterface, _ ->run {Toast.makeText(this, "已拒绝安装", Toast.LENGTH_SHORT).show()dialogInterface.dismiss()finish()}}.create().show()} else {//已获得权限直接安装startActivity(apkFileIntent)finish()}} else {//版本低于26则直接安装startActivity(apkFileIntent)finish()}} else {//无法获取到uri,抛出了异常AlertDialog.Builder(this).setTitle("提示").setMessage("无法获取安装包,请联系管理员获取帮助").setNeutralButton("确定") { dialogInterface, _ ->run {dialogInterface.dismiss()finish()}}.create().show()}}private fun getApkFileIntent(param: String?, context: Context): Intent? {try {println("文件所在地址:$param")val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {FileProvider.getUriForFile(context,context.applicationContext.packageName.toString() + ".provider",File(param!!))} else {Uri.fromFile(File(param!!))}println("编码之后:$uri")return Intent(Intent.ACTION_VIEW).run {addCategory(Intent.CATEGORY_DEFAULT)flags = Intent.FLAG_GRANT_READ_URI_PERMISSIONaddFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// grantUriPermission(packageName,uri,Intent.FLAG_GRANT_READ_URI_PERMISSION)setDataAndType(uri, "application/vnd.android.package-archive")this}} catch (e: Exception) {e.printStackTrace()return null}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {1 -> {startActivity(apkFileIntent)finish()}else -> {}}}
}
5.FileProvider需要设置路径的xml
文件存放于res目录下的xml中:

file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths><files-pathname="aaa"path="Apk/"/>
</paths>
</resources>
此段代码代表的路径为:data/data/com.包名/files/Apk/,name字段可以随便取名,不影响,path为data/data/com.包名/files下的子目录;
6.在AndroidManifest.xml注册FileProvider;
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>
















