从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。 Android 6.0系统6.0以前,所有的权限,访问网络的权限,读取SD卡的权限,访问通讯录,拨打电话的权限都在安装的时候系统授予了要安装的应用。在系统运行的时候不需要对权限做任何的处理。用户在安装的时候一般都不知道这些权限用在了什么地方,这样做很不安全。一个应用很容易在一个Service中读取用户所有的通讯录信息发送到服务器上。Android 6.0后的动态权限让我们的系统更加安全,牺牲了用户的方便性,得到的是安全。下图是Android 6.0系统对拨打电话和管理电话权限组的询问。
Android 6.0把权限分为两种:Normal Permissions(正常权限)和Dangerous Permissions(危险权限)。其中危险权限又进行了分类。把所有的危险权限分成了几个组。正常权限不会给用户的隐私带来不安全,不需要动态申请,在应用安装的时候就已经被授予了。危险权限需要动态处理,只有用户批准了这些权限,应用才能被授予这些权限。
所有的普通权限:
- ACCESS_LOCATION_EXTRA_COMMANDS
- ACCESS_NETWORK_STATE
- ACCESS_NOTIFICATION_POLICY
- ACCESS_WIFI_STATE
- BLUETOOTH
- BLUETOOTH_ADMIN
- BROADCAST_STICKY
- CHANGE_NETWORK_STATE
- CHANGE_WIFI_MULTICAST_STATE
- CHANGE_WIFI_STATE
- DISABLE_KEYGUARD
- EXPAND_STATUS_BAR
- GET_PACKAGE_SIZE
- INSTALL_SHORTCUT
- INTERNET
- KILL_BACKGROUND_PROCESSES
- MODIFY_AUDIO_SETTINGS
- NFC
- READ_SYNC_SETTINGS
- READ_SYNC_STATS
- RECEIVE_BOOT_COMPLETED
- REORDER_TASKS
- REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- REQUEST_INSTALL_PACKAGES
- SET_ALARM
- SET_TIME_ZONE
- SET_WALLPAPER
- SET_WALLPAPER_HINTS
- TRANSMIT_IR
- UNINSTALL_SHORTCUT
- USE_FINGERPRINT
- VIBRATE
- WAKE_LOCK
- WRITE_SYNC_SETTINGS
所有的危险权限:
在开发应用的时候不管是正常权限还是危险权限都必须在应用的Manifest.xml文件中声明。如果设备运行的是Android 5.1或更低的系统,或者应用的目标SDK小于23,那么在Manifest.xml文件中列出的危险权限在安装的时候用户必须接受,要不应用没法安装。如果设备运行的是Android 6.0或更高的系统,或者应用的目标SDK大于等于23,那么在Manifest.xml文件中列出的危险权限,会在应用运行的时候被用户授予或拒绝。开发者需要在代码中对危险权限进行处理。
public class MainActivity extends AppCompatActivity {
final public static int REQUEST_CODE_ASK_CALL_PHONE = 123;
private String mMobile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
onCallWrapper("12345678912");
}
});
}
public void onCallWrapper(String mobile) {
this.mMobile = mobile;
int checkCallPhonePermisssion = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
if (checkCallPhonePermisssion != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
Toast.makeText(this, "shouldShowRequestPermissionRationale", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("应用需要开启拍照的权限,是否继续?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).show();
} else {
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
} else {
callDirectly(mobile);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_CALL_PHONE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted 授予权限
callDirectly(mMobile);
} else {
// Permission Denied 权限被拒绝
Toast.makeText(MainActivity.this, "Permission Denied",
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void callDirectly(String mobile) {
Intent intent = new Intent();
intent.setAction("android.intent.action.CALL");
intent.setData(Uri.parse("tel:" + mobile));
this.startActivity(intent);
}
}
上面这个简单的例子是通过点击一个按钮拨打电话。在6.0系统之前,我们只需要直接调用方法callDirectly()方法就可以了。增加了系统的安全性,但是多了很多代码上的处理。ContextCompat和
ActivityCompat分别是Context和Activity的兼容类,在上的代码中我们就不用去判断当前系统的API是多好了,在API23之前也能用上面代码中的方法。ContextCompat.checkSelfPermission()方法是用来判断,当前应用是否拥有某个权限。如果拥有我们可以直接调用callDirectly()方法拨打电话。如果当前应用没有拨打电话的权限,会调ActivityCompat.shouldShowRequestPermissionRationale()方法,注意当前系统还没有弹出让用户去选择是允许还是拒绝,这个时候shouldShowRequestPermissionRationale()方法返回false。所以接下来会执ActivityCompat.requestPermissions()方法会向系统去请求,系统会弹出一个对话框让用户去选择。onRequestPermissionsResult()方法用来处理用户的选择。这个方法可以监听用户选择的是允许还是拒绝。当用户选择了允许,那么直接调用callDirectly()方法拨打电话。当用户选择了拒绝,上面的代码只是提示一个Toast。
当用户选择了拒绝,那么在下次点击按钮拨打电话的时候ActivityCompat.shouldShowRequestPermissionRationale()方法会返回true,在上面的代码中我们会弹出一个对话框给用户,给用户一个提示。当用户选择确定,会向系统请求权限。当用户选择取消,关闭对话框什么也不做。当我们选择了不在提示并且选择了拒绝的时候ActivityCompat.shouldShowRequestPermissionRationale()方法返回false。
在app层的build.gradle中
dependencies {
// EasyPermissions
compile 'pub.devrel:easypermissions:0.3.0'
}
在app层的build.gradle中
dependencies {
// EasyPermissions
compile 'pub.devrel:easypermissions:0.3.0'
}
public class SecondActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
private static final int RC_CAMERA_PERM = 123;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraTask();
}
});
}
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
// 该应用已经有打电话的权限
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
EasyPermissions.requestPermissions(this, "需要获取系统的拍照的权限!", RC_CAMERA_PERM, Manifest.permission.CAMERA);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, List perms) {
Toast.makeText(this, "onPermissionsGranted", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionsDenied(int requestCode, List perms) {
Toast.makeText(this, "onPermissionsDenied", Toast.LENGTH_SHORT).show();
new AppSettingsDialog.Builder(this)
.setTitle("请求权限")
.setRationale("需要开启启用相机的权限才能继续下去!")
.build()
.show();
}
}
这个例子是点击一个按钮,处理拍照的事情,所以我们需要获取相机的权限。首先我们要复写Activity或者Fragment的onRequestPermissionsResult()方法。SecondActivity实现了EasyPermissions.PermissionCallbacks这个接口复写了onPermissionsGranted()和onPermissionsDenied()这两个接口。当用户选择了允许那么调用onPermissionsGranted()方法,当用户选择了拒绝那么调用onPermissionsDenied()方法。AppSettingsDialog是一个询问用户是否跳转到运行应用的设置界面去开启权限的对话框。
在app层的build.gradle中
dependencies {
// Permissions Dispatcher
compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
}
这种配置需要有个条件:Android Gradle Plugin >= 2.2
这个库用到了5个注解:
注解 | 是否必须 | 描述 |
---|---|---|
@RuntimePermissions | 是 | 注册在Acttivity或者Fragment上 |
@NeedsPermission | 是 | 再需要权限的方法上注册 |
@OnShowRationale | 否 | 被注解的方法可以提示为什么需要这个权限 |
@OnPermissionDenied | 否 | 如果用户拒绝了权限申请那么调用该方法 |
@OnNeverAskAgain | 否 | 如果用户选择了不再询问,调用该方法 |
这个库用到了Annotion Processor,需要用到一个应用编译期间产生的类,这个类的命名是:类名+PermissionsDispatcher。
@RuntimePermissions
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ThirdActivityPermissionsDispatcher.showContactsWithCheck(ThirdActivity.this);
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
ThirdActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@NeedsPermission({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showContacts() {
Toast.makeText(this, "读取通讯录", Toast.LENGTH_SHORT).show();
}
@OnPermissionDenied({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsDenied() {
Toast.makeText(this, "onContactsDenied", Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsNeverAskAgain() {
Toast.makeText(this, "onContactsNeverAskAgain", Toast.LENGTH_SHORT).show();
new AppSettingsDialog.Builder(this)
.setTitle("请求权限")
.setRationale("需要开启启用相机的权限才能继续下去!")
.build()
.show();
}
@OnShowRationale({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showRationaleContacts(final PermissionRequest request) {
// Toast.makeText(this, "showRationaleContacts", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setPositiveButton("继续", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage("需要通讯录权限")
.show();
}
}
具体的过程可以参考Demo。
https://github.com/googlesamples/easypermissions
https://github.com/hotchemi/PermissionsDispatcher
http://hotchemi.github.io/PermissionsDispatcher/
https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en
https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
系统所需要的权限
https://developer.android.com/reference/android/Manifest.permission.html
普通权限
https://developer.android.com/guide/topics/permissions/normal-permissions.html
危险权限
https://developer.android.com/guide/topics/security/permissions.html#perm-groups
在运行时请求权限
https://developer.android.com/training/permissions/requesting.html
解读Android官方开发指导 - 运行时权限
http://www.jianshu.com/p/0beb6243d650