我的GitHub | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目录
对运行时权限的一些理解
在旧的权限管理系统中,权限仅仅在App安装时询问用户一次
,用户同意了这些权限App才能被安装,App一旦安装后就可以偷偷的做一些不为人知的事情了。
从Android6.0
开始,App可以直接安装
,App在运行时
由开发者决定在任一适当的时机一个一个询问用户是否授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App,这个Dialog是系统定义的,开发者不能定制,当App要求用户授予不恰当的权限的时候,用户可以拒绝
(然而,很多APP可能会在请求权限失败后直接退出,所以你往往无法拒绝),用户即使授予或拒绝后也可以在设置页面
对每个App的权限进行重新管理。
新的权限策略将权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC
等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写
等。
不需要运行时申请的权限
此类权限都是正常保护的权限,只需要在AndroidManifest.xml
中简单声明这些权限即可,安装即授权
,不需要每次使用时都检查权限,而且用户不能取消以上授权
,除非用户卸载App。
需要运行时申请的权限
所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0(API level 23)
或者更高级别的设备中,并且targetSdkVersion>=23
时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限而直接调用了相关代码,你的App可能就崩溃了。
综上所述,你需要注意:
- 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
- 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTS和GET_ACCOUNTS了。
- 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"。
其他情景:
1、targetSdkVersion小于等于22,但是设备系统版本大于等于6.0:
- app使用旧的权限管理策略
- 注册文件列出的权限将会在安装时要求用户授予权限
用户可以在设置列表中编辑相关权限,这对app能否正常运行有很大影响
2、targetSdkVersion大于等于23,但是设备系统版本小于6.0:
- app使用旧的权限管理策略
- 注册文件列出的权限将会在安装时要求用户授予权限
运行时权限使用案例
public class MainActivity extends ListActivity {
private static final int REQUEST_CODE = 20094;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"完整的授权过程演示"};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, array));
createFile();
}
private void createFile() {
try {
//如果将targetSdkVersion为22或以下,可以成功创建文件;如果改为23或以上,则失败(会抛异常)
File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".txt");
Toast.makeText(this, "结果:" + file.createNewFile(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "异常了", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
requestPermissions();
}
private void requestPermissions() {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
int permissionState = ContextCompat.checkSelfPermission(this, permissions[0]); //检查权限
if (permissionState != PackageManager.PERMISSION_GRANTED) { //没有权限,申请权限
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { //是否应该显示请求权限的说明
new AlertDialog.Builder(this)
.setTitle("通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限")
.setMessage("请求SD卡权限,作用是给你保存妹子图片,点击【好嘞】会重新尝试请求用户授权")
.setPositiveButton("好嘞", (dialog, which) -> ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE))
.create()
.show();
} else {
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE); //请求用户授权
}
} else {
Toast.makeText(this, "有权限了", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0) {
switch (requestCode) {
case REQUEST_CODE: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "有权限了", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "你拒绝了权限,我们已经没法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
}
}
}
}
}
}
开源库:PermissionsDispatcher
PermissionsDispatcher provides a simple annotation-based
API to handle runtime permissions.
This library lifts the burden 解除了负担
that comes with writing a bunch of 一系列
check statements whether a permission has been granted or not from you, in order to keep your code clean and safe.
特性:
- Kotlin support
- Special Permissions support
- 100% reflection-free 无反射
注解
@RuntimePermissions
和 @NeedsPermission
是必须的,其余注解均为可选。
注意:被注解的方法不能是私有方法,因为这些方法会被自动生成的类调用。
Annotation | Required | Description |
---|---|---|
@RuntimePermissions |
✓ | Register an Activity or Fragment to handle permissions |
@NeedsPermission |
✓ | Annotate a method which performs the action that requires one or more permissions 当用户给予权限时会执行该方法 |
@OnShowRationale |
optional | Annotate a method which explains why the permissions are needed. |
@OnPermissionDenied |
optional | Annotate a method which is invoked if the user doesn't grant the permissions |
@OnNeverAskAgain |
optional | Annotate a method which is invoked if the user chose to have the device "never ask again" about a permission |
OnShowRationale 方法的细节
It passes in aPermissionRequest
object which can be used tocontinue or abort
the current permission request upon user input. If you don't specify any argument for the method compiler will generateprocess{方法名}ProcessRequest
andcancel{方法名}ProcessRequest
. You can use those methods in place ofPermissionRequest
(ex: withDialogFragment
)
使用案例
使用步骤
1、声明权限
<uses-permission android:name="android.permission.CAMERA" />
2、引入框架
//注意:要将targetSdkVersion设为23或以上,否则这个库是无效的
//NOTE: 4.x only supports Jetpack. If you still use appcompat 3.x is the way to go.
implementation 'com.github.hotchemi:permissionsdispatcher:3.3.1'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
3、添加注解
4、点击 菜单栏 → Build → Make Project 重新编译整个项目
编译完成后,会在 appuildintermediatesclassesdebug
目录下生成一个与被注解的Activity同一个包下的辅助类,名称为被注解的Activity名称+PermissionsDispatcher
,例如MainActivityPermissionsDispatcher
。
测试代码
@RuntimePermissions
public class MainActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"开源库 PermissionsDispatcher"};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, array));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
MainActivityPermissionsDispatcher.performActionWithCheck(this);
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void performAction() {
Toast.makeText(this, "有权限了", Toast.LENGTH_SHORT).show();
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnPermissionDenied() {
Toast.makeText(this, "你拒绝了权限,我们已经没法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnShowRationale(final PermissionRequest request) {
new AlertDialog.Builder(this)
.setTitle("通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限")
.setMessage("请求SD卡权限,作用是给你保存妹子图片,点击【好嘞】会重新尝试请求用户授权")
.setPositiveButton("我知道了,继续吧", (dialog, which) -> request.proceed())
.setNegativeButton("我不需要,不要再请求了", (dialog, which) -> request.cancel())
.create()
.show();
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnNeverAskAgain() {
Toast.makeText(this, "你拒绝了权限,并勾选了不再提醒,已经没法愉快的玩耍了!", Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
}
自动生成的类
//final 类型的工具类
final class MainActivityPermissionsDispatcher {
private static final int REQUEST_PERFORMACTION = 0; //请求码
private static final String[] PERMISSION_PERFORMACTION = new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}; //需要的权限
private MainActivityPermissionsDispatcher() {} //私有构造方法
//注解处理器会检索所有带 @NeedsPermission 的方法名,生成相应的名为【方法名+WithCheck】的工具方法
//所有带注解的方法都不能是私有方法,不然只能通过反射调用,而这个库是100%不使用反射的
static void performActionWithCheck(MainActivity target) {
if (PermissionUtils.hasSelfPermissions(target, PERMISSION_PERFORMACTION)) { //检查是否已经有权限
target.performAction(); //有权限时执行
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_PERFORMACTION)) {
//判断是否需要显示提示,根据结果执行不同的回调
target.doOnShowRationale(new PerformActionPermissionRequest(target));
} else {
//不需要显示提示,所以开始通过系统API请求权限
ActivityCompat.requestPermissions(target, PERMISSION_PERFORMACTION, REQUEST_PERFORMACTION);
}
}
}
static void onRequestPermissionsResult(MainActivity target, int requestCode, int[] grantResults) {
switch (requestCode) { //处理授权结果
case REQUEST_PERFORMACTION:
if (PermissionUtils.verifyPermissions(grantResults)) { //授予了权限
target.performAction();
} else { //拒绝了权限
if (!PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_PERFORMACTION)) {
target.doOnNeverAskAgain(); //拒绝了权限,并勾选了不再提醒
} else {
target.doOnPermissionDenied(); //用户拒绝了权限
}
}
break;
default:
break;
}
}
private static final class PerformActionPermissionRequest implements PermissionRequest {
private final WeakReference<MainActivity> weakTarget; //弱引用,不影响GC
private PerformActionPermissionRequest(MainActivity target) {
this.weakTarget = new WeakReference<MainActivity>(target);
}
@Override
public void proceed() {
MainActivity target = weakTarget.get();
if (target == null) return;
//再次请求权限
ActivityCompat.requestPermissions(target, PERMISSION_PERFORMACTION, REQUEST_PERFORMACTION);
}
@Override
public void cancel() {
MainActivity target = weakTarget.get();
if (target == null) return;
target.doOnPermissionDenied();//结束请求
}
}
}
官方文档:请求权限
Every Android app runs in a limited-access sandbox
. If an app needs to use resources or information
outside of its own sandbox, the app has to request the appropriate 相应的
permission. You declare
that your app needs a permission by listing the permission in the app manifest
and then requesting that the user approve 批准
each permission at runtime
(on Android 6.0 and higher).
This page describes how to use the Android Support Library
to check for
and request permissions. The Android framework
provides similar methods as of Android 6.0 (API level 23), but using the support library makes it easier to provide compatibility
with older versions of Android.
Add permissions to the manifest
On all versions of Android, to declare that your app needs a permission, put a <uses-permission>
element in your app manifest, as a child of the top-level <manifest>
element.
The system's behavior after you declare a permission depends on how sensitive 敏感程度
the permission is. Some permissions are considered "normal" so the system immediately
grants them upon installation. Other permissions are considered "dangerous" so the user must explicitly 明确
grant your app access. For more information about the different kinds of permissions, see 保护级别.
Check for permissions
If your app needs a dangerous permission, you must check whether you have that permission every time
you perform an operation
that requires that permission. Beginning with Android 6.0 (API level 23), users can revoke 撤销
permissions from any app at any time, even if the app targets a lower API level. So even if the app used the camera yesterday, it can't assume 确认
it still has that permission today.
To check if you have a permission, call the ContextCompat.checkSelfPermission()
method. For example, this snippet
shows how to check if the activity has permission to write to the calendar:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
If the app has the permission, the method returns PERMISSION_GRANTED
, and the app can proceed 继续
with the operation. If the app does not have the permission, the method returns PERMISSION_DENIED
, and the app has to explicitly
ask the user for permission.
Request permissions
When your app receives PERMISSION_DENIED
from checkSelfPermission()
, you need to prompt
the user for that permission. Android provides several methods you can use to request a permission, such as requestPermissions()
, as shown in the code snippet below. Calling these methods brings up 显示
a standard Android dialog, which you cannot customize
.
How this is displayed to the user depends on the device Android version as well as the target version of your application, as described in the 权限概览.
Explain why the app needs permissions
In some circumstances
, you want to help the user understand why your app needs a permission. For example, if a user launches a photography
app, the user probably won't be surprised that the app asks for permission to use the camera, but the user might not understand why the app wants access to the user's location or contacts. Before your app requests a permission, you should consider providing an explanation
to the user. Keep in mind that you don't want to overwhelm 不胜其烦
the user with explanations; if you provide too many explanations, the user might find the app frustrating 很麻烦
and remove it.
One approach 方法
you might use is to provide an explanation only if the user has already denied
that permission request. Android provides a utility
method, shouldShowRequestPermissionRationale()
, that returns true if the user has previously 之前
denied the request, and returns false if a user has denied a permission and selected the Don't ask again option
in the permission request dialog, or if a device policy 政策
prohibits 禁止
the permission.
If a user keeps trying to use functionality
that requires a permission, but keeps denying the permission request, that probably means the user doesn't understand why the app needs the permission to provide that functionality. In a situation
like that, it's probably a good idea to show an explanation 解释
.
More advice
about how to create a good user experience when asking for permission is provided in 应用权限最佳做法.
Request the permissions you need
If your app doesn't already have the permission it needs, the app must call one of the requestPermissions()
methods to request the appropriate 相应
permissions. Your app passes the permissions it wants and an integer request code
that you specify 指定
to identify 识别
this permission request. This method functions asynchronously 异步
. It returns right away, and after the user responds to the prompt 响应提示
, the system calls the app's callback method with the results, passing the same request code that the app passed to requestPermissions()
.
The following code checks if the app has permission to read the user's contacts. If it does not have permission it checks if it should show an explanation for needing the permission, and if no explanation is needed, it requests the permission:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// Permission is not granted,Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
// Show an explanation to the user *asynchronously* -- don't block this thread waiting for the user's response!
// After the user sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);
// The callback method gets the result of the request.
}
} else {
// Permission has already been granted
}
The prompt 提示
shown by the system describes the 权限组 your app needs access to, not the specific permission.
Note: When your app calls
requestPermissions()
, the system shows astandard
dialog box to the user. Your app cannot configure or alter that dialog box. If you need to provide any information or explanation to the user, you should do that before you callrequestPermissions()
, as described in Explain why the app needs permissions.
Handle the permissions request response
When the user responds to your app's permission request, the system invokes 调用
your app's onRequestPermissionsResult()
method, passing it the user response. Your app has to override that method to find out whether the permission was granted. The callback is passed the same request code
you passed to requestPermissions()
. For example, if an app requests READ_CONTACTS access it might have the following callback method:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the contacts-related task you need to do.
} else {
// permission denied, boo! Disable the functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other permissions this app might request.
}
}
The dialog box shown by the system describes the permission group
your app needs access to; it does not list the specific permission. For example, if you request the READ_CONTACTS
permission, the system dialog box just says your app needs access to the device's contacts
. The user only needs to grant permission once for each permission group.
If your app requests any other permissions in that group (that are listed in your app manifest), the system automatically
grants them. When you request the permission, the system calls your onRequestPermissionsResult()
callback method and passes PERMISSION_GRANTED, the same way it would if the user had explicitly 明确
granted your request through the system dialog box.
如果您的应用请求该组中的其他任何权限(已在您的应用清单中列出),系统会自动授予这些权限。当您请求权限时,系统会调用您的 onRequestPermissionsResult() 回调方法并传递 PERMISSION_GRANTED,就像用户通过系统对话框明确同意了您的请求时的处理方式一样。
Note: Your app still needs to explicitly request every permission it needs, even if the user has already granted another permission in the same group.
In addition 此外
, the grouping of permissions into groups may change in future Android releases. Your code should not rely on theassumption 假设
thatparticular 特定
permissions are or are not in the same group.
For example, suppose you list both READ_CONTACTS and WRITE_CONTACTS in your app manifest. If you request READ_CONTACTS and the user grants the permission, and you then request WRITE_CONTACTS, the system immediately grants you that permission without interacting with 交互
the user.
If the user denies a permission request, your app should take appropriate
action. For example, your app might show a dialog explaining why it could not perform the user's requested action that needs that permission.
When the system asks the user to grant a permission, the user has the option 选择
of telling the system not to ask for that permission again
. In that case, any time an app uses requestPermissions() to ask for that permission again, the system immediately
denies the request. The system calls your onRequestPermissionsResult() callback method and passes PERMISSION_DENIED, the same way
it would if the user had explicitly rejected
your request again. The method also returns false if a device policy
prohibits
the app from having that permission. This means that when you call requestPermissions(), you cannot assume that any direct interaction with
the user has taken place 发生
.
To provide the best user experience
when asking for app permissions, also see 应用权限最佳做法.
Declare permissions by API level
To declare a permission only on devices that support runtime permissions, that is, devices running Android 6.0 (API level 23) or higher, include the uses-permission-sdk-23
tag instead of the uses-permission
tag.
When using either of these tags, you can set the maxSdkVersion
attribute to specify that, on devices running a higher version, a particular
permission isn't needed.
所有危险权限(组)
权限组:Manifest.permission_group
权限:Manifest.permission
中文解释参考
使用以下 adb 命令可以查看所有需要授权的权限组:
adb shell pm list permissions -d -g
不同系统可能稍有差异,不过基本都一样,以下为一个案例:
Dangerous Permissions:
group:com.google.android.gms.permission.CAR_INFORMATION 汽车资料
permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION
permission:com.google.android.gms.permission.CAR_MILEAGE
permission:com.google.android.gms.permission.CAR_FUEL
group:android.permission-group.CONTACTS 联系人
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE 【电话相关,高频使用,经常会变】
permission:android.permission.ANSWER_PHONE_CALLS ---API26新增的
permission:android.permission.READ_PHONE_NUMBERS ---API26新增的
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.ACCEPT_HANDOVER ---API28新增的
permission:android.permission.USE_SIP
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALL_LOG【电话相关,API28中从PHONE权限组抽离出来的】
permission:android.permission.READ_CALL_LOG
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.PROCESS_OUTGOING_CALLS
group:android.permission-group.CALENDAR 日历
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA 相机
permission:android.permission.CAMERA
group:android.permission-group.SENSORS 传感器
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION 【位置,高频使用】
permission:android.permission.ACCESS_FINE_LOCATION
permission:com.google.android.gms.permission.CAR_SPEED
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE 【存储,高频使用】
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE 麦克风
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS 短信
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
//省略了一小部分未分组的以及第三方应用(例如微博)定义的
2019-11-16