天天看點

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中:

Android下載下傳安裝Apk

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>