如何在Jetpack Compose中适当地处理权限问题

1,873 阅读6分钟

在Jetpack Compose中适当地处理权限问题

在安卓系统中,权限处理可以通过permissionsAPI 。在谷歌的伴奏库的帮助下,事情变得更容易了。

本教程将介绍我们如何在Jetpack Compose中处理权限。

先决条件

要继续学习本教程,读者应该具备以下条件。

目标

在本教程结束时,读者将能够。

  • 了解什么是权限以及何时使用权限。
  • 使用最有效的方式来处理Jetpack compose中的权限。
  • 在Jetpack compose中实现单个和多个权限。

术语

  • DisposableEffect - 当键改变或当可组合物离开组合物时,一个处理副作用的处理程序。
  • Lifecycle - 这是一个与android生命期相关的抽象类,允许一个对象检测状态并作出相应的反应。
  • Rationale - 这指的是一个行动所产生的过程的集合逻辑基础。对于这种情况,用户通过授予或撤销权限采取的行动。
  • States - 一个状态是一个值或一个随时间变化的元素。例如,每当互联网连接中断时,一个小吃店就会显示。

让我们开始吧 :)

什么是权限,它们在什么时候被使用?

在安卓系统中,权限定义了一个应用程序可以在用户的手机中访问什么。由于安全措施,一个应用程序不能访问手机的一些数据,这需要应用程序要求用户允许或拒绝该应用程序的访问。

接受权限将允许应用程序访问诸如联系人、短信等数据。每当应用程序需要用户授权来访问默认情况下无法访问的硬件或数据时,就会用到它们。

第一步:创建一个新的编排项目

要创建一个新的编译器项目。

  • 启动Android Studio,并选择新项目-->编排活动。
  • 将项目命名为PermissionsDemo ,然后点击完成来构建项目。

Create Project

第二步:设置依赖性

在这一步,我们要添加伴奏者权限的依赖关系。在应用级build.gradle 文件中添加这个依赖关系。

implementation 'com.google.accompanist:accompanist-permissions:0.21.1-beta'

本教程将使用Google Accompanist库来处理权限。你还可以用其他方式来处理,但它们要复杂得多。使用伴奏者库会使它更简单。

第三步:在清单文件上启用权限

我们将从实现单一权限开始。以后再看如何对多个权限进行操作。

要启用权限,请在清单文件上添加以下内容。

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <application ...>
        <activity ...>
        </activity>
    </application>
</manifest>

第四步:实现单一权限

创建一个函数并将其命名为SinglePermission()

我们将在这个函数中只请求读取手机外部存储的权限。这个函数定义了你想请求用户允许的权限。

创建一个权限状态将如下。

val permissionState =
        rememberPermissionState(permission = Manifest.permission.READ_EXTERNAL_STORAGE)

我们所说的正确的权限处理是什么意思?

正确的权限处理意味着正确地请求应用程序的权限(s),这意味着,与大多数开发者不同,我们将把我们的逻辑放在onStart

然而,Compose并没有onStart() 方法。相反,我们使用一个LocalLifecycleOwner ,并附加LifeCycleObserver ,以观察所有的活动和片段的生命周期。

代码将如下。

val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner, effect = {
    val eventObserver = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_START -> {
                permissionState.launchPermissionRequest()
            }
        }
    }
    lifecycleOwner.lifecycle.addObserver(eventObserver)

    onDispose {
        lifecycleOwner.lifecycle.removeObserver(eventObserver)
    }
})

DisposableEffect 处理程序补救的副作用,当键发生变化时,以及当可组合体离开组合体时,需要进行修复。在这种情况下,我们注册一个回调,在使用后被清理掉。每当钥匙 ,一次性的效果就会重新开始。lifecycleOwner

检查权限

现在让我们检查一下permissionState ,它是否被用户接受或拒绝。然后我们将应用逻辑。

代码如下。

when {
    permissionState.hasPermission -> {
        Text(text = "Reading external permission is granted")
    }
    permissionState.shouldShowRationale -> {
        Column {
            Text(text = "Reading external permission is required by this app")
        }
    }
    !permissionState.hasPermission && !permissionState.shouldShowRationale -> {
        Text(text = "Permission fully denied. Go to settings to enable")
    }
}

这里发生的是,我们正在检查权限的状态。hasPermission 表示权限是允许的。

语句permissionState.shouldShowRatonale 检查当权限被拒绝两次时。当第二次被拒绝时, 权限被认为是完全被拒绝的, 触发了检查权限状态的最后部分。

如果权限未被授予或被拒绝两次以上,应用程序会告诉用户打开设置并启用被拒绝的权限。

现在SinglePermission() 体将如下。

@SuppressLint("PermissionLaunchedDuringComposition")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SinglePermission() {
    val permissionState =
        rememberPermissionState(permission = Manifest.permission.READ_EXTERNAL_STORAGE)
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(key1 = lifecycleOwner, effect = {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> {
                    permissionState.launchPermissionRequest()
                }
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    })

    when {
        permissionState.hasPermission -> {
            Text(text = "Reading external permission is granted")
        }
        permissionState.shouldShowRationale -> {
            Column {
                Text(text = "Reading external permission is required by this app")
            }
        }
        !permissionState.hasPermission && !permissionState.shouldShowRationale -> {
            Text(text = "Permission fully denied. Go to settings to enable")
        }
    }
}

注意:记得用@ExperimentalPermissionsApi 来注释这些函数,以消除突出显示的错误,这表明权限API是在实践层面上的,是可以被改变的。

处理多个权限的工作

处理多个权限几乎与处理单个权限相似。在这种情况下,我们只在一个列表中声明权限。这样做的方法如下。

val permissionStates = rememberMultiplePermissionsState(
    permissions = listOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_FINE_LOCATION
    )
)

注意区别。我们用rememberMultiplePermissionsState() 而不是rememberPermissionState()

另外,我们通过循环检查每个权限的状态来一次检查每个权限。这可以按如下方式进行。

permissionStates.permissions.forEach { it ->
    when (it.permission) {
        Manifest.permission.READ_EXTERNAL_STORAGE -> {
            when {
                it.hasPermission -> {
                    /* Permission has been granted by the user.
                       You can use this permission to now acquire the location of the device.
                       You can perform some other tasks here.
                    */
                    Text(text = "Read Ext Storage permission has been granted")
                }
                it.shouldShowRationale -> {
                    /*Happens if a user denies the permission two times

                     */
                    Text(text = "Read Ext Storage permission is needed")
                }
                !it.hasPermission && !it.shouldShowRationale -> {
                    /* If the permission is denied and the should not show rationale
                        You can only allow the permission manually through app settings
                     */
                    Text(text = "Navigate to settings and enable the Storage permission")

                }
            }
        }
        Manifest.permission.ACCESS_FINE_LOCATION -> {
            when {
                it.hasPermission -> {
                    /* Permission has been granted by the user.
                       You can use this permission to now acquire the location of the device.
                       You can perform some other tasks here.
                    */
                    Text(text = "Location permission has been granted")
                }
                it.shouldShowRationale -> {
                    /*Happens if a user denies the permission two times

                     */
                    Text(text = "Location permission is needed")

                }
                !it.hasPermission && !it.shouldShowRationale -> {
                    /* If the permission is denied and the should not show rationale
                        You can only allow the permission manually through app settings
                     */
                    Text(text = "Navigate to settings and enable the Location permission")

                }
            }
        }
    }
}

这一切都被包裹在一个函数中,在MainActivity 。整个函数体将如下。

@ExperimentalPermissionsApi
@Composable
fun MultiplePermissions() {
    val permissionStates = rememberMultiplePermissionsState(
        permissions = listOf(
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.ACCESS_FINE_LOCATION
        )
    )
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(key1 = lifecycleOwner, effect = {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> {
                    permissionStates.launchMultiplePermissionRequest()
                }
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    })
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top
    )
    {
        permissionStates.permissions.forEach { it ->
            when (it.permission) {
                Manifest.permission.READ_EXTERNAL_STORAGE -> {
                    when {
                        it.hasPermission -> {
                            /* Permission has been granted by the user.
                               You can use this permission to now acquire the location of the device.
                               You can perform some other tasks here.
                            */
                            Text(text = "Read Ext Storage permission has been granted")
                        }
                        it.shouldShowRationale -> {
                            /*Happens if a user denies the permission two times

                             */
                            Text(text = "Read Ext Storage permission is needed")
                        }
                        !it.hasPermission && !it.shouldShowRationale -> {
                            /* If the permission is denied and the should not show rationale
                                You can only allow the permission manually through app settings
                             */
                            Text(text = "Navigate to settings and enable the Storage permission")

                        }
                    }
                }
                Manifest.permission.ACCESS_FINE_LOCATION -> {
                    when {
                        it.hasPermission -> {
                            /* Permission has been granted by the user.
                               You can use this permission to now acquire the location of the device.
                               You can perform some other tasks here.
                            */
                            Text(text = "Location permission has been granted")
                        }
                        it.shouldShowRationale -> {
                            /*Happens if a user denies the permission two times

                             */
                            Text(text = "Location permission is needed")

                        }
                        !it.hasPermission && !it.shouldShowRationale -> {
                            /* If the permission is denied and the should not show rationale
                                You can only allow the permission manually through app settings
                             */
                            Text(text = "Navigate to settings and enable the Location permission")

                        }
                    }
                }
            }
        }
    }
}

注意:请确保你用@Composable 来注释这些函数。另外,要确保你在主活动中逐一调用它们。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PermissionHandlingComposeDEmoTheme {
                //SinglePermission()
                MultiplePermissions()
            }
        }
    }
}

结论

使用伴奏者库来处理权限请求的方式更加省力。accompanist是许多库的集合,为Jetpack Compose提供了开发者的功能。

如果我们在应用程序启动时检查权限状态,那么权限处理是有效的。这将有助于避免尴尬的情况,即用户可以最小化应用,禁用应用内的权限设置,并返回到应用,这将导致应用不能正确运行。