权限管理 -1
随着棉花糖的出现,一种新的权限模式添加到了安卓中,这也导致开发者需要对安卓权限管理采取稍微不同的方法了。在这个系列中,我们将从技术和用户体验的角度,探讨处理权限请求的方法。
一般而言app权限分为两种情况:一种是app核心功能所需的权限,没有这个权限都不能正常运行;一种是次要功能所需的权限。比如,对于一个相机app来说,CAMERA权限就是核心功能的一部分,一个不能拍照的相机应用完全就是无用的。但是也存在一些额外的功能,没有它app还是可以使用,比如为相片标注位置,这是一个不错的功能,需要ACCESS_FINE_LOCATION权限,但也并不是没有它app就不能操作了。
同样,我们准备在下一个系列文章中着手一个app,但是因为某些用处它需要两个权限:RECORD_AUDIO 和 MODIFY_AUDIO_SETTINGS。为了获取这些权限,我们需要像通常那样在Manifest中声明它们:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.stylingandroid.permissions">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity" />
<activity
android:name=".PermissionsActivity"
android:label="@string/title_activity_permissions"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
这是至api 1之后在安卓上声明权限的标准方法。但是,一旦我们指定targetSdkVersion为23或者之后我们还需要在运行时请求这些所需的权限。这很重要,因为已经出现了很多开发者把targetSdkVersion飙到了最新,然后发现自己的app疯狂的崩溃,这是由于他们没有实现执行运行时权限请求的代码。当你已经把一个targeting API 为23或者之后的app发布到了Google Play上,这更是一个问题,你无法立即把那个apk的targeting API替换成更早的版本。
这里需要指出的是,目前已经有几个用来简化运行时权限请求过程的库。它们的质量和用处各异,但是我觉得在使用这些库之前,理解背后的过程还是相当重要的,否则可能因为你不了解自己所用的库到底做了什么而遇到问题。这也是这个系列文章的主要写作动机。
我们需要的两个权限刚好落在了两种权限中:RECORD_AUDIO 被视为敏感权限,而 MODIFY_AUDIO_SETTINGS被视为普通权限。一个敏感权限就是一个可能危及隐私和安全的权限;而普通权限则是为了获取app域之外的资源,但是对用户隐私只有很少或者没有风险。普通权限系统自动授权,而敏感权限则需要在运行时让用户明确授予。
在这个过程中,我们需要做的第一件事就是判断我们是都被授予了相关权限。在 API 23中,为了检查某个特定的权限是否已经被授权,Context中添加了几个新的方法。但是,为了避免自己写API-level 检查,最好使用ContextCompat而不是直接使用Context:
class PermissionsChecker {
private final Context context;
public PermissionsChecker(Context context) {
this.context = context;
}
public boolean lacksPermissions(String... permissions) {
for (String permission : permissions) {
if (lacksPermission(permission)) {
return true;
}
}
return false;
}
private boolean lacksPermission(String permission) {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED;
}
}
这段代码其实很简单-ContextCompat的checkSelfPermission方法顾名思义就是检查权限的意思,它返回 PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。其余的代码是这个app所需的,用于检查所需的某个权限是否还未被授权。
这里还得唠叨几句ContextCompat为我们做了的事情。当运行在不支持运行时权限模式的Marshmallow以前的设备上的时候,checkSelfPermission()方法总是会返回 PackageManger.PERMISSION_GRANTED - 老的系统版本上总是隐式的授予权限。因此我们跨系统版本也只需一个方法调用,也不用去写什么检查API-level 的代码。
你可能会好奇为什么为了这个专门写了个类,主要是后面所有的Activity中都要做这个检查,所以把这个检查的逻辑独立出来,减少重复,提高代码的可维护性。
要早在Activity中实际使用它,我们只需传入activity所需的权限列表:
public class MainActivity extends AppCompatActivity {
private static final String\[\] PERMISSIONS = new String\[\] {Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionsChecker checker = new PermissionsChecker(this);
if (checker.lacksPermissions(PERMISSIONS)) {
Snackbar.make(toolbar, R.string.no_permissions, Snackbar.LENGTH_INDEFINITE).show();
}
.
.
.
}
}
简单吧。
在 pre-Marshamllow的设备上正常工作:
但是我们还没有为Marshmallow和之后的设备做任何处理 - 我们只是显示一个Snackbar而已:
对于特殊权限的请求是更复杂的东西。我们将在下篇文章探讨。
本文的源代码在 这里。
作者Mark Allison。本文最初发表在 Styling Android。