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