Android下载安装Apk

1.自定义监听类,用来返回下载结果

interface DownLoadListener {
    /**
     * 下载成功之后的文件
     */
    fun onDownloadSuccess(file: File)
    /**
     * 下载进度
     */
    fun onDownloading(progress: Int)
    /**
     * 下载异常信息
     */
    fun onDownloadFailed(e:Exception)
}
  1. 进行文件下载
/**
     * @param destFileDir 文件下载目录
     * @param response okHttp的返回值
     * @param downLoadListener 监听事件,用于返回当前下载进度等
     */
    private fun downLoad(
        destFileDir: String,
        response: Response,
        downLoadListener: DownLoadListener
    ) {
        val byte = ByteArray(2048)
        var len: Int
        val fileOutputStream: FileOutputStream

        val 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() ?: 0L
        println("获取到的apk大小:$apkSize")
        fileOutputStream = FileOutputStream(apkFile)

        var sum = 0.0
        if (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 = 0L

    override 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为NotificationManager
            manager.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? = null

    companion 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需要申请权限来安装apk
                if (!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_PERMISSION
                addFlags(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-path
        name="aaa"
        path="Apk/"/>
</paths>
</resources>

此段代码代表的路径为:data/data/com.包名/files/Apk/,name字段可以随便取名,不影响,path为data/data/com.包名/files下的子目录;
6.在AndroidManifest.xml注册FileProvider;

 <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
评论

打赏作者

帅到不敢打代码

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值