Android应用首次授予存储权限时崩溃后续下载正常怎么解决?
问题原因
崩溃的根因是权限申请通过的回调中传入了空列表执行下载逻辑:
从堆栈信息可以看到IndexOutOfBoundsException: Index: 11, Size: 0,说明你尝试在大小为0的列表中取索引为11的元素,触发越界异常。
你在点击下载按钮时,如果检测到没有存储权限会触发权限申请,但是权限申请通过后,onRequestPermissionsResult回调里调用startDownloading时传的是ArrayList < ImageModel > ()——这是一个全新的空列表,而此时ViewPager的当前位置是11,空列表不存在对应索引的元素,直接触发崩溃。
后续操作正常是因为首次授权完成后,再次点击下载按钮不会走权限申请逻辑,直接传入有数据的全局列表执行下载,所以不会报错。
修复方案
最简修复
如果你页面的图片列表list是Activity的全局成员变量,直接修改onRequestPermissionsResult中的调用逻辑即可:
override fun onRequestPermissionsResult(requestCode:Int, permissions:Array<out String>, grantResults: IntArray){ super.onRequestPermissionsResult(requestCode, permissions, grantResults) when(requestCode) { STORAGE_PERMISSION_CODE -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 替换为空列表的调用,传入全局有数据的列表 startDownloading(list) } else { Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show() } } } }
优化方案(更稳定)
如果担心用户在申请权限的过程中滑动了ViewPager,导致权限通过后下载的不是用户之前点击的图片,可以额外新增成员变量存储待下载的目标图片:
- 新增全局成员变量存储待下载的图片数据
private var pendingDownloadModel: ImageModel? = null
- 点击下载按钮申请权限前,先存储当前要下载的图片
downloadBtn.setOnClickListener { val currentModel = list[viewPager.currentItem] if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // 先存下待下载的图片 pendingDownloadModel = currentModel ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE ) } else { startDownloading(currentModel) } } else { startDownloading(currentModel) } }
- 修改
startDownloading入参,直接接收单个图片模型,不需要传整个列表
private fun startDownloading(model: ImageModel){ Toast.makeText(this, "Downloading", Toast.LENGTH_SHORT).show() val downloadUrl = model.urls.full val request = DownloadManager.Request(Uri.parse(downloadUrl)) .setDestinationInExternalPublicDir( Environment.DIRECTORY_PICTURES, "/unsplash/${model.id}.jpg" ) .setTitle("Downloading").setDescription("downloading image").setAllowedOverMetered(true) .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) val manager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager myId = manager.enqueue(request) val br = object :BroadcastReceiver() { override fun onReceive(context:Context?, intent:Intent?){ val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) if (id == myId) { Toast.makeText( this@ViewPagerActivity, "Downloaded Successfully", Toast.LENGTH_SHORT ).show() } } } registerReceiver(br, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) }
- 权限通过后直接用预存的图片执行下载
override fun onRequestPermissionsResult(requestCode:Int, permissions:Array<out String>, grantResults: IntArray){ super.onRequestPermissionsResult(requestCode, permissions, grantResults) when(requestCode) { STORAGE_PERMISSION_CODE -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { pendingDownloadModel?.let { startDownloading(it) } pendingDownloadModel = null } else { Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show() pendingDownloadModel = null } } } }
额外注意事项
- 你当前代码中动态注册的下载完成广播没有解绑逻辑,需要在Activity销毁时调用
unregisterReceiver,避免内存泄漏。 - 如果你的应用要适配Android 13(API 33)及以上版本,
WRITE_EXTERNAL_STORAGE权限已经废弃,需要根据你的业务场景替换为对应的图片/视频/文件权限。
内容的提问来源于stack exchange,提问作者Muaz Khan
相关产品推荐
相关产品推荐

