zoukankan      html  css  js  c++  java
  • Permission 运行时权限 总结 翻译 [MD]

    博文地址

    我的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.

    特性:

    注解

    @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 a PermissionRequest object which can be used to continue or abort the current permission request upon user input. If you don't specify any argument for the method compiler will generate process{方法名}ProcessRequest and cancel{方法名}ProcessRequest. You can use those methods in place of PermissionRequest(ex: with DialogFragment)

    使用案例

    使用步骤

    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();//结束请求
            }
        }
    }
    

    官方文档:请求权限

    官方文档:Request App Permissions

    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 a standard 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 call requestPermissions(), 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 the assumption 假设 that particular 特定 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

  • 相关阅读:
    修改 dll
    SQLServer中char、varchar、nchar、nvarchar的区别:
    关于破解的一点心得
    asp.net 操作XML
    jquery autocomplete
    【转】height,posHeight和pixelHeight区别
    异常处理 Access to the path is denied
    asp.net 获得客户端 mac 地址
    cmd 跟踪路由
    Excel 宏
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/11710737.html
Copyright © 2011-2022 走看看