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

一、发生的问题是什么

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

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

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

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,一致封装成方法,这儿就直接贴代码了,原理请检查原作者博客:


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)
}

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

<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" />

恳求存储权限并获取手机里边的文件:

/**
 * 动态恳求存储权限并获取文件
 */
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 动态权限恳求从未如此简略