前段时间开端阅览郭霖大神的《第一行代码》,跟着书上的代码开发了一款天气App。由于以为手动获取地址实在不行方便,于是开端尝试写一个主动获取定位的feature。

随着现在用户关于隐私的重视不断提高,Android也在 6.0(M/API23) 开端对运用的权限进行了进一步的标准。从曾经在装置时告知用户运用将会运用的权限,到如今运行时权限。关于用户来说这是个好消息,可是关于开发者来说,这意味着需求针对不同的权限向用户进行恳求,这自然是一个头疼的问题。

这篇文章就以我自己对运用的一个需求 —— 获取“模糊定位”权限 为例,向咱们展示一下怎么让恳求权限愈加符合用户的逻辑,愈加符合开发要求,愈加符合Android开发标准。

关于当前Android的权限分类,请参阅 Android Developer Guides

关于权限的恳求,咱们也需求考虑是否做到了权限需求最小化,具体可以参阅 Android开发团队的主张

提示:此文仅叙述怎么更好的恳求权限,关于权限的具体介绍,还请各位移步 这篇文章 进行阅览


STEP0:整理 权限运用/恳求 的流程

根据Android Developer Guides的这篇文章,咱们可以发现恳求权限的全流程首要分为以下几个过程:

  1. 判别在没有权限的情况下是否能提供功用。假如可以,跳至5
  2. 判别是否声明权限
  3. 判别权限是否为运行时权限 (Runtime Permission)。假如不是,跳至5
  4. 向用户宣布权限恳求
  5. 完成权限恳求流程。持续运用运用功用无法提供对应功用

更符合逻辑、更规范的获取权限 | Android

虽然从流程上看起来,运用只要一次向用户获取权限的时机。可是Android其实还是给予了开发者时机再次向用户解说权限用处并再次恳求权限的时机。

咱们可以经过Activity.shouldShowRequestPermission办法判别用户是否已挑选 “回绝并不再问询” 选项。当返回值为true时,咱们可以运用Toast/Snackbar/Dialog向用户解说权限用处,并引导用户再次颁发权限。

STEP1:在Manifest中注册权限

由于关于天气运用而言,并不需求过于精确的地理信息。关于用户而言,经过粗略的坐标获取天气信息现已足够,所以考虑在注册权限时运用android.permission.ACCESS_COARSE_LOCATION

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    ...
</manifest>

留意,假如在编写完代码后Build&Run时发现恳求权限闪退,可以检查一下是否忘掉声明权限。当然,假如观察Logcat,体系也会打印error信息并提示去Manifest声明权限。 (不会吧不会真的有人忘掉吧)

STEP2:判别运用是否拥有权限

在全体的流程中,咱们先需求考虑用户是否现已颁发运用对应的权限。当咱们获得权限时,则直接执行对应的事务逻辑(在本例中是获取定位);若没有获取用户颁发的权限,则进行权限授权流程。

// set a button to use auto position feature
val getLocationBtn: Button = findViewById(R.id.request_location_btn)
// set button listener to determine whether user granted the permission
getLocationBtn.setOnClickListener {
    // check permission here
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    ) {
        // permission granted -> do get position feature here
        doGetPosition()
    } else {
        // permission denied -> use this to request location permission,
        //                      and we will expaine this later in artical
        permissionRequestLauncher.launch("android.permission.ACCESS_COARSE_LOCATION")
    }
}

这儿运用到了一个permissionRequestLauncher变量。很显着,咱们在此处运用了它的lunch办法,并传入了对应的 权限称号(String) 来进行权限的恳求,关于它的具体介绍,下面就将说到。

STEP3:运用Acitivity Result API恳求权限

在之前,咱们恳求权限需求运用ActivityCompat.requestPermission办法来恳求权限,并手动重写onRequestPermissionResult办法完成恳求回调。而在恳求和回调之间需求运用requestCode来坚持恳求和回调处理一一对应。

// this is java code, not kotlin
// source url here: https://zhuanlan.zhihu.com/p/76599492
private void callPermission(){
    if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
    } else {
        callPhone();
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode == 1){
        if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
            callPhone();
        }else {
            Toast.makeText(this, "权限未授权!", Toast.LENGTH_SHORT).show();
        }
    }
}

很显着,这样的代码十分混乱、不明晰,并且假如在恳求权限较少的时分,这样的代码还会显得十分不简洁。这时分无妨考虑一下运用Activity Result API来处理这个问题。

咱们首要需求声明一个ActivityResultLauncher<I>变量。在API中,此泛型用于表示输入进去用于创立Intent的类型,此处我运用String类型,传入android.permission.ACCESS_COARSE_LOCATION(见STEP2,具体原因后边讲解)

lateinit var permissionRequestLauncher: ActivityResultLauncher<String>

接下来,咱们在onCreate办法中对permissionRequestLauncher进行初始化:运用ComponentActivity.registerForActivityResult办法返回一个ActivityResultLauncher<I>目标。

override fun onCreate(savedInstanceState: Bundle?) {
   ...
    permissionRequestLauncher =
        registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
            if (isGranted) {
                doGetPosition()
            } else {
                doPermisionDenied()
            }
        }
}

这儿让咱们来看一下registerForActivityResult的参数,以便咱们了解它的用法。

@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull ActivityResultContract<I, O> contract,
        @NonNull ActivityResultCallback<O> callback) {
    return registerForActivityResult(contract, mActivityResultRegistry, callback);
}

关于contract变量,而上面咱们运用的RequestPermission()生成并返回的的RequestPermission类目标,便是Activity Result API中界说好的用于恳求权限Contract类,所以咱们只需求直接运用即可。同样的,ActivityResultContract.java文件中也存在许多其他现已被完成好的(extands)的类,当咱们在其他场景下运用Activity Result API时就可以运用这些其他的类。

留意,RequestPermission类完成的是ActivityResultContract<String, Boolean>。所以对应的Launch中的InputOutput类型便是StringBoolean类型了。(不记得了?回去看看STEP2吧)

关于callback变量,就比较显着了,这便是一个callback函数。当咱们RTFSC的时分,发现它其实只要一个onActivityResult办法,而它的返回值类型便是刚刚咱们说到的Output类型。OK,一切都明了了。所以咱们只需求一个返回值为用Boolean表示授权成果的lambda函数就好了。

OK了,现在再回去看看,是不是理解了呢?(假如还不理解,主张再回去看一遍)

留意:ActivityResultLacuncher<I>变量的创立时期需求在ActivityonCreate办法中,或者是FragmentonAttach/onCreate办法中

为什么我会知道呢,给咱们看看我其时犯的错x

java.lang.IllegalStateException: Fragment is attempting to registerForActivityResult after being created. Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()).

STEP4:完善回绝权限逻辑

STEP0的流程整理中说到了,在用户没有挑选“回绝且不再问询”之前,运用仍有时机向用户进行权限用处说明并再次恳求权限。那么咱们接下来就利用前面所说到的Activity.shouldShowRequestPermission办法来判别一下是否仍然存在恳求权限的时机,并向用户解说权限用处。

fun doPermisionDenied(): Unit {
        // determine whether still can request permission & explain
        if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) {
            Toast.makeText(this, "permission denied!(still can get permission)", Toast.LENGTH_SHORT)
                .show()
            // request permission again
            permissionRequestLauncher.launch("android.permission.ACCESS_COARSE_LOCATION")
        } else
            Toast.makeText(this, "permission denied!(go to settings)", Toast.LENGTH_SHORT).show()
    }

这儿我偷了个懒,没有写引导用户解说权限用处。各位可以根据自己的需求和事务场景挑选适宜的计划。(才不是想不到什么样比较好呢)


在经过了四个简略的过程后,咱们就现已完成了一个简略的权限获取的功用。相比于RequestPermission,它更简洁明了,愈加明晰;一起还甩开了RequestCode这个大包袱,让咱们的代码愈加明晰。

当然,关于一次性恳求多个权限,过程也是和上面的代码十分相似的,可以参阅一下Android Developer Guide

在看完整篇文章后,相信你也对Activity Result API有了更多的猎奇。期望上的另一篇文章能解答你更深层的疑问

期望这篇文章可以帮到你。第一次写长文,不足之处还各位请不吝赐教