android权限_android权限请求 - CSDN
精华内容
参与话题
  • Android权限管理

    千次阅读 2019-07-17 12:39:52
    Android权限管理(PermissionsDispatcher框架使用)简述一、Android权限二、6.0以上权限管理 简述 由于对于安全考虑,Android对于权限的管理更加的严谨,以6.0位界分为两种处理方式:6.0以下系统还是保持旧的处理...

    Android权限管理(PermissionsDispatcher框架使用)

    简述

    由于对于安全考虑,Android对于权限的管理更加的严谨,以6.0位界分为两种处理方式:6.0以下系统还是保持旧的处理方式,直接在AndroidManifest清单中注册相应权限就可以;6.0以上系统就需要根据权限的等级(普通权限和危险权限)进行权限注册,如果是普通权限还是依照之前的处理方式直接在AndroidManifest清单中注册即可,但是危险权限不仅需要在AndroidManifest清单中注册且还需要在使用时动态申请
    其实现在主流权限管理框架有三个,分别为PermissionsDispatcher、RxPermissions和easypermissions;在使用中作者方向从代码简洁及易用性来说PermissionsDispatcher跟优于其他两种两种框架;PermissionsDispatcher是采用注解的方式进行权限管理,RxPermissions是基于RxJava的权限管理,easypermissions是谷歌推出的;有兴趣的朋友加深去了解一下;但不管怎么样这三个框架都简化了需要动态管理的权限。

    一、Android权限

    在这里插入图片描述在这里插入图片描述
    terminal中使用这个命令可以列出调试手机的所有权限,包含应用自定义的权限:

    adb shell pm list permissions
    

    注意:并不是所有的危险权限都能申请,有某些权限系统是默认禁止的,目前暂时没有任何办法获取

    二、6.0以上权限管理

    在介绍该PermissionsDispatcher框架前我简述一下,官方或网上给出的很多注册方式都没问题的,但是对于Android本身的问题(千奇百怪手机,不同品牌对于系统再次定制),有时总会出现由于考虑不周总会出现这样那样的问题会让你焦头烂额的,我也是经过多种尝试,最终觉的还是这个框架还是目前来说最稳定的管理方式;
    介绍:PermissionsDispatcher是一个基于注解、帮助开发者简单处理Android 6.0系统中的运行时权限的开源库、避免开发者编写大量繁琐的样板代码并进行了简化处理。
    开源地址:https://github.com/hotchemi/PermissionsDispatcher
    文档介绍:http://hotchemi.github.io/PermissionsDispatcher/
    下面开始介绍如何在Android studio使用该框架:

    1、添加依赖
    在项目工程下的build.gradle文件中加入对maven仓库依赖引入的支持

    allprojects {
        repositories {
            jcenter()
            mavenCentral()
        }
    }
    

    之后在module下的build.gradle文件中添加两项依赖:

    implementation 'com.github.hotchemi:permissionsdispatcher:2.3.1'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
    

    并将targetSdkVersion设为23(一定要大于等于23),即:targetSdkVersion 23
    2、在Plugins加入PermissionsDispatcher插件
    在Android studio加入PermissionsDispatcher插件有益于你后期快速使用PermissionsDispatcher,可以说几乎于是一键导入;
    第一步在AS File点击Setting进入Setting操作面板

    第二部,在Setting操作面板选择Plugins,在右上角输入框中输入PermissionsDispatcher搜索,并安装,安装后需要重启
    在这里插入图片描述
    3、在工程中使用PermissionsDispatcher插件开始布局Permissions管理
    注意:在使用权限管理框架前,一定要明确一点,一定要Activity/Fragment中使用
    第一步:在需要使用权限管理的Activity/Fragment类中右击如下图,选择Generate -> Generate Runtime Permissions…
    第二步:如下图,在权限管理区域选择相应的权限在这里插入图片描述
    然后在Annotations区域,选择相应的注解,选择的注解同时需要自定义对应的方法名;
    最后点击“Generate”按钮;最好强调移一下点击“Generate”后会有个弹窗,可以选择rebuild,也可以不用,但一定要在生成的方法前使用public,且再次将工程build-》rebuild project一下,结果如下图:
    在这里插入图片描述
    以下为注解说明(注:带注释的方法一定不能private,一定要为public):

    注解 是否必须要 描述
    @RuntimePermissions 在Activity的Class声明此注解,来处理我们的权限
    @NeedsPermission 请求的权限成功后执行的方法
    @OnShowRationale 在申请权限前解释为什么需要这些权限
    @OnPermissionDenied 当用户拒绝授权时将调用该方法
    @OnNeverAskAgain 当用户选择了 “不再提醒” 将调用该方法

    a、@RuntimePermissions注解:这是必须使用的注解,用于标注在你想要申请权限的Activity或者Fragment上

    @RuntimePermissions
    public class TestActivity extends UMengBaseActivity {
    

    b、@NeedsPermission注解:这也是必须使用的注解,用于标注在你要获取权限的方法,注解括号里面有参数,传入想要申请的权限。也就是说你获取了相应的权限成功后就会执行这个方法

    @NeedsPermission(Manifest.permission.CAMERA)
    public void NeedsMethod() {
    	
    }
    

    c、@OnShowRationale注解:这个不是必须的注解,用于标注申请权限前需要执行的方法,注解
    括号里面有参数,传入想要申请的权限,而且这个方法还要传入一个PermissionRequest对象,这个对象有两种方法:proceed()让权限请求继续,cancel()让请求中断。也就是说,这个方法会拦截你发出的请求,这个方法用于告诉用户你接下来申请的权限是干嘛的,说服用户给你权限

    @OnShowRationale(Manifest.permission.CAMERA)
    public void RationaleMethod(final PermissionRequest request) {
    
    }
    

    d、@OnPermissionDenied注解:这个也不是必须的注解,用于标注如果权限请求失败,但是用户没有勾选不再询问的时候执行的方法,注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败之后的处理,如像用户解释为什么要申请,或者重新申请操作等

    @OnPermissionDenied(Manifest.permission.CAMERA)
    public void DeniedMethod() {
    
     }
    

    e、@OnNeverAskAgain注解:这个也不是必须的注解,用于标注如果权限请求失败,而且用户勾选不再询问的时候执行的方法,注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败并选择不再询问之后的处理。例如,可以告诉作者想开启权限的就从手机设置里面开启

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    public void AskMethod() {
    
    }
    

    为什么注解方法前一定要使用public:
    原因是只要我们实现了@RuntimePermissions和@NeedsPermission这两个必须的注解之后,再build一次project之后,编译器就会在在app\build\intermediates\classes\debug目录下与被注解的Activity同一个包下生成一个辅助类,名称为 “被注解的Activity的名称+PermissionsDispatcher” 的辅助类,用来调用被注解的Activity的方法(就是因为这个所以被注解的方法不能private,private方法的作用域不在其他的类)。所以,第一次用的话,要注解好之后,build一次,下面的方法里面的PermissionsDispatcherActivityPermissionsDispatcher才不会令AS报红。

    最后在相应的方法调用申请方法就可以了
    类名:类名+PermissionsDispatcher
    方法名:@NeedsPermission注解的方法名+WithCheck

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        TestActivityPermissionsDispatcher.NeedsMethodWithCheck(this);//调用此方法即可动态申请权限;
    }
    
    展开全文
  • android 6.0权限全面详细分析和解决方案

    万次阅读 多人点赞 2016-07-24 10:28:35
    android权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西,而且一般用户安装的时候很少会去仔细看权限列表,更不会去深入...

    原文:

    http://www.2cto.com/kf/201512/455888.html

    http://blog.csdn.net/yangqingqo/article/details/48371123

    http://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en


    一、Marshmallow版本权限简介

    android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西,而且一般用户安装的时候很少会去仔细看权限列表,更不会去深入了解这些权限可能带来的相关危害。所以在android 6.0 Marshmallow版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。



    二、旧版本app兼容问题

      那么问题来了,是不是所有以前发布的app都会出现问题呢?答案是不会,只有那些targetSdkVersion 设置为23和23以上的应用才会出现异常,在使用危险权限的时候系统必须要获得用户的同意才能使用,要不然应用就会崩溃,出现类似
    java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider

    的崩溃日志。所以targetSdkVersion如果没有设置为23版本或者以上,系统还是会使用旧规则:在安装的时候赋予该app所申请的所有权限。所以app当然可以和以前一样正常使用了,但是还有一点需要注意的是6.0的系统里面,用户可以手动将该app的权限关闭,如下图


      
      那么问题又来了,如果以前的老应用申请的权限被用户手动关闭了怎么办,应用会崩溃么?我们来试一试
      这里写图片描述
      好吧,可以庆幸了一下了,不会抛出异常,不会崩溃,只不过调用那些被用户禁止权限的api接口返回值都为null或者0,所以我们只需要做一下判空操作就可以了,不判空当然还是会崩溃的喽。


    三、普通权限和危险权限列表

      现在对于新版本的权限变更应该有了基本的认识,那么,是不是所有权限都需要去进行特殊处理呢?当然不是,只有那些危险级别的权限才需要。

    PROTECTION_NORMAL类权限

    当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:

    android.permission.ACCESS LOCATIONEXTRA_COMMANDS 
    android.permission.ACCESS NETWORKSTATE 
    android.permission.ACCESS NOTIFICATIONPOLICY 
    android.permission.ACCESS WIFISTATE 
    android.permission.ACCESS WIMAXSTATE 
    android.permission.BLUETOOTH 
    android.permission.BLUETOOTH_ADMIN 
    android.permission.BROADCAST_STICKY 
    android.permission.CHANGE NETWORKSTATE 
    android.permission.CHANGE WIFIMULTICAST_STATE 
    android.permission.CHANGE WIFISTATE 
    android.permission.CHANGE WIMAXSTATE 
    android.permission.DISABLE_KEYGUARD 
    android.permission.EXPAND STATUSBAR 
    android.permission.FLASHLIGHT 
    android.permission.GET_ACCOUNTS 
    android.permission.GET PACKAGESIZE 
    android.permission.INTERNET 
    android.permission.KILL BACKGROUNDPROCESSES 
    android.permission.MODIFY AUDIOSETTINGS 
    android.permission.NFC 
    android.permission.READ SYNCSETTINGS 
    android.permission.READ SYNCSTATS 
    android.permission.RECEIVE BOOTCOMPLETED 
    android.permission.REORDER_TASKS 
    android.permission.REQUEST INSTALLPACKAGES 
    android.permission.SET TIMEZONE 
    android.permission.SET_WALLPAPER 
    android.permission.SET WALLPAPERHINTS 
    android.permission.SUBSCRIBED FEEDSREAD 
    android.permission.TRANSMIT_IR 
    android.permission.USE_FINGERPRINT 
    android.permission.VIBRATE 
    android.permission.WAKE_LOCK 
    android.permission.WRITE SYNCSETTINGS 
    com.android.alarm.permission.SET_ALARM 
    com.android.launcher.permission.INSTALL_SHORTCUT 
    com.android.launcher.permission.UNINSTALL_SHORTCUT

    这类权限只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。

    危险权限

    Permission Group Permissions
    android.permission-group.CALENDAR
    • android.permission.READ_CALENDAR
    • android.permission.WRITE_CALENDAR
    android.permission-group.CAMERA
    • android.permission.CAMERA
    android.permission-group.CONTACTS
    • android.permission.READ_CONTACTS
    • android.permission.WRITE_CONTACTS
    • android.permission.GET_ACCOUNTS
    android.permission-group.LOCATION
    • android.permission.ACCESS_FINE_LOCATION
    • android.permission.ACCESS_COARSE_LOCATION
    android.permission-group.MICROPHONE
    • android.permission.RECORD_AUDIO
    android.permission-group.PHONE
    • android.permission.READ_PHONE_STATE
    • android.permission.CALL_PHONE
    • android.permission.READ_CALL_LOG
    • android.permission.WRITE_CALL_LOG
    • com.android.voicemail.permission.ADD_VOICEMAIL
    • android.permission.USE_SIP
    • android.permission.PROCESS_OUTGOING_CALLS
    android.permission-group.SENSORS
    • android.permission.BODY_SENSORS
    android.permission-group.SMS
    • android.permission.SEND_SMS
    • android.permission.RECEIVE_SMS
    • android.permission.READ_SMS
    • android.permission.RECEIVE_WAP_PUSH
    • android.permission.RECEIVE_MMS
    • android.permission.READ_CELL_BROADCASTS
    android.permission-group.STORAGE
    • android.permission.READ_EXTERNAL_STORAGE
    • android.permission.WRITE_EXTERNAL_STORAGE


      android开发者官网也有相关描述:
      http://developer.android.com/training/permissions/requesting.html
      http://developer.android.com/guide/topics/security/permissions.html

      所以仔细去看看自己的app,对照列表,如果有需要申请其中的一个权限,就需要进行特殊操作。还有一个比较人性的地方就是如果同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_EXTERNAL_STORAGE被授权了,app也有READ_EXTERNAL_STORAGE权限了。


    四、支持Marshmallow新版本权限机制

    关于权限控制主要使用到

    PermissionChecker类的checkSelfPermission();

    ActivityCompat类的

       public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
                @NonNull String permission) 

    Fragment类的

     public boolean shouldShowRequestPermissionRationale(@NonNull String permission) 

    ActivityCompat类的

        public static void requestPermissions(final @NonNull Activity activity,
                final @NonNull String[] permissions, final int requestCode) 

    Fragment类的

      public final void requestPermissions(@NonNull String[] permissions, int requestCode)


    终于要开始支持android 6.0版本了,最先一步当然就是修改build.gradle文件中的tragetSdkVersion和compileSdkVersion成23版本,同时使用compile ‘com.android.support:appcompat-v7:23.1.1’最新v7包。

    android {
        compileSdkVersion 23
        ...
     
        defaultConfig {
            ...
            targetSdkVersion 23
            ...
        }
    }
    ...
    dependencies {
    ...
    compile 'com.android.support:appcompat-v7:23.1.1'
      修改完后,感兴趣的朋友可以直接打包在手机上测试一下,看看是不是会出现类似于上面我说的那些崩溃日志。
      接着下一步当然就是要修改代码了,最原始代码,无任何处理:

    private void startGetImageThread(){
    ....
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        ContentResolver contentResolver = getContentResolver();
        //获取jpeg和png格式的文件,并且按照时间进行倒序
        Cursor cursor = contentResolver.query(uri, null, MediaStore.Images.Media.MIME_TYPE + "=\"image/jpeg\" or " +
        MediaStore.Images.Media.MIME_TYPE + "=\"image/png\"", null, MediaStore.Images.Media.DATE_MODIFIED+" desc");
        ....
    }
      这段代码需要访问外部存储(相册图片),属于危险级别的权限,直接使用会造成应用崩溃,所以在这段代码执行之前我们需要进行特殊处理:

    int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {

    Activity activty=this;

            ActivityCompat.requestPermissions(activty,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    CODE_FOR_WRITE_PERMISSION);
        return;
    }
      写完这段代码之后,就会出现如下系统dialog:
      
      紧接着就需要去处理DENY和ALLOW的回调了,重写 Activity activity的ActivityCompat.OnRequestPermissionsResultCallback函数:

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == CODE_FOR_WRITE_PERMISSION){
            if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
                //用户同意使用write
                startGetImageThread();
            }else{
                //用户不同意,自行处理即可
                finish();
            }
        }
    }
      好了,这样就算是简单初步适配完成了。


    五、处理不再提醒

      如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。

      

      如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调onRequestPermissionsResult函数,回调结果为最后一次用户的选择。所以为了应对这种情况,系统提供了一个shouldShowRequestPermissionRationale()函数,这个函数的作用是帮助开发者找到需要向用户额外解释权限的情况,这个函数:
    应用安装后第一次访问,直接返回false;第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回 true,这时候可以显示一些为什么需要这个权限的说明;第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回 false;设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermissionRationale()返回false;  注意:第二次请求权限时,才会有“不再提醒”的选项,如果用户一直拒绝,并没有选择“不再提醒”的选项,下次请求权限时,会继续有“不再提醒”的选项,并且shouldShowRequestPermissionRationale()也会一直返回true。
      所以利用这个函数我们可以进行相应的优化,针对shouldShowRequestPermissionRationale函数返回false的处理有两种方案。第一种方案:如果应用是第一次请求该权限,则直接调用requestPermissions函数去请求权限;如果不是则代表用户勾选了’不再提醒’,弹出dialog,告诉用户为什么你需要该权限,让用户自己手动开启该权限。链接:http://stackoverflow.com/questions/32347532/android-m-permissions-confused-on-the-usage-of-shouldshowrequestpermissionrati 。第二种方案:在onRequestPermissionsResult函数中进行检测,如果返回PERMISSION_DENIED,则去调用shouldShowRequestPermissionRationale函数,如果返回false代表用户已经禁止该权限(上面的3和4两种情况),弹出dialog告诉用户你需要该权限的理由,让用户手动打开。链接:http://stackoverflow.com/questions/30719047/android-m-check-runtime-permission-how-to-determine-if-the-user-checked-nev  处理方法已经有了,修改一下代码,我这里就以第二种方案来处理了:
     
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == CODE_FOR_WRITE_PERMISSION){
            if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
                //用户同意使用write
                startGetImageThread();
            }else{
                //用户不同意,向用户展示该权限作用
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    AlertDialog dialog = new AlertDialog.Builder(this)
                            .setMessage("该相册需要赋予访问存储的权限,不开启将无法正常工作!")
                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    finish();
                                }
                            })
                            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    finish();
                                }
                            }).create();
                    dialog.show();
                    return;
                }
                finish();
            }
        }
    }
      当勾选不再提醒,并且拒绝之后,弹出dialog,提醒用户该权限的重要性:
      
      


    六、使用兼容库


      以上的代码在6.0版本上使用没有问题,但是在之前就有问题了,最简单粗暴的解决方法可能就是利用Build.VERSION.SDK_INT >= 23这个判断语句来判断了,方便的是SDK 23的v4包加入了专门类进行相关的处理:

    ContextCompat.checkSelfPermission()被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。ActivityCompat.requestPermissions()这个方法在6.0之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者PERMISSION_DENIED。ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本调用,永远返回false。  用v4包的这三方法,完美兼容所有版本!下面是代码:
    //使用兼容库就无需判断系统版本
    int hasWriteContactsPermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (hasWriteContactsPermission == PackageManager.PERMISSION_GRANTED) {
        startGetImageThread();
    }
    //需要弹出dialog让用户手动赋予权限
    else{
        ActivityCompat.requestPermissions(PickOrTakeImageActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION);

    }

      onRequestPermissionsResult函数不变。后两个方法,我们也可以在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale()和activity效果一样。



    七、一次请求多个权限


      当然了有时候需要多个权限,可以用上面方法一次请求多个权限。当然最重要的是不要忘了为每个权限检查“不再提醒”的设置。
    List<string> permissionsNeeded = new ArrayList<string>();
    permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
    permissionsNeeded.add(Manifest.permission.READ_CONTACTS);
    permissionsNeeded.add(Manifest.permission.WRITE_CONTACTS);
    requestPermissions(permissionsNeeded.toArray(new String[permissionsList.size()]), CODE_FOR_MULTIPLE_PERMISSION);</string></string>
      最后在onRequestPermissionsResult函数中一个个处理返回结果即可。


    八、第三方库简化代码


      当然早就有第三方库来帮忙做这些事情了:
      Github上的开源项目 PermissionHelper和hotchemi’s PermissionsDispatcher


    九、APP处于运行状态下,被撤销权限


      如果APP正在运行中,用户进入设置-应用程序页面去手动撤销该APP权限,会出现什么情况呢?哈哈,系统又会接着弹出权限请求对话框,挺好挺好:
      
      这样就没有问题了吧O(∩_∩)O~
      上面的测试环境为genymotion6.0模拟器,有朋友跟我反映在6.0nexus 6p真机上会直接退出应用,所以这个应该还和测试环境有关。


    使用兼容库support-v4中的方法

    The v4 support library also contains the PermissionChecker class, which provides several static utility methods for apps that use IPC to provide services for other apps. For example,PermissionChecker.checkCallingPermission() checks whether an IPC made by a particular package has a specified permission.



     requestPermissions() 的一些说明:

    Note: When your app calls the framework's requestPermissions() method, 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.

    当调用 requestPermissions() 时,系统会显示一个获取权限的提示对话框,当前应用不能配置和修改这个对话框,
    如果需要提示用户一些这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理。



    To help find the situations where you need to provide extra explanation, the system provides theshouldShowRequestPermissionRationale() method. 

    This method returns true if the app has requested this permission previously and the user denied the request. 

    That indicates that you should probably explain to the user why you need the permission.


    If the user turned down the permission request in the past and chose the Don't ask again option in the permission request system dialog, this method returns false

    The method also returns false if the device policy prohibits the app from having that permission.


    1. 第一次请求权限时,用户拒绝了,下一次:shouldShowRequestPermissionRationale()  返回 true,应该显示一些为什么需要这个权限的说明

    2.第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项时:shouldShowRequestPermissionRationale()  返回 false

    3. 设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()  返回 false 


    注意:上面的:第二次请求权限时,才会有“不在提醒”的选项,如果用户一直拒绝,并没有选择“不在提醒”的选项,下次请求权限时,会继续有“不在提醒”的选项


    十、shouldShowRequestPermissionRationale() 的方法说明:


    Gets whether you should show UI with rationale for requesting a permission.

     You should do this only if you do not have the permission and the context in which the permission is requested does not clearly communicate to the user what would be the benefit from granting this permission.


    For example, if you write a camera app, requesting the camera permission would be expected by the user and no rationale for why it is requested is needed. If however, the app needs location for tagging photos then a non-tech savvy user may wonder how location is related to taking photos. In this case you may choose to show UI with rationale of requesting this permission.

    根据方法说明:
    显示权限说明:是根据你的应用中使用的权限分类来的:
    1.用户容易知道应用需要获取的权限:如一个拍照应用,需要摄像头的权限,是很正常,不用提示。
    2.一些用户感觉困惑的一些权限:如:分享图片,还需要获取位置的权限,这个需要提示用户:为什么需要这个权限。





      
    展开全文
  • Android申请权限

    万次阅读 2018-04-27 15:45:41
    Android申请权限 Android 6.0系统加入了危险权限管理,在使用一些涉及到用户隐私方面的操作时,需要获取用户的授权才能使用,如通讯录、打电话、短信、相机、定位、录音、存储等隐私权限。获取用户授权权限,我们...

    Android申请权限

    Android 6.0系统加入了危险权限管理,在使用一些涉及到用户隐私方面的操作时,需要获取用户的授权才能使用,如通讯录、打电话、短信、相机、定位、录音、存储等隐私权限。获取用户授权权限,我们提倡动态申请权限,用到的时候才去向用户申请,例如点击扫一扫,这时我们申请相机权限,用户也明白,自然就授权了。即使用户没有授权,也仅仅影响此模块不能使用正常功能,并不影响其他模块。千万不能,在应用打开时的启屏页,一股脑把应用所有地方用到的权限都申请,不给还不能用,简直是流氓行为!

    本文大纲:

    • Android权限
    • 申请权限流程
      • 检查是否已被授权危险权限
      • 提示或解释接下来的操作需要危险权限
      • 申请权限
    • 申请权限引起的生命周期变化
    • 申请权限封装
    • 总结

    一、Android权限

    Android中的权限分为系统权限和应用自定义权限,系统权限又分为正常权限和危险权限。使用系统权限需要在manifest文件中注册权限,若是危险权限,还需要在使用时动态申请。

    terminal中使用这个命令可以列出调试手机的所有权限,包含应用自定义的权限:

    adb shell pm list permissions

    正常权限(Normal 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_INSTALL_PACKAGES
    SET_ALARM
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    

    危险权限(Dangerous Permissions):

    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.READ_CALL_LOG
      permission:android.permission.READ_PHONE_STATE
      permission:android.permission.CALL_PHONE
      permission:android.permission.WRITE_CALL_LOG
      permission:android.permission.USE_SIP
      permission:android.permission.PROCESS_OUTGOING_CALLS
      permission:com.android.voicemail.permission.ADD_VOICEMAIL
    
    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: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
    

    二、申请权限流程

    动态申请权限的流程:

    这里写图片描述

    1、检查是否已被授权危险权限

    /**
     * 检查是否已被授权危险权限
     * @param permissions
     * @return
     */
    public boolean checkDangerousPermissions(Activity ac, String[] permissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
                    ActivityCompat.shouldShowRequestPermissionRationale(ac, permission)) {
                return false;
            }
        }
        return true;
    }
    

    Android 6.0以下,在AndroidManifest.xml文件中注册权限即可。
    Android 6.0及其以后,既要在AndroidManifest.xml文件中注册,又要动态申请危险权限。

    2、提示或解释接下来的操作需要危险权限

    在申请危险权限时,有些用户觉得涉及到了他的隐私内容,可能不会授权。在用户拒绝后,再提示授权危险权限的必要性,就为时已晚,用户需要去设置里打开(拒绝时设置不在提醒)。如若我们在申请权限前,就有一个温馨提示,告诉用户危险权限的必要性,用户理解后更容易授权。

    饿了么的权限申请过程:

    这里写图片描述

    3、申请权限

    在activity中申请权限,权限授权结果会回调在acitvity中:

    //acitivty中申请权限
    ActivityCompat.requestPermissions(activity, permissions, requestCode);
    
    //activity权限授权结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    

    下面我们看ActivityCompat.requestPermissions这个方法:

    public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
        if (Build.VERSION.SDK_INT >= 23) {
            if (activity instanceof RequestPermissionsRequestCodeValidator) {
                ((RequestPermissionsRequestCodeValidator) activity)
                        .validateRequestPermissionsRequestCode(requestCode);
            }
            activity.requestPermissions(permissions, requestCode);
        } else if (activity instanceof OnRequestPermissionsResultCallback) {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    final int[] grantResults = new int[permissions.length];
    
                    PackageManager packageManager = activity.getPackageManager();
                    String packageName = activity.getPackageName();
    
                    final int permissionCount = permissions.length;
                    for (int i = 0; i < permissionCount; i++) {
                        grantResults[i] = packageManager.checkPermission(
                                permissions[i], packageName);
                    }
    
                    ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                            requestCode, permissions, grantResults);
                }
            });
        }
    }
    
    • 系统版本大于等于Android 6.0,请求权限时,先校验requestCode是否有效,再去调用activity的requestPermissions申请权限。

    • 系统小于Android 6.0,请求权限时,在主线程检查AndroidManifest.xml文件中是否注册权限,并将结果回调。

    我们把activity.requestPermissions()放在这,等会再看他。查源码我们知道FragmentActivity实现了这两个接口:

    ActivityCompat.OnRequestPermissionsResultCallback
    ActivityCompat.RequestPermissionsRequestCodeValidator

    public class FragmentActivity extends BaseFragmentActivityApi16 implements
            ActivityCompat.OnRequestPermissionsResultCallback,
            ActivityCompat.RequestPermissionsRequestCodeValidator {
    
       .......省略
    
    
        @Override
        public final void validateRequestPermissionsRequestCode(int requestCode) {
            // We use 16 bits of the request code to encode the fragment id when
            // requesting permissions from a fragment. Hence, requestPermissions()
            // should validate the code against that but we cannot override it as
            // we can not then call super and also the ActivityCompat would call
            // back to this override. To handle this we use dependency inversion
            // where we are the validator of request codes when requesting
            // permissions in ActivityCompat.
            if (!mRequestedPermissionsFromFragment
                    && requestCode != -1) {
                checkForValidRequestCode(requestCode);
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                @NonNull int[] grantResults) {
            int index = (requestCode >> 16) & 0xffff;
            if (index != 0) {
                index--;
    
                String who = mPendingFragmentActivityResults.get(index);
                mPendingFragmentActivityResults.remove(index);
                if (who == null) {
                    Log.w(TAG, "Activity result delivered for unknown Fragment.");
                    return;
                }
                Fragment frag = mFragments.findFragmentByWho(who);
                if (frag == null) {
                    Log.w(TAG, "Activity result no fragment exists for who: " + who);
                } else {
                    frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
                }
            }
        }  
        .......省略
    }
    

    BaseFragmentActivityApi14.class中:

    /**
     * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
     * an {@link IllegalArgumentException} if the code is not valid.
     */
    static void checkForValidRequestCode(int requestCode) {
        if ((requestCode & 0xffff0000) != 0) {
            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
        }
    }
    

    requestCode是int数据类型,4个字节,按2进制是32位数据,从上面可以知道它的取值是有范围的,只用到了低16位,0x00000000 ~ 0x0000ffff0 ~ 65535,那系统保留高16位有什么用呢?我们看到上面FragmentActivityonRequestPermissionsResult中,先查找是否是fragment请求的权限,然后直接&0xffff,也就是取requestCode得低16位,并回调权限结果在fragment中。

    接下来我们看在fragment中请求权限:

    //fragment中申请权限
    framgent.requestPermissions(permissions, requestCode);
    
    //fragment权限授权结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    

    framgentrequestPermissions方法:

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (mHost == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
    }
    

    我们mHost即是fragment的宿主activity,在 FragmentActivityonRequestPermissionsFromFragment

        @Override
        public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
                @NonNull String[] permissions, int requestCode) {
            FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
                    requestCode);
        }
    
    
        /**
         * Called by Fragment.requestPermissions() to implement its behavior.
         */
        void requestPermissionsFromFragment(Fragment fragment, String[] permissions,
                int requestCode) {
            if (requestCode == -1) {
                ActivityCompat.requestPermissions(this, permissions, requestCode);
                return;
            }
            checkForValidRequestCode(requestCode);
            try {
                mRequestedPermissionsFromFragment = true;
                int requestIndex = allocateRequestIndex(fragment);
                ActivityCompat.requestPermissions(this, permissions,
                        ((requestIndex + 1) << 16) + (requestCode & 0xffff));
            } finally {
                mRequestedPermissionsFromFragment = false;
            }
        }
    
    • requestCode = -1时,直接调用ActivityCompat.requestPermissions
    • requestCode != -1时,先校验requestCode是否有效,即 0 ~ 65535。然后获取一个分配请求索引(范围0 ~ 0xffff - 10 ~ 65534),索引加1之后左移16位,在和requestCode的低16组成新的requestCode
      0xaaaabbbb:高16位aaaa为索引加1之后左移16位,低16位bbbbfragmentrequestCode, 这就和之前在fragment中回调权限结果时requestCode一致了。

    最后都会调用ActivityCompat.requestPermissions,就回到了前面,既而activity.requestPermissions(),再来看AcitvityrequestPermissions

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (requestCode < 0) {
            throw new IllegalArgumentException("requestCode should be >= 0");
        }
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can reqeust only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }
    
    • 通过startActivityForResult打开系统授权权限界面,注意字符串常量REQUEST_PERMISSIONS_WHO_PREFIX
    • 不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果grantResults会错误影响授权。

    然而在 ActivityonRequestPermissionsFromFragment中,这与FragmentActivity中的不一样

        @Override
        public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions,
                int requestCode) {
            String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho;
            Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
            startActivityForResult(who, intent, requestCode, null);
        }
    

    最终会通过startActivityForResult打开系统权限授权界面。注意这个who,系统权限授权界面回调结果时,就是根据这个who把结果分配给指定的fragment,即结果回调到fragment的onRequestPermissionsResult。 fragment请求权限时,requestCode取值范围没有限制.

    既然是通过startActivityForResult打开系统授权权限界面,那我们能在activity的onActivityResult(int requestCode, int resultCode, Intent data)中拿到授权结果吗?答案是不能,让我们一起来看为什么。当结果回传来时,都会调用ActivitydispatchActivityResult分配结果。

    void dispatchActivityResult(String who, int requestCode,
        int resultCode, Intent data) {
        if (false) Log.v(
            TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
            + ", resCode=" + resultCode + ", data=" + data);
        mFragments.noteStateNotSaved();
        if (who == null) {
            onActivityResult(requestCode, resultCode, data);
        } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
            who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
            if (TextUtils.isEmpty(who)) {
                dispatchRequestPermissionsResult(requestCode, data);
            } else {
                Fragment frag = mFragments.findFragmentByWho(who);
                if (frag != null) {
                    dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
                }
            }
        } else if (who.startsWith("@android:view:")) {
            ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
                    getActivityToken());
            for (ViewRootImpl viewRoot : views) {
                if (viewRoot.getView() != null
                        && viewRoot.getView().dispatchActivityResult(
                                who, requestCode, resultCode, data)) {
                    return;
                }
            }
        } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
            Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
            getAutofillManager().onAuthenticationResult(requestCode, resultData);
        } else {
            Fragment frag = mFragments.findFragmentByWho(who);
            if (frag != null) {
                frag.onActivityResult(requestCode, resultCode, data);
            }
        }
    }
    

    这时字符串常量REQUEST_PERMISSIONS_WHO_PREFIX就派上用场了。如果who不为空,说明activity是Activity,不是FragmentActivity,直接结果回调在fragment中。如果为空,actvity不一定为FragmentActivity,结果直接进入Activity的
    dispatchRequestPermissionsResult(requestCode, data);

    private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
        mHasCurrentPermissionsRequest = false;
        // If the package installer crashed we may have not data - best effort.
        String[] permissions = (data != null) ? data.getStringArrayExtra(
                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
                PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
        onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    

    结果进而进入回调activity的onRequestPermissionsResult,若activity未处理,且activit是FragmentActivity,则通过requestCode右移16位,进而传递结果到fragment的onRequestPermissionsResult(),完成整个权限请求流程。

    三、申请权限引起的生命周期变化

    由上面知道最终通过startActivityForResult来打开系统权限授权界面,我们在BaseActivity中相关方法中打印日志:

    @Override
    public void onResume() {
        super.onResume();
        Log.i(TAG, "onResume: ");
    }
    
    @Override
    public void onPause() {
        super.onPause();
        Log.i(TAG, "onPause: ");
    }
    
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState: ");
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.i(TAG, "onRequestPermissionsResult: ");
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    

    然后请求权限,我们观察到:

    这里写图片描述

    权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。

    注意此时fragment事务提交时会状态丢失,如果执行ft.commit(),会报错。

    如此时不能DialogFragment.show(),因为DialogFragment.show()是ft.commit(),这个不允许状态丢失。

    public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
    

    四、申请权限封装

    a、BaseActivity:

    public class BaseActivity extends AppCompatActivity {
        /**
         * 请求权限
         */
        public void requestDangerousPermissions(String[] permissions, int requestCode) {
            if (checkDangerousPermissions(permissions)){
                handlePermissionResult(requestCode, true);
                return;
            }
            ActivityCompat.requestPermissions(this, permissions, requestCode);
        }
    
        /**
         * 检查是否已被授权危险权限
         * @param permissions
         * @return
         */
        public boolean checkDangerousPermissions(String[] permissions) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                return true;
            }
            for (String permission : permissions) {
                if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
                        ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                    return false;
                }
            }
            return true;
        }
    
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            boolean granted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    granted = false;
                }
            }
            boolean finish = handlePermissionResult(requestCode, granted);
            if (!finish){
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    
        /**
         * 处理请求危险权限的结果
         * @return
         */
        public boolean handlePermissionResult(int requestCode, boolean granted) {
            return false;
        }
    }
    

    b、BaseFragment:

    public class BaseFragment extends Fragment {
        /**
         * 请求权限
         *
         * @param permissions
         * @param requestCode
         */
        public void requestDangerousPermissions(String[] permissions, int requestCode) {
            if (checkDangerousPermissions(permissions)){
                handlePermissionResult(requestCode, true);
                return;
            }
            requestPermissions(permissions, requestCode);
        }
    
        /**
         * 检查是否已被授权危险权限
         * @param permissions
         * @return
         */
        public boolean checkDangerousPermissions(String[] permissions) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                return true;
            }
            if (getActivity() == null){
                return false;
            }
            for (String permission : permissions) {
                if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED ||
                        ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
                    return false;
                }
            }
            return true;
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            boolean isGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    isGranted = false;
                }
            }
    
            if (!handlePermissionResult(requestCode, isGranted)){
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    
        /**
         * 处理请求危险权限的结果
         *
         * @param requestCode
         * @param isGranted 是否允许
         * @return
         */
        public boolean handlePermissionResult(int requestCode, boolean isGranted) {
            return false;
        }
    }
    

    c、使用,如打电话:

    private void attemptToCall(String phone) {
        if (!isIntentExisting(getContext(), Intent.ACTION_DIAL)) {
            Toast.makeText(getContext(), "该设备不能打电话", Toast.LENGTH_SHORT).show();
            return;
        }
        mPhone = phone;
        requestDangerousPermissions(callPermission, 88);
    }
    
    @Override
    public boolean handlePermissionResult(int requestCode, boolean isGranted) {
        if (requestCode == 88){
            final String phone = mPhone;
            getView().post(new Runnable() {
                @Override
                public void run() {
                    call(phone);
                }
            });
            mPhone = null;
            return true;
        }
    
        return super.handlePermissionResult(requestCode, isGranted);
    }
    
    private void call(String phone) {
        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phone));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
    
    //判断电话应用是否存在
    public boolean isIntentExisting(Context context, String action) {
        final PackageManager packageManager = context.getPackageManager();
        final Intent intent = new Intent(action);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        if (resolveInfo.size() > 0) {
            return true;
        }
        return false;
    }
    

    五、总结:

    a、当系统小于Android 6.0,只需要在AndroidManifest.xml注册权限即可使用(不包括某些手机厂商自定义的权限管理)。当系统大于等于Android 6.0时,既要在AndroidManifest.xml注册权限,又要去动态申请权限。

    b、申请权限时,最终通过startActivityForResult来打开系统权限授权界面,权限结果回调在onRequestPermissionsResult中,不会在onActivityResult中出现。

    c、requestCode取值范围:

    当activity为FragmentActivity时:

    • 在activity中请求权限时,requestCode有取值范围,为0 ~ 65535,权限回调结果在activity中。
    • 在fragment中请求权限时,requestCode有取值范围,为0 ~ 65535,权限回调结果先传递到activity中,若activity未拦截,再传递到fragment中。在activity中拦截时,可requestCode & 0xffff得到正确的requestCode。

    当activity为Activity时:

    • 在activity中请求权限时,requestCode取值范围无要求,权限结果回调在activity中。
    • 在fragment中请求权限时,requestCode取值范围无要求,权限结果直接回调在fragment中。在activity中不可拦截。

    d、不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果grantResults会错误影响授权

    e、权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。

    备注:

    本文基于Android API 26

    本文测试手机华为P20 Pro

    Git示例代码:https://github.com/jinxiyang/RequestPermissions.git

    展开全文
  • Android 权限的一些细节

    万次阅读 多人点赞 2016-12-01 00:39:52
    Android 权限的一些细节0x01 哪些app属于system app?为了区分privilege app和system app,这里先说明system app是什么,避免之后的讨论概念混乱。

    Android 权限的一些细节

    1 哪些app属于system app?

    为了区分privilege app和system app,这里先说明system app是什么,避免之后的讨论概念混乱。

    在PackageManagerService中对是否是system app的判断:
    具有ApplicationInfo.FLAG_SYSTEM标记的,被视为System app

        private static boolean isSystemApp(PackageParser.Package pkg) {
            return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
        }
    
        private static boolean isSystemApp(PackageSetting ps) {
            return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
        }

    有两类app属于System app:

    1.1 第一类System app: 特定的shared uid的app 属于system app

    例如:shared uid为android.uid.systemandroid.uid.phoneandroid.uid.logandroid.uid.nfcandroid.uid.bluetoothandroid.uid.shell。这类app都被赋予了ApplicationInfo.FLAG_SYSTEM标志。

    在PackageManagerService的构造方法中,代码如下:

        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

    1.2 第二类System app: 特定目录中的app属于system app

    特定目录包括:/vendor/overlay/system/framework/system/priv-app/system/app/vendor/app/oem/app。这些目录中的app,被视为system app。

    在PackageManagerService的构造方法中,代码如下:

        // /vendor/overlay folder
        File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
        scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
    
        // Find base frameworks (resource packages without code). /system/framework folder
        File frameworkDir = new File(Environment.getRootDirectory(), "framework");
        scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED,
                scanFlags | SCAN_NO_DEX, 0);
    
        // Collected privileged system packages. /system/priv-app folder
        final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
        scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
    
        // Collect ordinary system packages. /system/app folder
        final File systemAppDir = new File(Environment.getRootDirectory(), "app");
        scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    
        // Collect all vendor packages.
        File vendorAppDir = new File("/vendor/app");
        scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    
        // Collect all OEM packages. /oem/app folder
        final File oemAppDir = new File(Environment.getOemDirectory(), "app");
        scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

    scanDirLI参数中的PackageParser.PARSE_IS_SYSTEM最终会被转换为Package的ApplicationInfo.FLAG_SYSTEM属性。这个过程相关的代码流程(简略):

    scanDirLI(PackageParser.PARSE_IS_SYSTEM)
    
    -> scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null);
    
    -> scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user);
    
    -> final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user);
    
    -> scanPackageDirtyLI()中
        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
           pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
        }

    2 什么是privileged app(特权app)?

    注:privileged app,在本文中称之为 特权app,主要原因是此类特权app可以使用protectionLevel为signatureOrSystem或者protectionLevel为signature|privileged的权限。

    从PackageManagerService的isPrivilegedApp()可以看出特权app是具有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标志的一类app

        private static boolean isPrivilegedApp(PackageParser.Package pkg) {
            return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
        }

    特权app首先必须是System app。也就是说 System app分为普通的system app特权的system app

    System app = 普通的system app + 特权app

    直观的(但不准确严谨)说,普通的system app就是/system/app目录中的app,特权的system app就是/system/priv-app目录中的app。
    BTW: priv-appprivileged app的简写。

    区分普通system app和特权app的目的是澄清这个概念:"signatureOrSystem"权限中的System不是为普通system app提供的,而是只有特权app能够使用。

    进一步说,android:protectionLevel="signatureOrSystem"中的Systemandroid:protectionLevel="signature|privileged"中的privileged的含义是一样的。
    可以从PermissionInfo.java中的fixProtectionLevel()看出来:

        // PermissionInfo.java
        public static int fixProtectionLevel(int level) {
            if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
                // "signatureOrSystem"权限转换成了"signature|privileged"权限
                level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED;
            }
            return level;
        }

    所以,当我们说起system app时,通常指的是前面提到的特定uid和特定目录中的app,包含了普通的system app和特权app。
    当我们说起有访问System权限或者privileged权限的app时,通常指特权app。

    3 哪些app属于privileged app(特权app)?

    特权app首先是System app,然后要具有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标志。
    有两类app属于privileged app(特权app):

    参考PackageManagerService的构造方法。

    3.1 第一类privileged app: 特定uid的app

    shared uid为"android.uid.system""android.uid.phone""android.uid.log""android.uid.nfc""android.uid.bluetooth""android.uid.shell"的app被赋予了privileged的权限。这些app同时也是system app。

    代码如下:

        // PackageManagerService的构造方法
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

    3.2 第二类privileged app:/system/framework/system/priv-app目录下的app

    /system/framework/system/priv-app目录下的app被赋予了privileged的权限。
    其中/system/framework目录中的apk,只是包含资源,不包含代码(dex)。

    代码如下:

        // PackageManagerService的构造方法
        // /system/framework
        File frameworkDir = new File(Environment.getRootDirectory(), "framework");
        scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED,
                scanFlags | SCAN_NO_DEX, 0);
    
        // /system/priv-app
        final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
        scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

    PackageParser.PARSE_IS_PRIVILEGED标志最终会转换为Package的ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标志。大概的代码流程,如下:

    scanDirLI(PackageParser.PARSE_IS_PRIVILEGED)
    
    -> scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null);
    
    -> PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user);
    
    -> final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user);
    
    -> scanPackageDirtyLI()中
        if ((parseFlags & PackageParser.PARSE_IS_PRIVILEGED) != 0) {
            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }

    4 Android app中的权限是必须先声明后使用吗?

    在本文中,声明权限是指在AndroidManifest.xml中使用了<permission>使用权限是指在AndroidManifest.xml中使用了<uses-permission>获得权限(或赋予权限)是指真正的可以通过系统的权限检查,调用到权限保护的方法。

    场景:App A中声明了权限PermissionA,App B中使用了权限PermissionA。
    那么App A必须比App B先安装,App B才能获取到权限吗?

    答案是不一定,要看具体情况而定。这个具体情况就是权限的保护级别

    • 情况一:PermissionA的保护级别是normal或者dangerous
      App B先安装,App A后安装,此时App B没有获取到PermissionA的权限。
      即,此种情况下,权限必须先声明再使用。即使App A和App B是相同的签名

    • 情况二:PermissionA的保护级别是signature或者signatureOrSystem
      App B先安装,App A后安装,如果App A和App B是相同的签名,那么App B可以获取到PermissionA的权限。如果App A和App B的签名不同,则App B获取不到PermissionA权限。
      即,对于相同签名的app来说,不论安装先后,只要是声明了权限,请求该权限的app就会获得该权限。
      这也说明了对于具有相同签名的系统app来说,安装过程不会考虑权限依赖的情况。安装系统app时,按照某个顺序(例如名字排序,目录位置排序等)安装即可,等所有app安装完了,所有使用权限的app都会获得权限。

    在这里提供一种方法,可以方便的验证上面的说法:

    4.1 验证某个app是否获得了某个权限的方法

    可以用下面2个命令来验证:

    adb shell dumpsys package permission <权限名>
    
    adb shell dumpsys package <包名>

    其中,adb shell dumpsys package permission <权限名>可以查看某个权限是谁声明的,谁使用了
    adb shell dumpsys package <包名>可以看到某个app是否获得了某个权限

    例如,App A中声明了权限com.package.a.PermissionA,App B中使用了权限com.package.a.PermissionA。
    App A的包名为com.package.a,App B的包名为com.package.b。

    4.1.1 命令一: adb shell dumpsys package permission <权限名>

    查看某个权限是谁声明的,谁使用了。

    通过下面的命令,查看com.package.a.PermissionA权限:

    adb shell dumpsys package permission com.package.a.PermissionA

    输出结果为:(注:这里只显示关键信息,省略了其他很多信息)

    Permission [com.package.a.PermissionA] (a2930ef):
        sourcePackage=com.package.a            // 权限的声明者
        uid=10226 gids=null type=0 prot=normal //保护级别为normal
    
    Packages:
      Package [com.package.b] (350d95):      // 权限的使用者
        requested permissions:
          com.package.a.PermissionA
        install permissions:
          com.package.a.PermissionA, granted=true, flags=0x0

    其中granted=true表明App B 获取到了com.package.a.PermissionA权限。
    如果App B没有获取到com.package.a.PermissionA权限,则输出结果中只有权限的声明信息,如下:

    Permission [com.package.a.PermissionA] (a2930ef):
        sourcePackage=com.package.a
        uid=10226 gids=null type=0 prot=normal 保护级别为normal

    4.1.2 命令二: adb shell dumpsys package <包名>

    查看某个app是否获得了某个权限。

    查看App B(包名为com.package.b)是否获得了权限com.package.a.PermissionA

    adb shell dumpsys package com.package.b

    输出结果为:(注:省略了很多其他信息)

    Packages:
      Package [com.package.b] (6611d16):
        requested permissions:         // 申请了哪些权限
          com.package.a.PermissionA
        install permissions:           // 获得了哪些权限
          com.package.a.PermissionA, granted=true, flags=0x0

    上面的输出结果表明,App B获取到了com.package.a.PermissionA权限。
    如果App B没有获取到 com.package.a.PermissionA 权限,那么在 install permissions不会出现com.package.a.PermissionA, granted=true, flags=0x0

    5 关于signature权限和signatureOrSystem权限的获取(或拒绝)

    这里要说的是这种场景:App A中声明了权限com.package.a.PermissionA,App B中使用了权限com.package.a.PermissionA。其中com.package.a.PermissionA的保护级别为signature或者signatureOrSystem。App A先安装,App B后安装。App B和App A的签名可能一样,也可能不一样。App B的签名也可能是系统签名,这是对于厂商的app来说的。

    安装App B时,PackageManagerService会对App B能否获得com.package.a.PermissionA权限做检查。
    大概的代码流程如下:
    安装apk简要流程

    安装App B的时候:

    • installPackageLI()
        private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
            final File tmpPackageFile = new File(args.getCodePath());// 被安装的apk的路径
            final PackageParser.Package pkg;
            try {
                pkg = pp.parsePackage(tmpPackageFile, parseFlags);
            }
            ...
            if (replace) {
                ...
            } else {
                // run here
                installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                        args.user, installerPackageName, volumeUuid, res);
            }
        }
    • installNewPackageLI()
        private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, PackageInstalledInfo res) {
                PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user);
    
                updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);// run here
        }
    • updateSettingsLI()
        private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
                String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res,
                UserHandle user) {
    
                updatePermissionsLPw(newPackage.packageName, newPackage,
                        UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
                                ? UPDATE_PERMISSIONS_ALL : 0));
    • updatePermissionsLPw()
        private void updatePermissionsLPw(String changingPkg,
                PackageParser.Package pkgInfo, int flags) {
            if (pkgInfo != null) {
                grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0, changingPkg);
            }
        }
    • grantPermissionsLPw()
        private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
                String packageOfInterest) {
    
            // 遍历安装包pkg中所有请求的权限
            final int N = pkg.requestedPermissions.size();
            for (int i=0; i<N; i++) {
                final String name = pkg.requestedPermissions.get(i); // 权限的名字
                final BasePermission bp = mSettings.mPermissions.get(name);// 权限的信息
    
                final String perm = bp.name;
                boolean allowedSig = false;
                int grant = GRANT_DENIED;
    
                final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; // 权限的保护级别, PROTECTION_MASK_BASE 为0xf,signature级别时,bp.protectionLevel = 2;signatureOrSystem级别时,bp.protectionLevel = 0x12,所以对这两种级别的权限,level都是2,即,PermissionInfo.PROTECTION_SIGNATURE
                switch (level) {
                    case PermissionInfo.PROTECTION_NORMAL: { // value is 0
                        // For all apps normal permissions are install time ones.
                        grant = GRANT_INSTALL;
                    } break;
    
                    case PermissionInfo.PROTECTION_DANGEROUS: {// value is 1
                        // 略
                    } break;
    
                    // 被安装app使用了其他app声明的signature或者signatureOrSystem权限,上面提到的场景会执行到这里
                    case PermissionInfo.PROTECTION_SIGNATURE: {// value is 2
                        // For all apps signature permissions are install time ones.
                        allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
                        if (allowedSig) {
                            grant = GRANT_INSTALL;
                        }
                    } break;
                }
    • grantSignaturePermission()

    注:pre23的权限,请参考链接:关于pre23权限

        private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
                BasePermission bp, PermissionsState origPermissions) {
            boolean allowed;
            // 这里检查被安装的app的签名(pkg.mSignatures)与声明权限的app的签名(bp.packageSetting.signatures.mSignatures)是否一致,
            // 如果不一致,则再检查被安装app的签名是否与系统签名(mPlatformPackage.mSignatures)一致。
            // 如果其中一个是一致的,则赋予被安装app signature权限。
            // 注意:只要被安装的app的签名是系统签名,则其可以访问任意第三方声明的signature权限。
            allowed = (compareSignatures(
                    bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
                            == PackageManager.SIGNATURE_MATCH)
                    || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
                            == PackageManager.SIGNATURE_MATCH);
    
            // 如果被安装app的签名既不是声明权限的app的签名,也不是系统签名,则继续检查其他标志位。
            // 首先检查声明的权限是否是privileged权限(也就是signatureOrSystem中的System权限),如果权限是privileged的,那么对于系统应用(满足isSystemApp(pkg))并且是privileged应用(满足isPrivilegedApp(pkg)),就会赋予
            if (!allowed && (bp.protectionLevel
                    & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
                if (isSystemApp(pkg)) {// 如果被安装的app是system应用(见前面对system app的说明)
                    if (pkg.isUpdatedSystemApp()) { // 如果是更新系统app
                        // 略
                    } else {// 不是更新系统app,如果被安装的app是privileged的,则赋予其权限。也就是说,对于/system/priv-app目录中的app,会获取到权限。这种情况发生在privileged app第一次被安装时,[或者系统被root后,强行push apk到system/priv-app目录?(有待验证)]。
                        allowed = isPrivilegedApp(pkg);
                    }
                }
            }
    
            if (!allowed) {
                if (!allowed && (bp.protectionLevel
                        & PermissionInfo.PROTECTION_FLAG_PRE23) != 0
                        && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {// 如果之前没有获取到权限,再判断是否是pre23的权限。如果是pre23的权限,且被安装的app的targetVersion是22及以下,则赋予其权限。
                    allowed = true;
                }
                if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0
                        && pkg.packageName.equals(mRequiredInstallerPackage)) {// 只有指定的system installer才能使用该权限。
                    allowed = true;
                }
                if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0
                        && pkg.packageName.equals(mRequiredVerifierPackage)) {// 只有指定的system verifier才能使用该权限。
                    allowed = true;
                }
                if (!allowed && (bp.protectionLevel
                        & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0
                        && isSystemApp(pkg)) { // 对于preinstalled级别的权限,只有系统app可以使用该权限。
                    // Any pre-installed system app is allowed to get this permission.
                    allowed = true;
                }
                if (!allowed && (bp.protectionLevel
                        & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
                    // For development permissions, a development permission
                    // is granted only if it was already granted.
                    allowed = origPermissions.hasInstallPermission(perm);
                }
            }
            return allowed;
        }

    6 哪些app可以使用某app提供的signature权限或signatureOrSystem权限?

    根据上面的代码,我们可以得出以下结论

    • 如果App A声明了signatureOrSystem权限,即android:protectionLevel="signature|privileged"或者android:protectionLevel="signatureOrSystem",则可以使用该权限的app包括:

      • (1)与App A有相同签名的app
      • (2)与系统签名相同的app,即与厂商签名(厂商ROM中的系统app签名)相同的app
      • (3)在/system/priv-app目录中的app,不管是否满足(1)和(2)的条件。即任意app只要放到了/system/priv-app就可以使用App A的signatureOrSystem级别的权限。
    • 如果App A声明了signature权限,即android:protectionLevel="signature",则可以使用该权限的app包括:

      • (1)与App A有相同签名的app
      • (2)与系统签名相同的app,即与厂商签名(厂商ROM中的系统app签名)相同的app

    注意:与系统签名相同,即与厂商签名相同,这是指厂商推出的app,这些app有很大的访问权限。

    7 关于pre23的权限

    在framework/base/core/res/AndroidManifest.xml中,有2个权限从normal级别提升到signature级别,它们是

    android.permission.WRITE_SETTINGS
    android.permission.SYSTEM_ALERT_WINDOW

    可以预见,当市场上绝大多数app的targetSdkVersion为23或23以上(即Android 6.0或以上)时,pre23字样将会被去掉,到那时这两个权限将会得到更好地保护。

    pre23的含义是,如果某个app的targetSdkVersion是22或者22以下(即Android 5.x及以下),那么该app就可以获取到这2个权限。

        <permission android:name="android.permission.WRITE_SETTINGS"
            android:label="@string/permlab_writeSettings"
            android:description="@string/permdesc_writeSettings"
            android:protectionLevel="signature|preinstalled|appop|pre23" />
    
        <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
            android:label="@string/permlab_systemAlertWindow"
            android:description="@string/permdesc_systemAlertWindow"
            android:protectionLevel="signature|preinstalled|appop|pre23|development" />

    8 关于install权限和runtime权限

    • install权限:安装时权限,是指在安装app的时候,赋予app的权限。normalsignature级别的权限都是安装时权限不会给用户提示界面,系统自动决定权限的赋予或拒绝。

    • runtime权限:运行时权限,是指在app运行过程中,赋予app的权限。这个过程中,会显示明显的权限授予界面,让用户决定是否授予权限。如果app的targetSdkVersion是22(Lollipop MR1)及以下,dangerous权限是安装时权限,否则dangerous权限是运行时权限

    参考:grantPermissionsLPw()中的注释。

    9 官方文档关于权限保护级别的说明

    权限保护级别是指由android:protectionLevel指定的值。

    9.1 <permission>的权限级别 android:protectionLevel

    参考: https://developer.android.com/guide/topics/manifest/permission-element.html

    Value Meaning
    “normal” normal级别是默认值。低风险的权限采用此级别。在app安装的时候,系统自动赋予此app请求的所有normal权限,而不会征求用户的同意(但是,在安装app之前,用户总是有权选择检查这些权限)。
    “dangerous” 较高风险的权限,此级别的权限意味着,请求权限的app将要访问用户的隐私数据或者控制设备,这可能给用户带来负面影响。
    因为dangerous权限会引入潜在的风险,所以系统不会自动赋予此类权限给app。例如,在安装app的时候,会将dangerous权限展示给用户,并请求用户确认。
    “signature” 只有请求权限的app与声明权限的app的签名是一样的时候,系统才会赋予signature权限。
    如果签名一致,系统会自动赋予权限,而不会通知用户或者征求用户的同意。
    “signatureOrSystem” 系统赋予此类权限有2种情况:(1)请求权限的app与声明权限的app的签名一致;(2)请求权限的app在Android 系统镜像(system image)中。
    signatureOrSystem权限主要用在这个场景:多个软件供应商的apps预装到了系统目录(system/priv-app)中,而且这些apps之间会共享一些功能。除此之外,尽量不要使用此类权限级别。


    9.2 PermissionInfo中关于 protectionLevel

    参考: https://developer.android.com/reference/android/content/pm/PermissionInfo.html

    PermissionInfo.java中的权限保护级别主要有PROTECTION_NORMAL, PROTECTION_DANGEROUS, 或者 PROTECTION_SIGNATURE。

        public static final int PROTECTION_NORMAL = 0;
        public static final int PROTECTION_DANGEROUS = 1;
        public static final int PROTECTION_SIGNATURE = 2;

    除此之外,signature级别的权限还有一些附加的标志,例如,PROTECTION_FLAG_PRIVILEGED,PROTECTION_FLAG_APPOP或者PROTECTION_FLAG_PRE23 等等。

        public static final int PROTECTION_FLAG_PRIVILEGED = 0x10;
        public static final int PROTECTION_FLAG_DEVELOPMENT = 0x20;
        public static final int PROTECTION_FLAG_APPOP = 0x40;

    注意:PROTECTION_SIGNATURE_OR_SYSTEM 和PROTECTION_FLAG_SYSTEM已经被弃用。

        public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
        public static final int PROTECTION_FLAG_SYSTEM = 0x10;

    9.3 R.attr.html中 protectionLevel

    参考: https://developer.android.com/reference/android/R.attr.html#protectionLevel

    这里涵盖了protectionLevel所有可能的取值。

    protectionLevel描绘了权限所蕴含的潜在风险,并且指明了系统在赋予某个app请求的权限时所要遵守的规程(procedure)。Android标准的权限有着事先定义好的、固定不变的protectionLevel(事实上,protectionLevel也会变,见pre23相关的说明)。

    自定义的权限的protectionLevel属性可以取下面列表中的‘Constant’值,如果有多个’Constant’值,则需要用‘|’分隔开。如果没有定义protectionLevel,则默认权限为normal

    权限级别可以分为两类:基础权限级别附加权限级别

    • 基础权限级别
      有3种:normaldangeroussignature。(注:signatureOrSystem处于弃用状态,不建议使用)

    • 附加权限级别
      0x10及其之后的权限级别都属此类,例如,privilegedappop等。它们必须附加在基础权限上。目前貌似只能附加在signature权限上。

    Constant Value Description
    normal 0 normal级别是默认值。低风险的权限采用此级别。在app安装的时候,系统自动赋予此app请求的所有normal权限,而不会征求用户的同意(但是,在安装app之前,用户总是有权选择检查这些权限)。
    dangerous 1 较高风险的权限,此级别的权限意味着,请求权限的app将要访问用户的隐私数据或者控制设备,这可能给用户带来负面影响。
    因为dangerous权限会引入潜在的风险,所以系统不会自动赋予此类权限给app。例如,在安装app的时候,会将dangerous权限展示给用户,并请求用户确认。
    signature 2 只有请求权限的app与声明权限的app的签名是一样的时候,系统才会赋予signature权限。
    如果签名一致,系统会自动赋予权限,而不会通知用户或者征求用户的同意。
    signatureOrSystem 3 系统赋予此类权限有2种情况:(1)请求权限的app与声明权限的app的签名一致;(2)请求权限的app在Android 系统镜像(system image)中。
    signatureOrSystem权限主要用在这个场景:多个软件供应商的apps预装到了系统目录(system/priv-app)中,而且这些apps之间会共享一些功能。除此之外,尽量不要使用此类权限级别。
    privileged 0x10 只能与signature同时使用。signature|privilegedsignatureOrSystem意义相同。Android中system/priv-app目录和system/framework目录中的app可以访问privileged权限。
    system 0x10 意义与privileged相同。注意:由于system的概念会造成混乱,不建议使用。system权限并不是system/app目录中的app能访问的权限。
    development 0x20 development applications可以访问此权限。 Android标准权限中,development是与signatureprivileged一起用的。例如android:protectionLevel="signature|privileged|development"
    appop 0x40 由AppOpsManager来检查app是否访问此类权限。
    pre23 0x80 此类权限自动被赋予那些targetSdkVersion在22(Android 5.x)或22以下的app。
    installer 0x100 此类权限自动被赋予负责安装apk的系统app。
    verifier 0x200 此类权限自动被赋予负责验证apk的系统app。
    preinstalled 0x400 此类权限可以自动被赋予任何预安装在system image中的app,不只是privileged app。
    setup 0x800 此类权限自动被赋予‘安装向导’app。


    10 权限在四大组件中的使用以及URI权限

    参考: https://developer.android.com/guide/topics/security/permissions.html

    10.1 在AndroidManifest.xml使用权限保护组件

    通过在AndroidManifest.xml中采用高保护级别的权限,来保护系统或者app的组件不会被随意访问。实现方式是,为组件添加android:permission属性。

    Activity 权限(用于 <activity> 标记) 限定了 哪些app可以启动Activity。权限检查是在 Context.startActivity()Activity.startActivityForResult()的时候进行的。如果调用者没有此权限,则抛出SecurityException异常。

    Service 权限(用于 <service> 标记) 限定了哪些app可以启动(start)或者绑定(bind)服务。权限检查是在Context.startService(), Context.stopService()Context.bindService()的时候进行的。如果调用者没有此权限,则抛出SecurityException异常。

    BroadcastReceiver 权限(用于<receiver> 标记) 限定了哪些app可以发送广播给receiver。权限检查是在Context.sendBroadcast()返回之后,在系统尝试将广播递交给receiver的时候。即使调用者没有权限,也不会抛出异常,只是不递送intent(广播)给receiver。

    同理,在调用Context.registerReceiver()的时候,也可以加上权限,来控制谁可以发广播给这个动态注册的receiver。也可以在调用Context.sendBroadcast()的时候加上权限,来限定哪些BroadcastReceiver 允许接收这个广播。

    注意,可以同时给receiver(动态或静态receiver)加上权限,并且给Context.sendBroadcast()加上权限,在这种情况下,需要双方都验证通过,才能完成信息传递。
    注:动态receiver是指Context.registerReceiver()声明的receiver。
    静态receiver是指AndroidManifest.xml中<receiver> 声明的receiver。

    ContentProvider 权限(用于 <provider> 标记) 限定了哪些app可以访问ContentProvider中的数据。与其他组件不同,ContentProvider有2种权限android:readPermissionandroid:writePermission,分别控制读和写。
    注意:如果provider同时受读权限写权限保护,调用者只拥有写权限,是不能够执行读操作的。
    在获取provider的时候,会进行权限检查。如果没有读权限和写权限,则抛出SecurityException异常。

    在操作provider的时候,也会进行权限检查。在这里操作provider是指增删查改操作,读写文件。

    查询的时候(ContentResolver.query() 被调用),需要读权限;添加、修改、删除的时候(ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()被调用 ),需要写权限。如果没有相应的权限,则抛出SecurityException异常。

    读文件(openFile()时,mode值含有r),需要读权限;写文件(openFile()时,mode值含有w),需要写权限。

    具体都有哪些情况检查读写权限,可以参考ContentProvider.java

    10.2 URI Permissions

    URI 权限是对上面的权限的补充,是用这个场景中的:App A的content provider不能够开放读写权限给App B(或者其他app),但是App A又需要传递一些数据给App B。在这个场景中,就可以使用URI权限。

    典型的例子是,查看邮件app中的附件(例如图片)。访问邮件是受读写权限保护,因为是用户的敏感数据。通过image viewer这个app查看邮件附件中的图片,但是不能够让image viewer拥有读取邮件的权限,这时,image viewer无法打开附件中的图片。

    解决办法,就是使用URI权限。这也算是,给没有读写权限的app关上一扇门,然后给那些app打开一扇窗。

    启动activity 或者 返回结果给activity 的时候,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION 和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION标志。.这时接收数据的activity(例如上面场景中的App B或者image viewer,为便于描述,在这里称之为App B吧)就有权限访问Intent中的URI指定的数据了,即使App B没有读写content provider的权限。

    注意:App B保持访问特定URI的权限,不是永久的,只持续到App B应用退出之前或者 URI权限回收之前。

    Content provider通过android:grantUriPermissions或者<grant-uri-permissions>来支持URI权限。

    也可以在启动Activity的时候,通过Context.grantUriPermission()赋予URI权限。Context.revokeUriPermission()可收回URI权限,Context.checkUriPermission()用来检查URI权限。

    11 如果targetSdkVersion版本低,那些在targetSdkVersion之后新加的权限就会被自动赋予

    参考: https://developer.android.com/guide/topics/security/permissions.html#auto-adjustments

    随着时间的推移,Android中会对一些特定的API加上权限保护(应该是用新的权限对已有的API进行保护)。对于已经存在的app,以前调用这些API是不需要权限的,但是在新版本的Android系统中需要检查权限。这个问题就是旧app运行在新系统上的问题。

    为了让旧app能够在新版本的Android系统中正常的运行,Android会自动给这些app的manifest中加上相应的权限。何时给某个app加上缺失的权限,取决于targetSdkVersion如果targetSdkVersion的值比添加新权限时的Api level低,那么Android将为其加上权限,即使该app并不需要此权限。

    例如,WRITE_EXTERNAL_STORAGE权限是在API level 4的时候添加的,如果某个app的targetSdkVersion为3或者更小,那么当这个app运行在新版本的Android中时,自动会获得WRITE_EXTERNAL_STORAGE权限。在这里,新版本的Android是指Api level为4或4以上。

    在这种情况下,查看这个app请求的所有权限的时候,会看到Android自动为这个app添加的权限。(注:可以用命令 adb shell dumpsys package <包名>查看某个app所请求的全部权限。)

    为了避免不必要的权限添加到你的app中,要经常更新targetSdkVersion,越大越好。查看Android每一个版本中都新加了哪些权限,请参考:https://developer.android.com/reference/android/os/Build.VERSION_CODES.html

    12 Android版本演进过程中新添加的权限

    注:一般的权限名的前缀为android.permission.,也有部分权限不是,例如UNINSTALL_SHORTCUT,完整权限为com.android.launcher.permission.UNINSTALL_SHORTCUT

    Api level Android 版本 新加的权限
    4 Donut, 1.6 (2009.9) WRITE_EXTERNAL_STORAGE
    READ_PHONE_STATE
    CHANGE_WIFI_MULTICAST_STATE
    GLOBAL_SEARCH
    INSTALL_LOCATION_PROVIDER
    5 Eclair, 2.0 (2009.11) ACCOUNT_MANAGER
    8 Froyo, 2.2 (2010.6) BIND_DEVICE_ADMIN
    BIND_WALLPAPER
    KILL_BACKGROUND_PROCESSES
    SET_TIME
    9 Gingerbread, 2.3 (2010.11) NFC
    SET_ALARM
    USE_SIP
    11 Honeycomb, 3.0 (2011.2) BIND_REMOTEVIEWS
    14 Ice Cream Sandwich, 4.0 (2011.10) ADD_VOICEMAIL
    BIND_TEXT_SERVICE
    BIND_VPN_SERVICE
    16 JellyBean, 4.1 (2012.6) READ_CALL_LOG
    WRITE_CALL_LOG
    BIND_ACCESSIBILITY_SERVICE
    READ_EXTERNAL_STORAGE
    注意:请求 WRITE_EXTERNAL_STORAGE权限的时候,将会自动获取到READ_EXTERNAL_STORAGE权限。
    18 JellyBean MR2, 4.3 (2013.7) BIND_NOTIFICATION_LISTENER_SERVICE
    LOCATION_HARDWARE
    SEND_RESPOND_VIA_MESSAGE
    19 Kitkat, 4.4 (2013.10) BIND_NFC_SERVICE
    BIND_PRINT_SERVICE
    BLUETOOTH_PRIVILEGED
    CAPTURE_AUDIO_OUTPUT
    CAPTURE_SECURE_VIDEO_OUTPUT
    CAPTURE_VIDEO_OUTPUT
    INSTALL_SHORTCUT
    MANAGE_DOCUMENTS
    MEDIA_CONTENT_CONTROL
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    20 Kitkat Watch, 4,4W (2014.6) BODY_SENSORS
    21 Lollipop, 5.0 (2014.11) BIND_DREAM_SERVICE
    BIND_TV_INPUT
    BIND_VOICE_INTERACTION
    READ_VOICEMAIL
    WRITE_VOICEMAIL
    22 Lollipop MR1, 5.1 (2015.3) BIND_CARRIER_MESSAGING_SERVICE
    注意:此权限在Api level23的时候被弃用,采用BIND_CARRIER_SERVICES来代替。
    23 Marshmallow, 6.0 ACCESS_NOTIFICATION_POLICY
    BIND_CARRIER_SERVICES
    BIND_CHOOSER_TARGET_SERVICE
    BIND_INCALL_SERVICE
    BIND_MIDI_DEVICE_SERVICE
    BIND_TELECOM_CONNECTION_SERVICE
    GET_ACCOUNTS_PRIVILEGED
    PACKAGE_USAGE_STATS
    REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
    REQUEST_INSTALL_PACKAGES
    USE_FINGERPRINT
    24 Nougat, 7.0 BIND_CONDITION_PROVIDER_SERVICE
    BIND_QUICK_SETTINGS_TILE
    BIND_SCREENING_SERVICE
    BIND_VR_LISTENER_SERVICE

    参考:https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
    完整权限名,请参考:https://developer.android.com/reference/android/Manifest.permission.html

    Android版本与Api level的对应关系,请参考:https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels

    展开全文
  • Android权限说明 Android系统是运行在Linux内核上的,Android与Linux分别有自己的一套严格的安全及权限机制。 一、linux文件系统上的权限 -rwxr-x--x system system 4156 2012-06-30 16:12 test.apk. “-rwxr...
  • Android权限之动态权限

    千次阅读 2019-05-05 14:57:08
    安卓系统的权限管理机制从API 23 (也就是Android 6.0 又叫做 Android M,)之后发生了比较大的改变,在一些比较危险的权限上要求必须申请动态权限,即使你在AndroidMainfest.xml文件中申请也没有任何用,或者你可以将...
  • Android权限管理详解

    万次阅读 2017-05-15 19:39:02
    概述Android安全架构规定:默认情况下,任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读写用户的私有数据(如联系人或电子邮件等)、读写其他应用的文件、执行网络访问、使设备...
  • Android权限(Permissions)处理

    千次阅读 2018-07-02 15:11:23
    Android权限主要用于限制应用程序内部某些具有限制性的功能使用,以及应用程序之间的组件访问。但是呢Android6.0之后,Google对权限做了一些优化,将一些权限的申请放在了应用运行的时候去申请(动态获取权限),所以...
  • 前言 感觉已经很久没有写博客了,5月份之后一直在学习kotlin,边学边用,算是入门了吧;然后又突然对热更新技术很有...就当我正在浑浑噩噩之时,突和朋友讨论起的Android权限申请的问题,最后我们得出这样一个结论...
  • 本文显示Android的权限大全和动态使用Android权限方法
  • Android权限  权限是一种限制,用于限制对部分代码或设备上数据的访问。施加限制是为了保护可能被误用以致破坏或损害用户体验的关键数据和代码。每种权限均由一个唯一的标签标识。标签通常指示受限制的操作。  ...
  • Android 权限清单大全

    千次阅读 2015-05-28 10:44:50
    Android权限设置 概述 权限 说明 访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES  读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permission.ACCESS_COARSE_...
  • 很多像我这样的新手,尤其是习惯了windows低安全限制的用户,很容易在这方面弄混淆,下面是我总结的Android系统权限相关的内容, 作为这段时间对android权限学习的总结,也希望能对大家有所帮助,不正确之...
  • Xamarin Android权限请求

    千次阅读 2017-09-12 14:19:45
    Xamarin Android权限请求
  • Android权限处理,不同版本的兼容

    千次阅读 2017-09-07 15:10:50
    Android权限机制的变化.主要以Android 6.0为分水岭.关于Android的权限机制变化的文章,网上的博客很多,我这里对自己开发过程中遇到的情况,进行的整理和分类,主要根据targetSdkVersion和Build.VERSION.SDK_INT的区别...
  • Android权限设置引导

    千次阅读 2018-05-19 19:02:54
    Android中当我们需要的权限被禁用时,需要引导用户去开启该权限(一般是跳转到权限设置页面),这样可以达到优化用户体验的效果。 我们知道Android 6.0之后对于部分敏感权限,如电话、短信、SD和相机等需要进行...
  • cordova 插件 开发添加 android 权限

    千次阅读 2016-11-28 22:28:36
    cordova 插件 开发添加 android 权限
  • Unity Android 权限

    万次阅读 2017-06-22 14:22:51
    unity android 权限
  • Android权限机制,你真的了解吗?

    万次阅读 2016-08-15 16:29:34
    一、Android权限机制 Android是目前最流行的智能手机软件平台之一,在智能移动终端如火如荼发展的同时,其安全态势也日益严峻。有调查表明,恶意软件的数量在持续的上升,Google在Android安全机制上面也做了很...
  • Android6.0之后推出动态申请权限,所有敏感权限默认都是没有开启的状态,这两天偶然间看到其他app安装后所有权限(包括敏感权限)默认都是开启的状态,6.0之后敏感权限都需要申请的,怎么可能跨过用户的,好奇心驱使...
1 2 3 4 5 ... 20
收藏数 236,271
精华内容 94,508
关键字:

android权限