• Android动态权限详解


    什么是动态权限

    首先,从一张图开始此文。

    IOS 12定位权限

    Android权限管理简史

    第一阶段:没遮拦

      <!-- PHONE_STATE权限-->
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <!-- 网络权限-->
     <uses-permission android:name="android.permission.INTERNET" />
    
    

    第二阶段:第三方安全App

    例:HTC T328 Android 4.0.2系统。浏览器扫码功能触发相机调用时,360手机卫士会弹出权限提示窗,用户可以允许或拒绝授权。注意,此窗由第三方安全软件弹出,非系统级弹窗,跟后面要说的两种弹窗有所区别。

    360手机卫士 弹窗

    第三阶段:手机厂商介入

    第四阶段:谷歌升级权限管理

    例:Pixel2,原生Android 10;华为mate8,基于Android 8.0的EMUI8。浏览器扫码功能触发相机调用时,会弹出权限提示窗。此窗,由App通知系统弹出,为系统级权限弹窗。

    第三阶段与第四阶段,同为系统弹出授权弹窗。二者有什么区别吗?

    首先,从UI上很难判断所弹授权窗为第三阶段或第四阶段。第三阶段弹的系统授权窗大都带有一个倒计时自动拒绝逻辑;第四阶段弹的系统授权窗基本不带自动拒绝逻辑。此点可以粗略判断系统使用的哪种机制。

    其次,从原理上。第三阶段的弹窗,为系统监测到App在使用危险权限行为自动弹出弹窗。第四阶段的弹窗,为App发觉自己没有权限,让系统弹出的弹窗。粗俗的理解,第三阶段,你去朋友家串门,到门口看到大门敞开就直接往里走,触发了红外线报警器,报警器通知了你朋友;第四阶段,你去朋友家串门,到门口发觉门关着,就按下门铃呼叫朋友给你开门。

    目前,国内主要处于第三阶段(涵盖Android4.07.1)和第四阶段(涵盖Android6.010),此点将在后文用到。

    3

    如何应对动态权限特性

    方案一:逃避

    因为动态权限特性,仅从Android 6.0开始拥有,所以,可以简单粗暴的通过不提升targetSDK(targetSDK<23)的方式,便可不触发此特性。

    方案二:实现动态权限****

    1. 在使用权限前,检测权限。

    首先,我们需要判断自己是否拥有权限。判断时间点为执行需要权限的对应操作前。如我们在获取IMEI前,需要判断是否拥有PHONE_STATE权限。

    我们可以调用ContextCompat.checkSelfPermission()方法检测授权状态,返回的结果为PackageManager中的两个常量:PERMISSION_GRANTED(已授权)和PERMISSION_DENIED(未授权)。

    2. 已授权的情况下,执行你的原有操作。

    当已授权时,就可以执行你原有的操作了。代码如下:

     // 检测PHONE_STATE 如果已授权
     if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
         //做你想做的
     }
    
    

    那么如果未授权怎么办?

    3. 未授权的情况下,申请权限。

    如果App未获得授权,我们就需要向用户申请授权。可以调用requestPermissions()方法来请求授权。代码如下:

    // 检测PHONE_STATE 如果未授权
     if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
         //申请权限
         ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), PERMISSIONS_REQUEST_PHONE_STATE)
     }
    
    

    调用申请授权方法后,ROM会调起一个系统级弹窗(如下图),这个dialog你无法定制。当用户点击同意后,系统会记录,下次再判断权限时就会返回已授权状态;当App卸载时,记录会被清除。

    以上,就完成了最朴素版的授权逻辑。整体代码如下:

     // 检测PHONE_STATE 如果未授权
     if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
         //申请权限
         ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), PERMISSIONS_REQUEST_PHONE_STATE)
     }else {
         //如果已授权做你想做的
     }
    
    

    那么弹出申请弹窗之后呢?上面说道,弹出的dialog为系统的,我们无法在dialog中加代码,但当弹窗被用户点击后,会触发回调,我们在指定函数中处理回调即可。

    4. 重写函数,处理授权弹窗的点击结果。

    直接在Activity或Fragment中重写onRequestPermissionsResult()函数,来处理权限申请结果。requestPermissions()的第三个参数,将在这里被用到。代码如下:

      // 处理授权弹窗回调
     override fun onRequestPermissionsResult(
         requestCode: Int,
         permissions: Array<out String>,
         grantResults: IntArray
     ) {
         when(requestCode){
             // 识别刚刚用到的请求码,根据请求码识别不同弹窗回调并处理
             PERMISSIONS_REQUEST_PHONE_STATE ->{
                 // 如果用户点击“允许”
                 if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                     Toast.makeText(this, "用户允许权限!",Toast.LENGTH_SHORT).show()
                     // 可以继续执行你原来想做的事情了
     
                 }else{
                     Toast.makeText(this, "用户拒绝权限!",Toast.LENGTH_SHORT).show()
                     // 用户拒绝了,你想咋办?
                 }
                 return;
             }
             // 可以识别其他请求码并处理
         }
     }
    
    

    这样,就完成了授权流程。然后,为提升授权概率,对流程进行优化。

    5. 优化授权流程,提高授权几率。

    首先,系统授权窗我们无法定制,但是我们可以在这之前做个引导。在触发系统弹窗之前,弹出一个引导UI,来告知用户将要申请权限,并说明所需权限可带来哪些更好体验。尤其当你申请的权限看似与主要功能并无关系时,比如一个相机App如果需要申请定位权限的时候。

    综上,动态权限主要实现步骤

    1. 在AndroidManifest明确我们需要哪些权限。(非动态权限也需要此步)
      1.
      在执行操作前检是否获得对应授权 -> checkSelfPermission()。
      1.
      如果已授权可以继续操作;如果未授权,判断之前是否授权被拒 -> shouldShowRequestPermissionRationale() (非必须操作)
      a)判断如果没有被拒过,弹出首次授权引导。
      b)判断如果被据过,弹出非首次授权引导。
      1.
      引导后,申请权限-> requestPermissions()。
      1.
      处理申请的结果信息-> 回调函数onRequestPermissionsResult()。

    系统一共提供如下4个函数完成动态权限相关操作。

     /**
         * 检查指定的权限是否授权(Context对象调用)
         */
        public static int checkSelfPermission (Context context,
                    String permission)
    
        /**
         * 在没有授权的情况下,有些时候可能需要提示给用户为什么需要改权限,就通过该函数来实现。
         * 关于shouldShowRequestPermissionRationale的返回值问题,我们分三种情况
         * 1. 第一次打开App时 -> false
         * 2. 上次弹出权限点击了禁止(但没有勾选“下次不在询问”) -> true
         * 3. 上次选择禁止并勾选:下次不在询问 -> false
         */
        public static boolean shouldShowRequestPermissionRationale (Activity activity,
                    String permission)
    
        /**
         * 申请指定的权限(Activity或者Fragment对象调用)
         * @param permissions 权限列表,可以同时申请多个权限
         * @param requestCode 该次权限申请对应的requestCode。和 onRequestPermissionsResult()回调函数里面的requestCode对应
         */
        public static void requestPermissions (Activity activity,
                    String[] permissions,
                    int requestCode)
    
        /**
         * 处理请求权限的响应,当用户对请求权限的dialog做出响应之后,系统会回调该函数(Activity或者Fragment中重写)
         * @param requestCode 申请权限对应的requestCode
         * @param permissions 权限列表
         * @param grantResults 权限列表对应的返回值,判断permissions里面的每个权限是否申请成功
         */
        public abstract void onRequestPermissionsResult (int requestCode,
                    String[] permissions,
                    int[] grantResults)
    
    

    写到这里,动态授权实现demo部分均已完成,实际业务场景肯定比以上流程复杂的多

    4

    系统版本兼容

     fun test(){
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
             // 走动态授权
             return
         else
             // 走非动态授权
             return
     }
    
    

    以下为目前主流国内厂商对动态权限支持情况。(测试方法:在全新安装未进行过授权操作的情况下,使用checkSelfPermission()检查PHONE_STATE、定位、相机权限,返回如果是PERMISSION_GRANTED,则认为不支持动态权限)

    基于Android6.0的ROM基于Android7.0的ROM|------

    5

    关于权限弹窗

    1. 权限组icon
      1.
      App名称
      1.
      申请的权限
      1.
      允许、拒绝 操作
      1.
      不再询问选项
      1.
      多弹窗索引

    2.是否存在不再询问选项

    关于权限弹窗,针对同一个App的同一个权限,有时弹窗不带“拒绝&不再询问”选项,有时带此选项。如下图是谷歌原生系统、小米MIUI系统的两种弹窗对比。这是什么原因呢?Android原生实现:App全新安装后首次申请权限,弹窗不带此选项,即图左效果。当用户拒绝授权后,App下次再申请该权限时,则带此选项,即图右效果。但是,国内部分手机厂商并未遵循此标准,比如华为的Android 10之前的系统、OPPO/VIVO的部分权限,授权弹窗不管是否首次,都带此选项。此为系统行为,App无法决定。

    3.弹窗选项与App设置中权限选项对应关系

    以原生Android 10系统为例:

    以基于Android 9.0的MIUI10.4.8为例:

    4.弹窗选项对四个函数的影响。

    弹窗弹出,用户操作指定选项后,下次再调用四个函数会有如下现象:

    6

    权限分类

    But,部分ROM修改了此逻辑。比如,华为9.0以下系统,遵循的是原生系统Android 8.0之前的逻辑。但是,华为9.0以后系统和小米6.0以后系统,都用的比原生系统Android 8.0更严格的逻辑。每个权限都需要单独申请权限,而且会单独弹窗要求用户确认。

    例如:
    步骤一:Manifest中加入了
    READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE
    步骤二:在程序中只申请了READ_EXTERNAL_STORAGE权限,用户同意后
    步骤三:在程序中申请WRITE_EXTERNAL_STORAGE权限
    结果:会弹出授权弹窗,需要用户再次授权
    带来问题:相同权限组不同权限的授权弹窗是一毛一样的。这就导致用户很懵逼,明明刚刚授权过了,为什么又要问我一次。

    所以,部分手机上,你会发觉有些App,先后弹出两个访问文件存储的权限弹窗。那是因为写App的时候,先后申请了READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限导致。如何解决?

    查看requestPermissions()方法的第二个参数,为一个数组。也就是说,可以传入一个权限列表。

     /**
         * 申请指定的权限(Activity或者Fragment对象调用)
         * @param permissions 权限列表,可以同时申请多个权限
         * @param requestCode 该次权限申请对应的requestCode。和 onRequestPermissionsResult()回调函数里面的requestCode对应
         */
        public static void requestPermissions (Activity activity,
                    String[] permissions,
                    int requestCode)
    
    

    经测试,如果直接调该方法同时传入READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE只会弹出一个授权窗,而且用户同意后可以同时获得两个权限。如果传入不同组权限,则按先后每组弹出一个弹窗。而且,这种单次传入多组权限的情况,弹窗中大都会出现一个m/n的编号,以标识弹到第几个,还剩几个。如下图分别是MIUI10(基于android9)和EMUI10(基于android10)的弹窗样式:


    7

    写在最后

    后期的一些权限策略变化,仅列部分户感知较大的。

    • IOS 8(2014年),定位权限选项分为“使用期间”(新增项)、“始终允许”、“不允许”。(减少App后台定位)
    • IOS 10(2016年),App访问网络需要授权。
    • Android 8.0(2017年),
      • 安装未知来源应用需要申请权限。(App自升级、三方应用市场、广告App安装其他App需申请权限)
      • 权限组授权问题修复,上文有提及。
      • 定位权限选项分为“使用期间”(新增项)、“始终允许”、“拒绝”。(减少App后台定位)
      • 部分电话、蓝牙、WLAN的API,需要申请精确位置权限。
      • 无法再获取手机IMEI
      • 分区存储强制执行。Download目录、SD卡目录访问受限。
      • 对位置、麦克风、相机增加一次性权限许可,见IOS 13定位权限(即,如果用户选了一次性许可,重启App后需要重新申请权限)。
      • 自动阻止App重复的权限请求。也就是说如果用户点击2次拒绝授权,那么系统会自动停止询问授权,当然了,用户也可以前往设置中手动调整。
        两大平台,都在多个版本中对用户隐私进行了优化,仅定位权限的优化就多次提及。

        可见,在手机逐渐转化为人体器官之一的今天,IOS和Android两大移动平台对于权限、隐私的管理越发严苛,而且趋同的速度越来越快。估计以后Android App想访问网络也需申请授权。但手机厂商自行定制修改ROM,仍是开发者最头疼的问题。

      视频干货合集

      2024最新Python3.1x软件测试开发必备语法基础讲解

      7天软件测试快速入门教程

      测试开发精品公开课合集

      测试开发/自动化测试/性能测试/精准测试/测试左移/测试右移/人工智能测试

      大厂面试真题解析

      JMeter实时性能监控平台


      在这里插入图片描述

      另免费赠送软件测试开发方面的专业资料包!助您事半功倍,提升技能,把握职场先机。

      软件测试职业发展
      在这里插入图片描述
      零基础入门
      在这里插入图片描述

      测试必备编程篇
      在这里插入图片描述
      自动化测试
      在这里插入图片描述
      性能测试
      在这里插入图片描述
      测试管理
      在这里插入图片描述
      工程效能篇
      在这里插入图片描述
      面试求职篇

      软件测试的面试宝典,内含一线互联网大厂面试真题、面试技巧、软件测试面试简历指导,免费领取!
      在这里插入图片描述

    • 相关阅读:
      K8s---- Pod进阶(资源限制、健康检查)
      每日10行代码175:按条件取出且仅取出一条记录
      Redis 底层对 String 的 3 个优化
      ModSecurity开源WAF防火墙和控制面板安装教程
      vue高级用法extend,动态生成组件,checkbox选不中问题排除
      vim绝对常用命令
      微服务学习计划——SpringCloud
      csp202009-1称检测点查询(java实现)
      SSM整合
      Java Excel转PDF,支持xlsx和xls两种格式, itextpdf【即取即用】
    • 原文地址:https://blog.csdn.net/Hogwartstester/article/details/126951871