概述
默认情况下,Android应用程序是没有任何授权的,当应用程序需要使用设备的任何受保护功能(发送网络请求,访问摄像机,发送短信等)时,必须从用户那里获得相应的权限。
在Marshmallow之前,权限都在安装时在项目中的清单文件中定义。在Marshmallow之后,现在必须在运行时请求权限,然后才能使用。PermissionsDispatcher第三方库用于管理运行时权限。
Marshmallow之前的权限控制
当用户从Google Play商店安装应用程序时,系统会向用户显示该应用程序所需的权限列表(有些人将此称为“权限墙”),用户可以接受所有权限,然后继续安装应用程序或决定不安装应用程序。没有办法只授予该应用程序的某些权限,用户没有办法撤消在安装应用程序后的某些权限。
关于系统的权限列表:permissions
Marshmallow之后的权限控制
Marshmallow引入了运行时权限的概念,意味着你的targetSdkVersion<23的话需要做兼容性处理。
当你需要添加新权限时,请先检查是否属于PROTECTION_NORMAL
。在Marshmallow中,Google已将某些权限指定为“安全”,并称为“常规权限”。 这些东西,比如ACCESS_NETWORK_STATE,INTERNET等。 在安装时自动授予常规权限,并且不会提示用户请求权限。
常规权限必须在清单文件中定义:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
运行时权限
这类权限将向用户显示一个对话框,类似于以下:
- 将运行时权限添加到清单文件中:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codepath.androidpermissionsdemo" >
<uses-permission android:name="android.permission.READ_CONTACTS" />
...
</manifest>
- 你需要使用android.support.v4.app.ActivityCompat这个向系统申请权限的工具类,兼容了各种系统版本。如下:
- ActivityCompat.requestPermissions向系统申请一个或一组权 限
- ActivityCompat.checkSelfPermissionApp检查自己是否有某个权限
- ActivityCompat.shouldShowRequestPermissionRationale判断弹出对话框中是否含有“不再询问”的选择框
步骤
- 你要有一个运行Android 6.0系统的设备
- 将App的targetSdkVersion设置为23
- 把AndroidManifest.xml中申请的并且是危险的所有权限都列出来,用ActivityCompat.requestPermissions方法向系统申请权限
- 在所在的Activity中OverrideonRequestPermissionsResult方法接受系统权限申请的回调
- 处理回调,比如用户拒绝了某个权限,这时App可以弹出一个对话框描述一下App为何需要这个权限等等
// MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// In an actual app, you'd want to request a permission when the user performs an action
// that requires that permission.
getPermissionToReadUserContacts();
}
// Identifier for the permission request
private static final int READ_CONTACTS_PERMISSIONS_REQUEST = 1;
// Called when the user is performing an action which requires the app to read the
// user's contacts
public void getPermissionToReadUserContacts() {
// 1) Use the support library version ContextCompat.checkSelfPermission(...) to avoid
// checking the build version since Context.checkSelfPermission(...) is only available
// in Marshmallow
// 2) Always check for permission (even if permission has already been granted)
// since the user can revoke permissions at any time through Settings
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// The permission is NOT already granted.
// Check if the user has been asked about this permission already and denied
// it. If so, we want to give more explanation about why the permission is needed.
if (shouldShowRequestPermissionRationale(
Manifest.permission.READ_CONTACTS)) {
// Show our own UI to explain to the user why we need to read the contacts
// before actually requesting the permission and showing the default UI
}
// Fire off an async request to actually get the permission
// This will show the standard permission request dialog UI
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
READ_CONTACTS_PERMISSIONS_REQUEST);
}
}
// Callback with the request from calling requestPermissions(...)
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String permissions[],
@NonNull int[] grantResults) {
// Make sure it's our original READ_CONTACTS request
if (requestCode == READ_CONTACTS_PERMISSIONS_REQUEST) {
if (grantResults.length == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Read Contacts permission granted", Toast.LENGTH_SHORT).show();
} else {
// showRationale = false if user clicks Never Ask Again, otherwise true
boolean showRationale = shouldShowRequestPermissionRationale( this, Manifest.permission.READ_CONTACTS);
if (showRationale) {
// do something here to handle degraded mode
} else {
Toast.makeText(this, "Read Contacts permission denied", Toast.LENGTH_SHORT).show();
}
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
权限组
相关权限分组到一个权限组。 当应用请求属于特定权限组(即READ_CONTACTS)的权限时,Android会向用户询问更高级别的组(CONTACTS)。 这样,当应用程序稍后需要WRITE_CONTACTS权限时,Android可以自动授予此权限,而不会提示用户。
向后兼容
- 你的App指定的targetSdkVersion<23,不过你的设备或者模拟器是Marshmallow
- 你的app会继续使用旧的权限模型
- 将在安装时询问AndroidManifest中列出的所有权限。
- 用户将能够在安装应用程序后撤消权限
- 使用PermissionChecker.checkSelfPermission方法检查App自身有没有某一个权限,这个方法的返回结果只有三种:
- PERMISSION_GRANTED: 已授权
- PERMISSION_DENIED: 没有被授权
- PERMISSION_DENIED_APP_OP: 没有被授权(如果targetSdkVersion小于23)
- 你的App指定的targetSdkVersion>=23,不过你的设备或者模拟器低于Marshmallow
- 你的app会继续使用旧的权限模型
- 将在安装时询问AndroidManifest中列出的所有权限。
- 使用context.checkSelfPermission(permission)方法检查App自身有没有某一个权限。
try {
final PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
targetSdkVersion = info.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
public boolean selfPermissionGranted(Context context, String permission) {
// Android 6.0 以前,全部默认授权
boolean result = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (targetSdkVersion >= Build.VERSION_CODES.M) {
// targetSdkVersion >= 23, 使用Context#checkSelfPermission
result = context.checkSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
} else {
// targetSdkVersion < 23, 需要使用 PermissionChecker
result = PermissionChecker.checkSelfPermission(context, permission)
== PermissionChecker.PERMISSION_GRANTED;
}
}
return result;
}
存储权限
重新思考您是否需要读取/写入存储权限(即android.permission.WRITE_EXTERNAL_STORAGE或android.permission.READ_EXTERNAL_STORAGE),它会为您提供SD卡上的所有文件。 相反,您应该使用Context上的方法来访问外部存储上特定于软件包的目录。 您的应用程序始终可以访问对这些目录的读/写,因此无需请求权限:
// Application-specific call that doesn't require external storage permissions
// Can be Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_RINGTONES,
// Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PICTURES, or Environment.MOVIES
File dir = MyActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);