在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是系统自带的方法,获取它的具体实现:
/**
* 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.
*
* 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.
*
*
* @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。
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方法。
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是一个回调函数,当requestPermissions完成授权之后会调用这个方法,该方法接收三个参数,分别为int requestCode,@NonNull String[] permissions,@NonNull int[] 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" />
这样即可完成读取通讯录权限的授予。