Android 6.0动态权限申请详解

上篇文章我们讲解了通过隐式意图拨打电话,在AndroidManifest.xml文件中添加了权限 
<uses-permission android:name="android.permission.CALL_PHONE"/> 
接下来我们单独写个拨打电话的代码,添加好权限,并部署到最新的Android 6.0的模拟器上,需要注意的小细节,我在app/build.gradle的配置为:

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.demo.myapplication"
        minSdkVersion 14
        targetSdkVersion 22 
        versionCode 1
        versionName "1.0"
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

需要注意的是 targetSdkVersion22 代表适配的版本是Android5.1 并不是Android6.0, 我们先来看看运行结果,当我们点击拨打电话,是可以正常运行的吗,并没有任何问题。 
这里写图片描述

代码也是和之前的一模一样:

    public void callPhone(View view) {

        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + 5335458));
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然而,这并不是重点,接下来重点来了,上面的代码目标版本是Android5.1,接下来我们把targetSdkVersion改成最新的23, 前提是你的sdk里面有23这个版本。

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.demo.myapplication"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们把改后的代码重新装到23版本的模拟器中,这时候当我们点击拨打电话的时候,程序崩溃了,

这里写图片描述

我仅仅改了一处地方,带来的变化是如此之大。是不是有种想撞墙的感觉,我们先来看看Android6.0重要的变化。

Android6.0 新的权限机制

android在不断发展,android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西。(国内定制系统除外) 
难怪一些坏蛋利用这个缺陷恶意收集用户数据用来做坏事了! 
android小组也知道这事儿。权限系统终于被重新设计了。在android6.0棉花糖,app将不会在安装的时候授予权限。取而代之的是,app不得不在运行时一个一个询问用户授予权限。如下图(“你好”是程序的名字,大家别误会….): 
这里写图片描述

注意权限询问对话框不会自己弹出来。开发者不得不自己调用。如果开发者要调用的一些函数需要某权限而用户又拒绝授权的话,函数将抛出异常直接导致程序崩溃。 
另外,用户也可以随时在设置里取消已经授权的权限。

你或许已经感觉到背后生出一阵寒意。。。如果你是个android开发者,意味着要完全改变你的程序逻辑。你不能像以前那样直接调用方法了,你不得不为每个需要的地方检察权限,否则app就崩溃了! 
是的。我不能哄你说这是简单的事儿。尽管这对用户来说是好事,但是对开发者来说就是噩梦。我们不得不修改编码不然不论短期还是长远来看都是潜在的问题。

这个新的运行时权限仅当我们设置targetSdkVersion to 23(这意味着你已经在23上测试通过了)才起作用,当然还要是6.0系统的手机。app在6.0之前的设备依然使用旧的权限系统。

这就可以解释上面的例子,如果你的代码targetSdkVersion是小于23的,Android系统默认采取低版本的权限规则,如果targetSdkVersion=23,系统就会认为你的程序是经历过23版本测试的,就会采用新的权限管理机制.所以如果大家的程序没有适配好6.0,不要轻易改成targetSdkVersion=23。

之前的App如果没有兼容6.0,也是可以在6.0系统上嗷嗷的跑,不会有问题,但是用户是可以关闭授权的,这会影响用户的正常体验。我们如何在6.0上申请权限呢?

申请权限

权限分为两种: 
第一种就是危害不大的,比如手机振动权限,这种权限是用户在安装程序的时候添加的,和之前请求权限是一样的,没有任何变化,就是在清单文件中添加相关的权限,这些权限我简单的列了出来:

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

第二种权限就是涉及到用户隐私之类的权限,这类权限就需要在代码中动态请求用户批准了,权限被分组了,如下表: 
这里写图片描述 
同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了。 
源码中被用来检查和请求权限的方法分别是Activity的checkSelfPermission和requestPermissions。这些方法api23引入。

我们接下来把上面的例子进行修改:

    public void callPhone(View view) {
        // 检查是否有授权
        int i = checkSelfPermission(Manifest.permission.CALL_PHONE);
        if(i!= PackageManager.PERMISSION_GRANTED){   // 当没有授权的时候调用
            // 参数1请求的授权,可以同时请求多个授权,  参数2为请求码
            requestPermissions(new String[]{Manifest.permission.CALL_PHONE},1);
            return;
        }
        // 如果有授权直接拨打电话
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + 5335458));
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

当调用requestPermissions()方法的时候,用户就会看到请求授权的对话框,当用户点击对话框的时候就会调用Activity的onRequestPermissionsResult方法,我们接下来在这个方法中去实现拨打电话:

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if(requestCode==1){
            if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
                //  权限通过
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + 5335458));
                startActivity(intent);
            }else{
                // 权限拒绝
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面的代码有一个问题,就是当程序部署到6.0以下的设备的时候,就会报错,因为之前版本没有checkSelfPermission和requestPermissions方法,粗暴的方法就是直接检查版本,

if (Build.VERSION.SDK_INT >= 23) {
    // 6.0之后的操作
} else {
   // 6.0之前的操作
}
  • 1
  • 2
  • 3
  • 4
  • 5

其实我们可以借助v7包里的api, 
ContextCompat.checkSelfPermission() 
被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。 
ActivityCompat.requestPermissions() 
这个方法在6.0之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。 
ActivityCompat.shouldShowRequestPermissionRationale() 
在6.0之前版本调用,永远返回false。 
用v7包的这三方法,完美兼容所有版本!这个方法需要额外的参数,Context or Activity。别的就没啥特别的了。下面是完整代码:

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
    }

    public void callPhone(View view) {
        int i = ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE);
        if(i!= PackageManager.PERMISSION_GRANTED){   // 当没有授权的时候调用
            // 参数1申请的权限,可以同时请求多个授权,  参数2请求码
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1);
            return;
        }
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + 5335458));
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if(requestCode==1){
            if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
                //  权限通过
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + 5335458));
                startActivity(intent);
            }else{
                // 权限拒绝
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

我们也可以在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions(), FragmentCompat.shouldShowRequestPermissionRationale().和activity效果一样。

最后的建议

我相信你对新权限模型已经有了清晰的认识。我相信你也意识到了问题的严峻。 
如果你代码没支持新权限,不要设置targetSdkVersion 23 。尤其是当你在Android Studio新建工程时,不要忘了修改! 
如果你有要维护的程序,建议你提前重构好代码,否则,一旦加上新的授权认证,代码很难保证像以前容易阅读。


//安卓6.0以上读取内存文件需要手动请求权限
    public static boolean isGrantExternalRW(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            activity.requestPermissions(new String[]{
				//将需要动态申请的权限写在这里
				//内存卡读写
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);
            return false;
        }
        return true;
    }
    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1) {
            for (int i = 0; i < permissions.length; i++) {
                String permission = permissions[i];
                int grantResult = grantResults[i];
                if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    if (grantResult == PackageManager.PERMISSION_GRANTED) {
                        //授权成功后的逻辑
                    } else {
                        requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
                    }
                }
            }
        }
    }


版权声明:本文为Swust_Zeng_zhuo_K原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Swust_Zeng_zhuo_K/article/details/78669047

智能推荐

Ubuntu 14.04 下,安装 Java8

下载Java http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 打开上述链接, , 下载 特定的 Java8 版本。笔者选择的是 jdk-8u172-linux-x64.tar.gz。 - 配置Java环境变量 将 jdk-8u172-linux-x64.tar.gz 解压至指定目...

OpenCV学习之路(五)图像的几何变换

在这一章将要学习图像的移动、旋转,仿射变换等 扩展缩放 我们如果想要改变图像的大小,我们就需要对图像进行扩展缩放,opencv提供给我们控制扩展缩放的函数: 参数解释: src:进行扩展缩放的原图片 dst:可以在此处设置缩放因子,也可手动设置尺寸 interpolation:在缩放时我们推荐使用cv2.INTER_AREA, 在扩展时我们推荐使用cv2.INTER_CUBIC(慢) 和 cv2....

2018.8.27

2018.8.27...

HTML 表单元素的基本样式

HTML 表单元素的基本样式 原创 ixygj197875 发布于2018-02-22 17:48:53 阅读数 2296 收藏 更新于2018-05-20 15:35:58 分类专栏: 揭秘 CSS 揭秘 CSS 收起 表单元素主要包括 label、input、textarea、select、datalist、******、progress、meter、output等,以及对表单元素进行分组的 ...

php输出语句

php输出语句 常见的输出语句 echo(): 可以一次输出多个值,多个值之间用逗号分隔。echo是语言结构(language construct),而并不是真正的函数,因此不能作为表达式的一部分使用。 print(): 函数print()打印一个值(它的参数),如果字符串成功显示则返回true,否则返回false。 print_r(): 可以把字符串和数字简单地打印出来,而数组则以括起来的键和值...

猜你喜欢

工厂模式

简介 常见的实例化对象模式。 用工厂方法替代new操作的一种模式。 当我们使用new操作实例化对象时,调用构造函数完成初始化。若初始化仅是进行赋值等简单的操作,写入构造函数即可。但如果初始化时需要执行一长串复杂的代码,将多个工作装入一个方法,是不妥的。 创建实例与使用实例分离。将创建实例所需的大量初始化工作从基类的构造函数中分离出去。 简单工厂模式、工厂方法模式针对的是一个产品等级结构;而抽象工厂...

B1105 Spiral Matrix (画图)

B1105 Spiral Matrix (25分) //第一次只拿了21分 矩阵的长和宽,求最大因子,从sqrt(num)开始枚举. 每次循环一次,s++,t--,d--,r++ 测试点四运行超时,是因为输入一个数字的时候,需要直接输出这个数字。//1分 测试点二运行超时,最后一个数字不必再while循环一次,直接输出即可。//3分 最后一个测试点卡了好久/(ㄒoㄒ)/~~ 螺旋矩阵...

Java基础=>String,StringBuffer与StringBuilder的区别

字符串常量池 什么是字符串常量池? JVM为了减少字符串对象的重复创建,其维护了一块特殊的内存,这段内存被称为字符串常量池(存储在方法区中)。 具体实现 当代码中出现字符串时,JVM首先会对其进行检查。 如果字符串常量池中存在相同内容的字符串对象,如果有,则不再创建,直接返回这个对象的地址返回。 如果字符串常量池中不存在相同内容的字符串对象,则创建一个新的字符串对象并放入常量池,并返回新创建的字符...

java调用其他java项目的Https接口

项目中是这样的: 用户拿出二维码展示,让机器识别二维码, 机器调用开门的后台系统接口, 然后开门的后台系统接口需要调用管理系统的接口, 管理系统需要判断能不能开门.这两个系统是互相独立的.当时使用http调用是没有问题的.当时后来要求必须用https.废话不说,直接代码: 我的项目中调用的是 HttpsUtils.Get(utlStr) 这个接口 开门系统接口如下图:   管理系统的接口...

Hadoop1.2.1全分布式模式配置

一 集群规划 主机名            IP                               安装的软件 &nbs...