简介

在Android应用开发过程中,经常会向用户申请获得手机的一些权限,以提升应用的交互友好性(如通讯录权限),有时候这些权限甚至是必不可少的(如连接网络等)。本篇博客就将对Android应用开发中的权限管理进行一定的探究与分析。


初识

在Android Studio中通过创建Login Activity,可以获得系统自动为我们编写好的权限申请代码,如下所示:

/**
 * Id to identity READ_CONTACTS permission request.
 */
private static final int REQUEST_READ_CONTACTS = 0;

private boolean mayRequestContacts() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return true;
    }
    if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
        return true;
    }
    if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
        Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                .setAction(android.R.string.ok, new View.OnClickListener() {
                    @Override
                    @TargetApi(Build.VERSION_CODES.M)
                    public void onClick(View v) {
                        requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                    }
                });
    } else {
        requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
    }
    return false;
}

/**
 * Callback received when a permissions request has been completed.
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    if (requestCode == REQUEST_READ_CONTACTS) {
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            populateAutoComplete();
        }
    }
}

故名思议,该代码申请的权限为REQUEST_READ_CONTACTS,即为申请阅读联系人,用于在登录过程中可以访问通讯录直接填写登录信息。
来分析这段代码,通过调用mayRequestContacts进入权限申请流程,该方法中由三个if语句构成:

  • if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)这个if语句是用来判断当前Android版本是否小于系统所定义的版本代号为M的Android版本,即Android 6.0,对应API为23)。当小于这个版本时会返回true,即权限获得。这一条判断是由于Android版本不同时申请权限方式不同造成的,我们之后再来分析。
  • if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)这个if语句很明显,是用来判断是否已经对应用授权了读取通讯录的权限,如果已经获取,则返回true。
  • if (shouldShowRequestPermissionRationale(READ_CONTACTS))这个if语句才是获得授权的主体,也是我们主要分析的对象。

深入

shouldShowRequestPermissionRationale(READ_CONTACTS)

shouldShowRequestPermissionRationale是系统自带的方法,获取它的具体实现:

/**
 * Gets whether you should show UI with rationale for requesting a permission.
 * You should do this only if you do not have the permission and the context in
 * which the permission is requested does not clearly communicate to the user
 * what would be the benefit from granting this permission.
 * <p>
 * For example, if you write a camera app, requesting the camera permission
 * would be expected by the user and no rationale for why it is requested is
 * needed. If however, the app needs location for tagging photos then a non-tech
 * savvy user may wonder how location is related to taking photos. In this case
 * you may choose to show UI with rationale of requesting this permission.
 * </p>
 *
 * @param permission A permission your app wants to request.
 * @return Whether you can show permission rationale UI.
 *
 * @see #checkSelfPermission(String)
 * @see #requestPermissions(String[], int)
 * @see #onRequestPermissionsResult(int, String[], int[])
 */
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
    return getPackageManager().shouldShowRequestPermissionRationale(permission);
}

可以看到,该方法又调用了另一个方法,我们在此不再深究,着重看该方法的注释。注释中说,该方法当且仅当应用需要申请权限,且所申请的权限用户不一定可以理解原因的时候才需要调用,调用的目的是确定开发者需要展示一个可视化说明来请求权限。
READ_CONTACTS是系统定义的一个字符串常量android.permission.READ_CONTACTS


Snackbar.make

当确定需要一个可视化说明后,则创建一个Snackbar。
Snackbar是Android Support Design Library库中的一个控件,可以在屏幕底部快速弹出消息,比Toast更加好用,感兴趣的读者可以进一步了解。

Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                    .setAction(android.R.string.ok, new View.OnClickListener() {
                        @Override
                        @TargetApi(Build.VERSION_CODES.M)
                        public void onClick(View v) {
                            requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                        }
                    });

可以看到,当点击OK(即android.R.string.ok定义的字符串)后会调用requestPermissions方法,这个便是赋予应用权限的方法。
值得一提的是,当该权限不需要可视化权限的时候(即shouldShowRequestPermissionRationale返回false),会直接调用requestPermissions方法。


requestPermissions
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (mHasCurrentPermissionsRequest) {
        Log.w(TAG, "Can reqeust only one set of permissions at a time");
        // Dispatch the callback with empty arrays which means a cancellation.
        onRequestPermissionsResult(requestCode, new String[0], new int[0]);
        return;
    }
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}

该方法的第一个if语句是用来保证在同一时间,只能允许一组(不是一个)权限,之后会创建一个Intent,并开始对这一组权限进行授权。


onRequestPermissionsResult

回到第一段代码,onRequestPermissionsResult是一个回调函数,当requestPermissions完成授权之后会调用这个方法,该方法接收三个参数,分别为int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults。

  • requestCode:在requestPermissions中我们传入了这个参数,它是用来在onRequestPermissionsResult方法中与我们事先定义好的请求代号匹配,以确保的确是该请求被同意并完成授权;
  • permissions:同样是在requestPermissions传入的参数,是系统定义的该组权限的字符串格式的名称;
  • grantResults:请求结果。

在方法体中:

if (requestCode == REQUEST_READ_CONTACTS) {
    if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        populateAutoComplete();
    }
}

第一个if语句是用来检测onRequestPermissionsResult处理的请求是否为我们定义的读取通讯录请求;
第二个if语句是用来检测请求结果是否为授予权限,其中第一个条件是该组请求只包含一个请求,第二个条件是该组请求的第一个请求的处理结果是授予权限
通过这两个if判断后,便可以继续获取权限之后的代码了。


补充

之前有提到,当当前Android版本小于系统所定义的版本代号为M的Android版本时,是会直接返回true而不继续执行接下来的请求权限代码的。这是因为在之前,Android采取的是安装时授权,即当安装应用程序时,会出现向用户请求权限的界面,用户确认之后才可以开始安装,而在实际情况中,用户通常都不会仔细查看这些权限,导致了用户手机的安全隐患,因此从Android 6.0(即版本代号M)开始,Android便更改为运行时授权,即当应用程序运行且运行到需要该权限的时候,才会向用户请求权限。这一方面保证了用户手机的安全性,同时也实现了当用户不对某一请求授权时,只会影响一部分功能,而不会影响其它功能的使用。

老版本请求权限方式

当Android版本低于Android 6.0时,授权方式非常简单,只需要在AndroidManifest.xml中添加对应的权限声明即可。如:

<uses-permission android:name="android.permission.READ_CONTACTS" />

这样即可完成读取通讯录权限的授予。