android 应用版本更新的记录
Categories:
android
开始
公司的APP发布在蒲公英平台管理的本来用的是蒲公英SDK里面的自带的下载更新功能。但是最近老是出问题,而已UI方面也不符合自己应用的情况,所以就决定自己改造下,下载以及更新的流程和代码。
缓存目录
文件下载缓存起来,一般我们用的有这样的几个方式
/**
* 应用内部缓存目录
*/
fun appCacheDir(context: Context): File? = context.cacheDir
/**
* 应用外部缓存目录
*/
fun appExternalCacheDir(context: Context): File? = context.externalCacheDir
/**
* sd卡根目录
*/
fun sdcardDir() = Environment.getExternalStorageDirectory()
从android 6.0开始系统加入了动态权限验证,需要读写外部存储的时候就需要验证权限了,为了减少用户验证权限,所以sd卡存储就pass了。应用内部缓存目录,写文件没有问题,不需要验证权限,但是这个目录外部访问有问题,我们通过Intent执行action操作,然后这个Intent传输的Uri指向的文件,外部应用访问不到。所有最后决定用应用外部缓存目录来存放这个下载下来的文件。
然后,android 7.0 开始读取Uri方式有改变了,原来我们一直使用的Uri.fromFile
不能用了,需要我们使用provider才能访问。所有访问uri代码就有改动:
manifest需要配置一个provider:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="net.zoneland.x.bpm.mobile.v1.zoneXBPM.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
file_paths.xml:
<paths>
<external-path name="external_path" path="."/>
<cache-path name="cache_path" path="."/>
</paths>
访问uri:
uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(context, "net.zoneland.x.bpm.mobile.v1.zoneXBPM.fileProvider", file)
} else {
Uri.fromFile(file)
}
通知显示下载进度
下载进度条一般都在通知栏中显示,因为直接用Dialog在应用中启动下载,就把UI给阻塞了,用户必须得等待,体验不好。通知栏显示也有个问题就是,有些用户会把通知栏禁用了就看不到,这里就多了个通知栏是否启用的判断,然后引导用户去设置界面开启,或者是在不想看到通知也可以跳过。
/**
* 是否开启通知
*/
fun isNotificationEnabled(context: Context): Boolean {
return try{
val manager = NotificationManagerCompat.from(context)
manager.areNotificationsEnabled()
}catch (e: Exception) {
true
}
}
如果通知被禁用,提示用户去设置:
/**
* 提示用户去设置开启通知,也可以跳过直接后台下载
*/
fun alertNotificationProcessBar(activity: Activity) {
MaterialDialog.Builder(activity)
.title(activity.getString(R.string.alert_permission))
.content(activity.getString(R.string.permission_notification))
.positiveText(activity.getString(R.string.positive))
.negativeText(activity.getString(R.string.cancel))
.neutralText(activity.getString(R.string.ignore))
.negativeColor(Color.parseColor("#9B9B9B"))
.onPositive { dialog, which ->
//去设置通知权限
AndroidUtils.gotoSettingApplication(activity)
}.onNeutral { dialog, which ->
//跳过 不开启通知显示进度条
toDownloadService(activity)
}
.show()
}
下载服务和代码
下载过程就是启动一个Service,把apk文件下载到上面说的缓存目录中,并且在通知栏更新下载进度。
class DownloadAPKService: IntentService("DownloadAPKService") {
companion object {
val DOWNLOAD_SERVICE_ACTION = "net.zoneland.x.bpm.mobile.v1.zoneXBPM.action.UPDATE"
val VERSIN_NAME_EXTRA_NAME = "VERSIN_NAME_EXTRA_NAME"
val DOWNLOAD_URL_EXTRA_NAME = "DOWNLOAD_URL_EXTRA_NAME"
}
private lateinit var downloadHandler: Handler
private val mNotificationManager: NotificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
private var mRemoteViews: RemoteViews? = null
private var mNotification: Notification? = null
override fun onHandleIntent(intent: Intent?) {
val action = intent?.action
XLog.info("service action $action..........")
if (DOWNLOAD_SERVICE_ACTION == action) {
startDownload(intent)
}
}
private fun startDownload(intent: Intent) {
downloadHandler = Handler(Looper.getMainLooper(), {msg: Message? ->
XLog.info("receive message type:${msg?.arg1}")
when(msg?.arg1) {
0 ->{//create notify
createNotify()
}
1->{//update notify
updateNotify(msg)
}
2->{//finish download
installAPK(msg)
}
}
true
})
val message = downloadHandler.obtainMessage()
message.arg1 = 0 //开始下载
downloadHandler.sendMessage(message)
val file:File? = downloadFile(intent)
finishUpdateMessage(file)
}
private fun downloadFile(intent: Intent): File? {
return try {
val versionName = intent.getStringExtra(VERSIN_NAME_EXTRA_NAME)
val downloadUrl = intent.getStringExtra(DOWNLOAD_URL_EXTRA_NAME)
val file = File(
FileUtil.appExternalCacheDir(applicationContext)?.absolutePath
+ File.separator
+ versionName + ".apk")
val url = URL(downloadUrl)
val conn = url.openConnection() as HttpURLConnection
conn.setRequestProperty("Accept-Encoding", "identity")
conn.connect()
val length = conn.contentLength
val inputStream = conn.inputStream
val fos = FileOutputStream(file, true)
var oldProgress = 0
val buf = ByteArray(1024 * 8)
var currentLength = 0
while (true) {
val num = inputStream.read(buf)
currentLength += num
// 计算进度条位置
val progress = (currentLength / length.toFloat() * 100).toInt()
if (progress > oldProgress) {
updateMessage(progress)
oldProgress = progress
}
if (num <= 0) {
break
}
fos.write(buf, 0, num)
fos.flush()
}
fos.flush()
fos.close()
inputStream.close()
file
}catch (e: Exception) {
XLog.error("下载应用安装包失败", e)
null
}
}
private fun createNotify() {
val mBuilder = NotificationCompat.Builder(this)
mBuilder.setSmallIcon(R.mipmap.logo)
mBuilder.setTicker(getString(R.string.downloading_begin))
mRemoteViews = RemoteViews(packageName, R.layout.remote_notification_download)
mRemoteViews?.setProgressBar(R.id.progressBar_remote_notification_download, 100, 0, false)
mBuilder.setCustomContentView(mRemoteViews)
mNotification = mBuilder.build()
mNotification?.flags = Notification.FLAG_NO_CLEAR
mNotificationManager.notify(0, mNotification)
}
private fun updateNotify(msg:Message) {
val flag = msg.obj
if (flag!=null){
flag as Int
mRemoteViews?.setProgressBar(R.id.progressBar_remote_notification_download, 100, flag, false)
mRemoteViews?.setTextViewText(R.id.tv_remote_notification_download_note, "正在下载 $flag %")
mNotificationManager.notify(0, mNotification)
}
}
private fun installAPK(msg: Message) {
val file = msg.obj
if (file!=null) {
file as File
mRemoteViews?.setProgressBar(R.id.progressBar_remote_notification_download, 100, 100, false)
mRemoteViews?.setTextViewText(R.id.tv_remote_notification_download_note, "下载完成!")
mNotification?.flags = Notification.FLAG_AUTO_CANCEL
val uri = FileUtil.getUriFromFile(applicationContext, file)
val intent = Intent(Intent.ACTION_VIEW)
intent.addCategory("android.intent.category.DEFAULT")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val type = FileUtil.getMIMEType(file)
intent.setDataAndType(uri, type)
mNotification?.contentIntent = PendingIntent.getActivity(this, 0, intent, 0)
mNotificationManager.notify(0, mNotification)
startActivity(intent)
}else {
mRemoteViews?.setTextViewText(R.id.tv_remote_notification_download_note, "下载失败!")
mNotification?.flags = Notification.FLAG_AUTO_CANCEL
mNotificationManager.notify(0, mNotification)
}
}
private fun updateMessage(progress:Int) {
val message = downloadHandler.obtainMessage()
message.arg1 = 1
message.obj = progress
downloadHandler.sendMessage(message)
}
private fun finishUpdateMessage(file: File?) {
val message = downloadHandler.obtainMessage()
message.arg1 = 2
message.obj = file
downloadHandler.sendMessage(message)
}
}
The End !