Android 获取存储权限后首次获取文件拿不到uri的问题

本篇是记录实践开发过程中遇到的一个小问题:获取存储权限后选择文件没有回调。

一、产生的问题是什么

项目中有一个需求是用户可以选择手机中保存的某格式文件上传到我们的云服务,代码写完后在Android11及以上的版本跑的挺好的,但是在Android10及以下的手机版本首次获取到存储权限后去选择文件上传都获取不到文件的uri

刚开始以为是手上华为手机鸿蒙系统3.0(Android10 API29)获取存储权限的问题,于是去网上去找解决方案,还真找到了一篇"像模像样"的文章:【FAQ】从存储权限看HarmonyOS 3.0中应用适配

但是这篇文章漏洞百出MANAGE_EXTERNAL_STORAGE权限是Android 11才有的,华为手机鸿蒙系统3.0(Android10 API29)在适配的时候压根就不会走Android 11的代码分支,伪代码如下:

kotlin 复制代码
fun requestStoragePermissions(isSuccess: () -> Unit = {}) {
    //判断权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        val permissionArray = arrayOf(
            Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.READ_MEDIA_AUDIO,
            Manifest.permission.READ_MEDIA_VIDEO
        )
        //大于等于Android 13
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        //Android 11 12 如果获取所有文件需要权限MANAGE_EXTERNAL_STORAGE
    } else {
        val permissionArray = arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
       //小于等于Android 10,华为手机鸿蒙系统3.0(Android10 API29)获取存储权限走这里
    }
}

因为华为手机鸿蒙系统3.0(Android10 API29)获取存储权限只会走最后的else,所以压根不需要MANAGE_EXTERNAL_STORAGE权限。

那问题可能在哪里?为什么在Android10的手机上只有首次会获取权限后拿不到文件呢?但是在Android10以上的手机就是正常的?有没有可能在Android10及以下的手机上提前注册ActivityResultLauncher去拿文件有问题?

二、解决问题的思路

这里就有一个悖论,获取文件的ActivityResultLauncher注册需要在onStart之前,但是我又需要先获取存储权限后再注册ActivityResultLauncher,此时早就过了onStart的生命周期...

于是这里就只能采用代理绕过生命周期注册的方法了。

三、解决办法

之前看过一篇文章Android 动态权限申请从未如此简单,确实比之前的封装简化很多,于是现学现用了。

在上面的需求中,需要动态注册权限,还要动态注册获取文件的Launcher,统一封装成方法,这里就直接贴代码了,原理请查看原作者博客:

kotlin 复制代码
private val nextLocalRequestCode = AtomicInteger()

private val nextKey: String
    get() = "activity_rq#${nextLocalRequestCode.getAndIncrement()}"

/**
 * 申请权限
 */
fun AppCompatActivity.requestPermission(
    permission: String,
    onPermit: () -> Unit,
    onDeny: (shouldShowCustomRequest: Boolean) -> Unit
) {
    if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
        onPermit()
        return
    }
    var launcher by Delegates.notNull<ActivityResultLauncher<String>>()
    launcher = activityResultRegistry.register(
        nextKey,
        ActivityResultContracts.RequestPermission()
    ) { result ->
        if (result) {
            onPermit()
        } else {
            onDeny(!ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
        }
        launcher.unregister()
    }
    lifecycle.addObserver(object : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                launcher.unregister()
                lifecycle.removeObserver(this)
            }
        }
    })
    launcher.launch(permission)
}

/**
 * 申请权限组
 */
fun AppCompatActivity.requestPermissions(
    permissions: Array<String>,
    onPermit: () -> Unit,
    onDeny: (shouldShowCustomRequest: Boolean) -> Unit
) {
    var hasPermissions = true
    for (permission in permissions) {
        if (ContextCompat.checkSelfPermission(
                this,
                permission
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            hasPermissions = false
            break
        }
    }
    if (hasPermissions) {
        onPermit()
        return
    }
    var launcher by Delegates.notNull<ActivityResultLauncher<Array<String>>>()
    launcher = activityResultRegistry.register(
        nextKey,
        ActivityResultContracts.RequestMultiplePermissions()
    ) { result ->
        var allAllow = true
        for (allow in result.values) {
            if (!allow) {
                allAllow = false
                break
            }
        }
        if (allAllow) {
            onPermit()
        } else {
            var shouldShowCustomRequest = false
            for (permission in permissions) {
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                    shouldShowCustomRequest = true
                    break
                }
            }
            onDeny(shouldShowCustomRequest)
        }
        launcher.unregister()
    }
    lifecycle.addObserver(object : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                launcher.unregister()
                lifecycle.removeObserver(this)
            }
        }
    })
    launcher.launch(permissions)
}


/**
 * 申请获取手机里面的单格式文件
 */
fun AppCompatActivity.requestGetContent(
    mimeType: String,
    onGetUri: (Uri?) -> Unit
) {
    var launcher by Delegates.notNull<ActivityResultLauncher<String>>()
    launcher = activityResultRegistry.register(
        nextKey,
        ActivityResultContracts.GetContent()
    ) { uri ->
        onGetUri(uri)
        launcher.unregister()
    }
    lifecycle.addObserver(object : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                launcher.unregister()
                lifecycle.removeObserver(this)
            }
        }
    })
    launcher.launch(mimeType)
}

清单文件中注册存储权限:

kotlin 复制代码
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
    android:name="android.permission.MANAGE_EXTERNAL_STORAGE"    //这个权限有可能会导致App过不了审,过不了审就去掉
    tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

请求存储权限并获取手机里面的文件:

kotlin 复制代码
/**
 * 动态请求存储权限并获取文件
 */
fun requestStoragePermissionsAndGetFileUri() {
    //判断权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        //权限按需添加
        val permissionArray = arrayOf(
            Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.READ_MEDIA_AUDIO,
            Manifest.permission.READ_MEDIA_VIDEO
        )
        requestPermissions(permissionArray, onPermit = {
            requestGetContent("*/*"){uri ->
                //拿到文件uri了
            }
        }, onDeny = {
            //权限未全部同意
        })
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {   //如果不加MANAGE_EXTERNAL_STORAGE权限,那这个分支就不需要了
        if (Environment.isExternalStorageManager()) {
            requestGetContent("*/*"){uri ->
                //拿到文件uri了
            }
        } else {
            //去系统设置界面获取所有文件的权限
            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
            intent.data = Uri.parse("package:$packageName")
            startActivityForResult(intent, 200)
        }
    } else {
        val permissionArray = arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
        requestPermissions(permissionArray, onPermit = {
            requestGetContent("*/*"){uri ->
                //拿到文件uri了
            }
        }, onDeny = {
            //权限未全部同意
        })
    }
}

因为写博客为了清晰明了就写一起了,实际项目建议拆开来写。其中注意MANAGE_EXTERNAL_STORAGE权限有可能过不了审,过不了审就去掉。

获取uri后需要拿文件的真实路径,可以看我的这篇博客:Android ActivityResultContracts.GetContent 真实的文件路径

参考了以下资料

Android 动态权限申请从未如此简单

相关推荐
Kapaseker21 分钟前
2026年,我们还该不该学编程?
android·kotlin
雨白17 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk17 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING17 小时前
RN容器启动优化实践
android·react native
恋猫de小郭20 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker1 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 天前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe2 天前
Now in Android 架构模式全面分析
android·android jetpack