运行时权限_程序以管理员权限运行和以非管理员权限运行有什么区别 - CSDN
  • 在Kotlin中处理Android运行时权限的最简单方法
  • 运行时权限简介: Android权限的存在旨在保护用户的隐私和安全,不过在Android6.0之前,权限会在安装程序前进行询问,拒绝则不能安装程序,这样就出现了一些我们离开不了的程序出现“店大欺客”的现象。为了解决...
    运行时权限简介:

    Android权限的存在旨在保护用户的隐私和安全,不过在Android6.0之前,权限会在安装程序前进行询问,拒绝则不能安装程序,这样就出现了一些我们离开不了的程序出现“店大欺客”的现象。为了解决这个问题,Android在6.0添加了运行时权限功能,那些涉及用户安全和隐私的权限会在程序安装后,使用这个功能时,在用户同意获取权限后,程序方可动态添加权限。

    权限的分类:

    1.普通权限:不涉及用户安全和隐私的权限,只需在Manifest中添加权限语句,程序就可以自动完成权限的获取
    2.特殊权限:这种权限用的很少,我就不说明了
    3.危险权限:涉及用户的安全和隐私,这些权限需要运行时权限

    危险权限合计:

    这里写图片描述

    上图就是所有的危险权限,需要注意的是,每一个权限都在一个权限组中,当一个危险权限获取了,那么这个组中的其他危险权限默认可获取了。

    下面我们以获取CALLL_PHONE这个例子为例,来实例获取CALL_PHONE的运行时权限

    效果展现:

    效果分析:

    1.你拒绝获取时需要弹出提示,你拒绝了获取权限;
    2.当点击同意获取CALL_PHONE 权限后,程序才可以拨打电话;
    3.再次启用打电话功能时,不需要再次获取运行时权限;

    实现步骤:

    1.在activity_main.xml中添加Button控件
    2.在MainActivity中获取运行时权限
    3.在AndroidManifest.xml中添加权限

    代码实现:

    1.在activity_main.xml中添加Button控件
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.runtimepermissiontest.MainActivity">
    
        <Button
            android:id="@+id/main_call_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="make call" />
    
    </LinearLayout>
    2.在MainActivity中获取运行时权限
    public class MainActivity extends AppCompatActivity {
    
        private Button callBtn;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            callBtn = findViewById(R.id.main_call_btn);
            //对按钮进行监听
            callBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //通过if语句判断程序是否获取了CALL_PHONE的权限
                    //checkSelfPermission()param1:当前环境,param2:具体要获取的权限名
                    //通过checkSelfPermission()的返回值与PackageManager.PERMISSION_GRANTED比较,不等就是没有获取权限呢
                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                        //调用ActivityCompat.requestPermissions()方法获取权限
                        //param1:当前环境   param2:通过string数组来添加我们想要获取的权限 param3:请求码
                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
    
                    } else {
                        //如果获取了权限就直接调用call()方法拨打电话
                        call();
                    }
                }
            });
        }
    
        private void call() {
            try {
                //这里使用了隐式intent方法拨打电话
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10086"));
                startActivity(intent);
            } catch (SecurityException e) {
                e.printStackTrace();
            }
    
        }
        //重写onRequestPermissionsResult()方法,接收用户的操作,同意获取或拒绝
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            //通过请求码进行筛选
            switch (requestCode) {
                case 1:
                    //条件符合说明获取运行时权限成功
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        call();
                    } else {
                        //用户拒绝获取权限,则Toast出一句话提醒用户
                        Toast.makeText(this, "you denied the permission", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }
    3.在AndroidManifest.xml中添加权限
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.runtimepermissiontest">
    
        <uses-permission android:name="android.permission.CALL_PHONE" />
        <application
            android:allowBackup="true"
           ........................
    over
    这样就已经完成了,不过你是不是觉得有点繁琐,尤其是在涉及文件的时候会更加复杂,下面我来另一种获取权限的方法吧。

    印度人写的第三方插件:链接地址
    你可以先看他的介绍,在继续看我的,这样会明白一点

    1.添加依赖包:
    compile 'com.master.android:permissionhelper:2.0'
    2.修改MainActivity中的内容:
    public class MainActivity extends AppCompatActivity {
        private Button callBtn;
        //首先创建两个对象
        private static final String TAG = "MainActivity";
        private PermissionHelper permissionHelper;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            callBtn = findViewById(R.id.main_call_btn);
            callBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //下面都是对印度大神代码的copy,只是修改了部分
                    //将我们要获取的曲线添加进去
                    permissionHelper = new PermissionHelper(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 100);
                    permissionHelper.request(new PermissionHelper.PermissionCallback() {
                        @Override
                        public void onPermissionGranted() {
                            //这个方法是全都授权后将要进行的操作,我们在这调用call()方法
                            call();
                            Log.d(TAG, "onPermissionGranted() called");
                        }
    
                        @Override
                        public void onIndividualPermissionGranted(String[] grantedPermission) {
                            //某个授权
                            Log.d(TAG, "onIndividualPermissionGranted() called with: grantedPermission = [" + TextUtils.join(",", grantedPermission) + "]");
                        }
    
                        @Override
                        public void onPermissionDenied() {
                            //某个拒绝,我们弹出提示
                            Toast.makeText(MainActivity.this, "you denied the permission", Toast.LENGTH_SHORT).show();
                            Log.d(TAG, "onPermissionDenied() called");
                        }
    
                        @Override
                        public void onPermissionDeniedBySystem() {
                            //用户选择了"不再询问"后,点击"拒绝按钮",执行此方法
                            Log.d(TAG, "onPermissionDeniedBySystem() called");
                        }
                    });
                }
            });
    
    
        }
    
        private void call() {
            try {
                //这里使用了隐式intent方法拨打电话
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10086"));
                startActivity(intent);
            } catch (SecurityException e) {
                e.printStackTrace();
            }
    
        }
        //这也是印度大神写的一部分,这里不需要进行修改
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (permissionHelper != null) {
                permissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    
    
    }
    3.最后不要忘了在Manifest中添加权限。这样就完成了,不要被印度人写的代码给吓住,直接拷贝就行了啊,只需要修改少部分的代码。
    展开全文
  • 前言 今天是2017年8月01日,到目前为止,Android6.0已经发布了两年的时间,随着时间的推移,Android6.0肯定会越来越普及,而6.0版本的一个重大改动就是增加了运行时权限(动态权限):一些危险的权限不单止要在...

    前言

      今天是2017年8月01日,到目前为止,Android6.0已经发布了两年的时间,随着时间的推移,Android6.0肯定会越来越普及,而6.0版本的一个重大改动就是增加了运行时权限(动态权限):一些危险的权限不单止要在AndroidMainifest文件声明,还要在运行的时候使用代码来申请,让用户同意才进行授权
      由于Android自带的API使用起来(怎么使用就不写了,网上很多)比较麻烦,所以网上出现了一大堆简化这个过程的开源库,这里选取目前最流行的三个开源库(GitHub上Star最多)PermissionsDispatcherRxPermissionseasypermissions进行体验并对比他们的用法,了解一下这三个库的功能,方便做出选择。


    准备工作

    这里的demo使用一个Activity来测试每一种动态权限请求框架,分别测试它们的请求单个权限和请求多个权限的功能:

    这里写图片描述

      这里检查权限的方法我采用了一个工具类封装(由于PermissionsDispatcher、RxPermissions都没带有单纯检查权限的功能,只有easypermissions有,这里用一个工具类封装一下检查权限的方法,返回我想要的字符串):

    public class PermissionsLogUtils {
        private static StringBuffer logStringBuffer = new StringBuffer();
        // 查看权限是否已申请
        public static String checkPermissions(Context context,String... permissions) {
            logStringBuffer.delete(0,logStringBuffer.length());
            for (String permission : permissions) {
                logStringBuffer.append(permission);
                logStringBuffer.append(" is applied? \n     ");
                logStringBuffer.append(isAppliedPermission(context,permission));
                logStringBuffer.append("\n\n");
            }
            return logStringBuffer.toString();
        }
    
    
        //使用EasyPermissions查看权限是否已申请
        public static String easyCheckPermissions(Context context,String ... permissions){
            logStringBuffer.delete(0,logStringBuffer.length());
            for (String permission : permissions) {
                logStringBuffer.append(permission);
                logStringBuffer.append(" is applied? \n     ");
                logStringBuffer.append(EasyPermissions.hasPermissions(context,permission));
                logStringBuffer.append("\n\n");
            }
            return logStringBuffer.toString();
        }
    
    
        // 查看权限是否已申请
        private static boolean isAppliedPermission(Context context,String permission) {
            return context.checkSelfPermission(permission) ==
                    PackageManager.PERMISSION_GRANTED;
        }
    }

      然后,还要在AndroidMainifest文件声明demoAPP用到的权限,不在这里申明的话,无论后面在代码怎么动态申请,返回的结果都是权限拒绝并不再询问。


    基于注解的PermissionsDispatcher

    GitHub地址:https://github.com/hotchemi/PermissionsDispatcher
    目前Star数:4.7k

    集成方式

    在app的build.gradle文件里:

    dependencies {
      compile('com.github.hotchemi:permissionsdispatcher:${latest.version}') {
          // if you don't use android.app.Fragment you can exclude support for them
          exclude module: "support-v13"
      }
      annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
    }

    或者直接使用插件添加依赖,怎么用插件?下面有讲。

    使用

      PermissionsDispatcher是基于注解来写的库,基本原理就是你给你写的一个方法加上一个注解,然后它就会在适当的时候调用这个被注解的方法(这种方法很有趣,让代码变得简洁和清晰,以后可以学一下)。
      目前PermissionsDispatcher支持5个注解,先看看GitHub主页的介绍:

    这里写图片描述

    • @RuntimePermissions注解:这是必须使用的注解,用于标注在你想要申请权限的Activity或者Fragment上,如demo里面的PermissionsDispatcherActivity:
    @RuntimePermissions
    public class PermissionsDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
    
    }
    • @NeedsPermission注解:这也是必须使用的注解,用于标注在你要获取权限的方法,注解括号里面有参数,传入想要申请的权限。也就是说你获取了相应的权限之后就会执行这个方法:
        //获取单个权限
        @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        public void getSingle() {
            Toast.makeText(this, "getSingle", Toast.LENGTH_SHORT).show();
        }
    
        //获取多个权限
        @NeedsPermission({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})
        public void getMulti() {
            Toast.makeText(this, "getMulti", Toast.LENGTH_SHORT).show();
        }
    • @OnShowRationale注解:这个不是必须的注解,用于标注申请权限前需要执行的方法,注解
      括号里面有参数,传入想要申请的权限,而且这个方法还要传入一个PermissionRequest对象,这个对象有两种方法:proceed()让权限请求继续,cancel()让请求中断。也就是说,这个方法会拦截你发出的请求,这个方法用于告诉用户你接下来申请的权限是干嘛的,说服用户给你权限。
    @OnShowRationale({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})
        //给用户解释要请求什么权限,为什么需要此权限
        void showRationale(final PermissionRequest request) {
            new AlertDialog.Builder(this)
                    .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE和RECORD_AUDIO权限,下一步将继续请求权限")
                    .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            request.proceed();//继续执行请求
                        }
                    }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            request.cancel();//取消执行请求
                        }
                    }).show();
        }
    
        @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            //给用户解释要请求什么权限,为什么需要此权限
        void showSingleRationale(final PermissionRequest request) {
            new AlertDialog.Builder(this)
                    .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE,下一步将继续请求权限")
                    .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            request.proceed();//继续执行请求
                        }
                    }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    request.cancel();//取消执行请求
                }
            }).show();
        }
    • @OnPermissionDenied注解:这个也不是必须的注解,用于标注如果权限请求失败,但是用户没有勾选不再询问的时候执行的方法,注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败之后的处理,如像用户解释为什么要申请,或者重新申请操作等。
     @OnPermissionDenied({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//一旦用户拒绝了
        public void multiDenied() {
            Toast.makeText(this, "已拒绝一个或以上权限", Toast.LENGTH_SHORT).show();
        }
    
    
        @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)//一旦用户拒绝了
        public void StorageDenied() {
            Toast.makeText(this, "已拒觉WRITE_EXTERNAL_STORAGE权限", Toast.LENGTH_SHORT).show();
        }
    • @OnNeverAskAgain注解:这个也不是必须的注解,用于标注如果权限请求失败,而且用户勾选不再询问的时候执行的方法,注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败并选择不再询问之后的处理。例如,可以告诉作者想开启权限的就从手机设置里面开启。

    注意,有些系统的不再询问勾选项是要用户拒绝授权一次才显示出来的。

     @OnNeverAskAgain({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//用户选择的不再询问
        public void multiNeverAsk() {
            Toast.makeText(this, "已拒绝一个或以上权限,并不再询问", Toast.LENGTH_SHORT).show();
        }
        @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)//用户选择的不再询问
        public void StorageNeverAsk() {
            Toast.makeText(this, "已拒绝WRITE_EXTERNAL_STORAGE权限,并不再询问", Toast.LENGTH_SHORT).show();
        }

    注意,这些注解的方法都不能是private,原因看下面。

      使用PermissionsDispatcher除了要实现注解之外,还要重写Activity的onRequestPermissionsResult()方法,在里面让一个PermissionsDispatcher执行回调。这个PermissionsDispatcher是什么来的呢?

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

     @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            PermissionsDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
        }

      最后,申请权限的时候,调用辅助类的方法(名字从下面可以看出是被@OnPermissionDenied注解的方法加上WithCheck,参数是这个Activity或者Fragment)就行了:

     //申请单个权限
        PermissionsDispatcherActivityPermissionsDispatcher.getSingleWithCheck(this);
        //申请多个权限
        PermissionsDispatcherActivityPermissionsDispatcher.getMultiWithCheck(this);

    来一个流程图:

    这里写图片描述

    插件

      觉得这么多注解要自己一个一个弄不够方便,PermissionsDispatcher还在AndroidStudio做了插件,只要在setting设置里的plugins界面里搜索PermissionsDispatcher就可以安装了,安装完重启一下就能使用:

    这里写图片描述

      在所需的Activity或者Fragment的代码里面右键,选择Generate,然后就可以选择Generate Runtime Permissions…(生成动态权限的生成)或者下面的Add PermissionsDispatcher dependencies(添加PermissionsDispatcher依赖)

    这里写图片描述

    点击Generate Runtime Permissions…出现如下界面,输入方法名字就能生成,很简单粗暴:

    这里写图片描述

    生成了这个,如果你没onRequestPermissionsResult和@RuntimePermissions的话也会帮你加上:

      @NeedsPermission(Manifest.permission.CALL_PHONE)
        void call() {
        }
    
        @OnShowRationale(Manifest.permission.CALL_PHONE)
        void callshow(final PermissionRequest request) {
        }

    效果

    这里写图片描述

    这里的效果录制时长有限,没有展示出全部的情况,想了解全部情况的可以clone下我的demo来试试。


    谷歌推出的easypermissions

    GitHub地址:https://github.com/googlesamples/easypermissions
    目前Star数:3.5k

    集成方式

    在app的build.gradle文件里:

    dependencies {
        compile 'pub.devrel:easypermissions:0.4.2'
    }

    使用

      easypermissions是谷歌给出的一个运行时权限申请库(连谷歌自己都觉得自己的API用起来麻烦),下面我们来开始使用(下面步骤除了最后一步申请权限之外不分先后):

    1.重写要申请权限的Activity或者Fragment的onRequestPermissionsResult()方法,在里面调用
    EasyPermissions.onRequestPermissionsResult(),实现回调。

      @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
        }

    2.让需要申请权限的Activity或者Fragment实现EasyPermissions.PermissionCallbacks接口,重写里面的方法:

    public class EasyPermissionsActivity extends AppCompatActivity EasyPermissions.PermissionCallbacks{
    }
    • onPermissionsGranted(int requestCode, List list)方法:当权限被成功申请的时候执行回调,requestCode是代表你权限请求的识别码,list里面装着申请的权限的名字:
    @Override
        public void onPermissionsGranted(int requestCode, List<String> perms) {
            switch (requestCode){
                case 0:
                    Toast.makeText(this, "已获取WRITE_EXTERNAL_STORAGE权限", Toast.LENGTH_SHORT).show();
                    break;
                case 1:
                    Toast.makeText(this, "已获取WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    • onPermissionsDenied(int requestCode, List perms)方法:当权限申请失败的时候执行的回调,参数意义同上。在这个方法里面,官方还建议用EasyPermissions.somePermissionPermanentlyDenied(this, perms)方法来判断是否有权限被勾选了不再询问并拒绝,还提供了一个AppSettingsDialog来给我们使用,在这个对话框里面解释了APP需要这个权限的原因,用户按下是的话会跳到APP的设置界面,可以去设置权限(是不是很不要脸^_^),这个Dialog可以使用默认的样式new AppSettingsDialog.Builder(this).build().show(),也可以定制,像下面的一样:
        @Override
        public void onPermissionsDenied(int requestCode, List<String> perms) {
            //处理权限名字字符串
            StringBuffer sb = new StringBuffer();
            for (String str : perms){
                sb.append(str);
                sb.append("\n");
            }
            sb.replace(sb.length() - 2,sb.length(),"");
    
            switch (requestCode){
                case 0:
                    Toast.makeText(this, "已拒绝权限" + perms.get(0), Toast.LENGTH_SHORT).show();
                    break;
                case 1:
                    Toast.makeText(this, "已拒绝WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限"+ perms.get(0), Toast.LENGTH_SHORT).show();
                    break;
            }
            if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
                Toast.makeText(this, "已拒绝权限" + sb + "并不再询问" , Toast.LENGTH_SHORT).show();
                new AppSettingsDialog
                        .Builder(this)
                        .setRationale("此功能需要" + sb + "权限,否则无法正常使用,是否打开设置")
                        .setPositiveButton("好")
                        .setNegativeButton("不行")
                        .build()
                        .show();
            }
        }

    3.(可选)检查权限

      easypermissions提供了EasyPermissions.hasPermissions(Context context, @NonNull String… perms)方法来检测一个或者多个权限是否被允许(当有一个权限被拒绝就会返回false),可能是因为android自带的checkSelfPermission()比较方便(或者没这个必要?),PermissionsDispatcher和RxPermissions没有实现这个查询功能。这里我把它放到工具类里面封装了使用:

        //使用EasyPermissions查看权限是否已申请
        public static String easyCheckPermissions(Context context,String ... permissions) {
            logStringBuffer.delete(0,logStringBuffer.length());
            for (String permission : permissions) {
                logStringBuffer.append(permission);
                logStringBuffer.append(" is applied? \n     ");
                logStringBuffer.append(EasyPermissions.hasPermissions(context,permission));
                logStringBuffer.append("\n\n");
            }
            return logStringBuffer.toString();
        }

    4.(可选)添加@AfterPermissionGranted()注解

      要传入的参数是int类型的requestCode被这个注解标注的方法,当这个requestCode的请求成功的时候,会执行这个方法。其实就相当于在onPermissionsGranted()调用这个方法而已:

      @AfterPermissionGranted(0)
        private void afterGet(){
            Toast.makeText(this, "已获取权限,让我们干爱干的事吧!", Toast.LENGTH_SHORT).show();
        }

    5.调用申请权限

      最后,就是调用EasyPermissions.requestPermissions()方法来申请权限了:

    这里写图片描述

        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_check:
                    String str = PermissionsLogUtils.easyCheckPermissions(this,
                            Manifest.permission.RECORD_AUDIO,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE);
                    tv_log.setText(str);
                    break;
                case R.id.btn_getSingle:
                    EasyPermissions.requestPermissions(this,
                            "接下来需要获取WRITE_EXTERNAL_STORAGE权限",
                            R.string.yes,
                            R.string.no,
                            0,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE);
                    break;
                case R.id.btn_getMulti:
                    EasyPermissions.requestPermissions(this,
                            "接下来需要获取WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限",
                            R.string.yes,
                            R.string.no,
                            1,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.RECORD_AUDIO);
                    break;
            }

    这个流程比较简单:

    这里写图片描述

    效果

    这里写图片描述

    这里的效果录制时长有限,没有展示出全部的情况,想了解全部情况的可以clone下我的demo


    基于RxJava的RxPermissions

    GitHub地址:https://github.com/tbruyelle/RxPermissions
    目前Star数:3.8k

    集成方式

    在app的build.gradle文件里:

    //Rxjava1.x用这个
    dependencies {
        compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
    }
    //Rxjava2.x用这个
    dependencies {
        compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    }

      如果没有加入RxJava的还要加入它,我这里使用的是RxJava2,所以加上:

        compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
        compile 'io.reactivex.rxjava2:rxjava:2.1.0'

    使用

    下面的代码例子都是使用的RxJava2,和1版本有些不同,不过会用RxJava的基本都能看懂吧。

     RxPermissions的使用比较简单清晰,就在申请权限的时候使用一个方法,再里面实现逻辑就行了:

    • 申请单个或者多个权限,不在乎是否不再询问和哪个权限申请失败,只要有一个失败就执行失败操作:
        //请求权限
        private void requestRxPermissions(String... permissions) {
            RxPermissions rxPermissions = new RxPermissions(this);
            rxPermissions.request(permissions).subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(@NonNull Boolean granted) throws Exception {
                    if (granted){
                        Toast.makeText(RxPermissionsActivity.this, "已获取权限", Toast.LENGTH_SHORT).show();
                    }else {
                        Toast.makeText(RxPermissionsActivity.this, "已拒绝一个或以上权限", Toast.LENGTH_SHORT).show();
                    }
                }
            });
        }
    • 申请多个权限,在乎是否不再询问和哪个权限申请失败:
        private void requestEachRxPermission(String... permissions) {
            RxPermissions rxPermissions = new RxPermissions(this);
            rxPermissions.requestEach(permissions).subscribe(new Consumer<Permission>() {
                @Override
                public void accept(@NonNull Permission permission) throws Exception {
                    if (permission.granted) {
                        Toast.makeText(RxPermissionsActivity.this, "已获取权限"+ permission.name , Toast.LENGTH_SHORT).show();
                    } else if (permission.shouldShowRequestPermissionRationale){
                        //拒绝权限请求
                        Toast.makeText(RxPermissionsActivity.this, "已拒绝权限"+ permission.name , Toast.LENGTH_SHORT).show();
                    } else {
                        // 拒绝权限请求,并不再询问
                        // 可以提醒用户进入设置界面去设置权限
                        Toast.makeText(RxPermissionsActivity.this, "已拒绝权限"+ permission.name +"并不再询问", Toast.LENGTH_SHORT).show();
                    }
                }
            });
    
        }

    效果

    这里写图片描述

    这里的效果录制时长有限,没有展示出全部的情况,想了解全部情况的可以clone下我的demo来试试。


    总结

    共同点

      三者都简化了Android6.0申请运行时权限的流程,比使用Android自带的API方便很多,可扩展性高。

    不同点

    功能 PermissionsDispatcher easypermissions RxPermissions
    单独检查权限功能
    申请权限前提示操作 有,可以自定义操作,弹出Dialog、Toast、SnackBar等等都行 有,而且定制了Dialog 无,需要自己实现
    不再提示时的处理操作 有,而且可以使用Dialog让用户选择跳到APP设置界面
    一次申请多个权限时,对单个失败的权限处理操作
    结合RxJava
    不能把方法私有

    后话

      了解到这些之后,我们应该就可以按照自己的需求来选择用什么样的动态权限请求库了。

    以上经验经过本人demo测试,API Level 25,如有错漏,敬请指正。

    参考资料

    https://github.com/hotchemi/PermissionsDispatcher

    https://github.com/googlesamples/easypermissions

    https://github.com/tbruyelle/RxPermissions

    http://www.cnblogs.com/duduhuo/p/6228426.html

    转载自:http://blog.csdn.net/totond/article/details/73648103

    展开全文
  • android6.0之后加入了运行时权限:即危险权限在用户安装app并不授予,当在具体用到再提示用户是否授予,如获取手机联系人。 在之前开发的app中没有进行运行时权限处理,在调用获取手机联系人权限时并没有报错,...

    android6.0之后加入了运行时权限:即危险权限在用户安装app时并不授予,当在具体用到时再提示用户是否授予,如获取手机联系人。
    在之前开发的app中没有进行运行时权限处理,在调用获取手机联系人权限时并没有报错,而是系统提示用户要不要授予。

    我感觉6.0之后运行时异常这么处理是更合理的,一份代码在不同的系统都可以正常运行,不需要什么特殊适配,需要危险权限的时候直接系统提示用户给不给;你想啊要是换了6.0的手机下载之前的app没有做运行时权限处理,突然崩了,那不扯淡么,系统越更新,越好用才是。
    
    展开全文
  • 运行时权限(Runtime Permission)是Android 6.0( 代号为 Marshmallow,API版本为 23)及以上版本新增的功能,相比于以往版本,这是一个较大变化。本文将介绍如何在代码中加入并配置运行时权限功能。

    运行时权限(Runtime Permission)是Android 6.0( 代号为 Marshmallow,API版本为 23)及以上版本新增的功能,相比于以往版本,这是一个较大变化。本文将介绍如何在代码中加入并配置运行时权限功能。


    如需阅读英文原文,请您点击这个链接:《Everything every Android Developer must know about new Android’s Runtime Permission》

    如需阅读官方运行时权限的相关介绍,请您点击这个链接:《Working with System Permissions》


    运行时权限介绍

    一直以来,为了保证最大的安全性,安装Android应用时,系统总是让用户选择是否同意该应用所需的所有权限。一旦安装应用,就意味着该应用所需的所有权限均已获得。若在使用某个功能时用到了某个权限,系统将不会提醒用户该权限正在被获取(比如微信需要使用摄像头拍照,在Android 6.0以前的设备上,用户将不会被系统告知正在使用“使用系统摄像头”的权限)。


    这在安全性上是个隐患:在不经用户同意的情况下,一些应用在后台可以自由地收集用户隐私信息而不被用户察觉。


    从Android 6.0版本开始,这个隐患终于被消除了:在安装应用时,该应用无法取得任何权限!相反,在使用应用的过程中,若某个功能需要获取某个权限,系统会弹出一个对话框,显式地由用户决定是否将该权限赋予应用,只有得到了用户的许可,该功能才可以被使用。


    这里写图片描述


    需要注意的是,在上述的右图中,对话框并不会自动弹出,而需要由开发者手动调用。若程序调用的某个方法需要用户赋予相应权限,而此时该权限并未被赋予时,那么程序就会抛出异常并崩溃(Crash),如下图所示。


    这里写图片描述


    除此之外,用户还可以在任何时候撤销赋予过的权限。


    这里写图片描述


    运行时权限无疑提升了安全性,有效地保护了用户的隐私,这对于用户来说确实是个好消息,但对于开发者来说简直就是噩梦:因为这需要开发者在调用方法时,检查该方法使用了什么系统权限——这仿佛颠覆了传统的编程的逻辑——开发者编写每一句代码时都得小心翼翼,否则应用可能随时崩溃。


    在程序中,设置目标SDK版本(targetSDKVersion)为23及以上时(这意味着程序可以在Android 6.0及以上的版本中运行),将应用安装在Android 6.0及以上机型中,运行时权限功能才能生效;若将其安装在Android 6.0以前的机型中,权限检查仍将仅仅发生在安装应用时。


    运行时权限与各版本间的兼容性问题

    假如将一个早期版本的应用安装在Android 6.0版本的机型上,应用是不会崩溃的,因为这只有两种情况:1)该应用的targetSDKVersion < 23,在这种情况下,权限检查仍是早期的形式(仅在安装时赋予权限,使用时将不被提醒);2)该应用的targetSDKVersion ≥ 23时,则将使用新的运行时权限规则。


    这里写图片描述


    所以,这个早期版本的应用将运行如常。不过,将该应用安装在Android 6.0上,且targetSDKVersion ≥ 23时,用户仍然可以随时手动撤销权限,当然这种做法不被官方推荐。


    这里写图片描述


    不被推荐的原因是,这种做法容易导致应用崩溃。若targetSDKVersion < 23,当然不会出问题;若早期应用的targetSDKVersion ≥ 23,在使用应用时手动撤消了某个权限,那么程序在调用了需要这个权限才能执行的方法时,应用什么也不做,若该方法还有返回值,那么会根据实际情况返回 0 或者 null。如下图所示。


    这里写图片描述


    若上述调用的方法没有崩溃,那么这个方法被其他方法调用时也会因为返回值是 0 或者 null 而崩溃。


    不过好消息是,用户几乎不会手动撤销已经赋予给应用的权限。


    说了这么多,在避免应用崩溃的前提下,适配新的运行时权限功能才是王道:对于那些在代码中并未支持运行时权限的应用,请将targetSDKVersion设置为 < 23,否则应用有崩溃隐患;若代码中支持了运行时权限,再将targetSDKVersion设置为 ≥ 23。


    请注意:在Android Studio中新建Project时,会自动赋予targetSDKVersion为最新版本,若您的应用还暂时无法完全支持运行时权限功能,建议首先将targetSDKVersion手动设置为22。


    自动赋予应用的权限

    以下罗列了在安装应用时,自动赋予应用的权限,这些权限无法在安装后手动撤销。我们称其为基本权限(Normal Permission)

    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

    开发者仅需要在AndroidManifest.xml中声明这些权限,应用就能自动获取无需用户授权。


    为应用适配新的运行时权限


    为了设配新的运行时权限,首先需要将compileSdkVersiontargetSdkVersion设置为23:

    android {
        compileSdkVersion 23
        ...
    
        defaultConfig {
            ...
            targetSdkVersion 23
            ...
        }

    下面演示了一个增加联系人的方法,该方法是需使用WRITE_CONTACTS的权限:

    private static final String TAG = "Contacts";
    private void insertDummyContact() {
        // Two operations are needed to insert a new contact.
        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(2);
    
        // 1、设置一个新的联系人
        ContentProviderOperation.Builder op =
                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                        .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                        .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
        operations.add(op.build());
    
        // 1、为联系人设置姓名
        op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                        "__DUMMY CONTACT from runtime permissions sample");
        operations.add(op.build());
    
        // 3、使用ContentResolver添加该联系人
        ContentResolver resolver = getContentResolver();
        try {
            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
        } catch (RemoteException e) {
            Log.d(TAG, "Could not add a new contact: " + e.getMessage());
        } catch (OperationApplicationException e) {
            Log.d(TAG, "Could not add a new contact: " + e.getMessage());
        }
    }

    调用这个方法需要配置WRITE_CONTACTS权限,否则应用将崩溃:在AndroidManifest.xml中配置如下权限:

    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>

    接着,我们需要创建一个方法用于判断WRITE_CONTACTS权限是否确实被赋予;若方法为创建,那么可以弹出一个对话框向用户申请该权限。待权限被赋予后,方可新建联系人。


    权限被归类成权限组(Permission Group),如下表所示:


    这里写图片描述


    若应用被赋予了某个权限组中的一个权限(比如READ_CONTACTS权限被赋予),那么该组中的其他权限将被自动获取(WRITE_CONTACTSGET_ACCOUNTS权限被自动获取)。


    检查和申请权限的方法分别是Activity.checkSelfPermission()Activity.requestPermissions,这两个方法是在 API 23 中新增的。


    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
    
    private void insertDummyContactWrapper() {
        //检查AndroidManiFest中是否配置了WRITE_CONTACTS权限
        int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
        //若未配置该权限
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            //申请配置该权限
            requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                    REQUEST_CODE_ASK_PERMISSIONS);
            //直接返回,不执行insertDummyContact()方法
            return;
        }
        //若配置了该权限,才能调用方法
        insertDummyContact();
    }

    若程序赋予了权限,insertDummyContact()方法将被调用;否则,requestPermissions()方法将弹出一个对话框申请权限,如下所示:


    这里写图片描述


    无论您选择的是“DENY”还是“ALLOW”,程序都将回调Activity.onRequestPermissionsResult()方法,并将选择的结果传到方法的第三个参数中:

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_PERMISSIONS:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 用户选择了“ALLOW”,获取权限,调用方法
                    insertDummyContact();
                } else {
                    // 用户选择了“DENY”,未获取权限
                    Toast.makeText(MainActivity.this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)
                            .show();
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    这就是Android 6.0的全新运行时权限机制,为了提高安全性,增加代码量在所难免:为了匹配运行时权限机制,必须把处理方法的所有情况考虑在内。


    处理 “不再询问”(“Never Ask Again”)


    每当系统申请权限时,弹出的对话框会有一个“不再询问”(“Never Ask Again”)的勾选项。
    若用户打了勾,并选择拒绝(“DENY”),那么下次程序调用Activity。requestPermissions()方法时,将不会弹出对话框,权限也不会被赋予。
    这种没有反馈的交互并不是一个好的用户体验(User Experience)。所以,下次启动时,程序应弹出一个对话框,提示用户“您已经拒绝了使用该功能所需要的权限,若需要使用该功能,请手动开启权限”,应调用Activity.shouldShowRequestPermissionRationale()方法:


    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
    
    private void insertDummyContactWrapper() {
        int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
                if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
                    showMessageOKCancel("You need to allow access to Contacts",
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                                            REQUEST_CODE_ASK_PERMISSIONS);
                                }
                            });
                    return;
                }
            requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }
        insertDummyContact();
    }
    
    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(MainActivity.this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

    效果如下:
    这里写图片描述


    上述对话框应在两种情形下弹出:
    1)应用第一次申请权限时;

    2)用户勾选了“不再询问”复选框。


    对于第二种情况,Activity.onRequestPermissionsResult()方法将被回调,并回传参数PERMISSION_DENIED,该对话框将不再弹出。


    一次性申请多个权限


    有些功能需要申请多个权限,仍然可以像上述方式一样编写代码:

    final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
    
    private void insertDummyContactWrapper() {
        //提示用户需要手动开启的权限集合
        List<String> permissionsNeeded = new ArrayList<String>();
    
        //功能所需权限的集合
        final List<String> permissionsList = new ArrayList<String>();
        //若用户拒绝了该权限申请,则将该申请的提示添加到“用户需要手动开启的权限集合”中
        if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
            permissionsNeeded.add("GPS");
        if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS))
            permissionsNeeded.add("Read Contacts");
        if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))
            permissionsNeeded.add("Write Contacts");
    
        //若在AndroidManiFest中配置了所有所需权限,则让用户逐一赋予应用权限,若权限都被赋予,则执行方法并返回
        if (permissionsList.size() > 0) {
            //若用户赋予了一部分权限,则需要提示用户开启其余权限并返回,该功能将无法执行
            if (permissionsNeeded.size() > 0) {
                // Need Rationale
                String message = "You need to grant access to " + permissionsNeeded.get(0);
                for (int i = 1; i < permissionsNeeded.size(); i++)
                    message = message + ", " + permissionsNeeded.get(i);
                //弹出对话框,提示用户需要手动开启的权限
                showMessageOKCancel(message,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                            }
                        });
                return;
            }
            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            return;
        }
    
        insertDummyContact();
    }
    //判断用户是否授予了所需权限 
    private boolean addPermission(List<String> permissionsList, String permission) {
        //若配置了该权限,返回true
        if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
            //若未配置该权限,将其添加到所需权限的集合,返回true
            permissionsList.add(permission);
            // 若用户勾选了“永不询问”复选框,并拒绝了权限,则返回false
            if (!shouldShowRequestPermissionRationale(permission))
                return false;
        }
        return true;
    }

    当用户设置了每个权限是否可被赋予后,Activity.onRequestPermissionsResult()方法被回调,并传入第三个参数:


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
                {
                //初始化Map集合,其中Key存放所需权限,Value存放该权限是否被赋予
                Map<String, Integer> perms = new HashMap<String, Integer>();
                // 向Map集合中加入元素,初始时所有权限均设置为被赋予(PackageManager.PERMISSION_GRANTED)
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
                // 将第二个参数回传的所需权限及第三个参数回传的权限结果放入Map集合中,由于Map集合要求Key值不能重复,所以实际的权限结果将覆盖初始值
                for (int i = 0; i < permissions.length; i++)
                    perms.put(permissions[i], grantResults[i]);
                // 若所有权限均被赋予,则执行方法
                if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                        && perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
                        && perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
                    // All Permissions Granted
                    insertDummyContact();
                } 
                //否则弹出toast,告知用户需手动赋予权限
                else {
                    // Permission Denied
                    Toast.makeText(MainActivity.this, "Some Permission is Denied", Toast.LENGTH_SHORT)
                            .show();
                }
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    使用支持库(Support Library)提高程序的兼容性


    尽管上述代码在Android 6.0版本的设备上能够正常运行,但运行在早前版本的设备上,程序将崩溃。
    简单直接的方式是事先进行版本判断:


    if (Build.VERSION.SDK_INT >= 23) {
        // Marshmallow+
    } else {
        // Pre-Marshmallow
    }

    但这样会使程序变得臃肿。

    比较好的解决方式是使用Support Library v4支持库中的方法替换原来的方法,这将省去为不同版本的设备分别提供代码的麻烦:


    // 将Activity.checkSelfPermission()方法替换为如下方法
    ContextCompat.checkSelfPermission()

    // 将Activity.requestPermissions()方法替换为如下方法
    ActivityCompat.requestPermissions()

    //将Activity.shouldShowRequestPermissionRationale()方法替换为如下方法,在早期版本中,该方法直接返回false
    ActivityCompat.shouldShowRequestPermissionRationale() 

    无论哪个版本,调用上面的三个方法都需要Content或Activity参数。


    以下是使用Support Library v4支持库中的方法替换原代码中相应方法后的程序:


    private void insertDummyContactWrapper() {
        int hasWriteContactsPermission = ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.WRITE_CONTACTS);
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.WRITE_CONTACTS)) {
                showMessageOKCancel("You need to allow access to Contacts",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[] {Manifest.permission.WRITE_CONTACTS},
                                        REQUEST_CODE_ASK_PERMISSIONS);
                            }
                        });
                return;
            }
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[] {Manifest.permission.WRITE_CONTACTS},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }
        insertDummyContact();
    }

    需要注意的是,若程序中用到了Fragment,也最好使用android.support.v4.app.Fragment,这样可以兼容更低的版本,使应用适配更多设备。


    使用第三方开源库(3rd Party Library)简化代码


    为了是代码更加简洁,推荐一个第三方框架。该框架可以方便地集成运行时权限机制并有效兼容新旧版本。


    在应用打开时撤销权限所带来的问题


    如上所述,用户可以随时撤销赋予应用的权限,若某个应用正在运行时,用户撤消了其某些权限,应用所在进程会立刻终止(application’s process is suddenly terminated),所以尽量不要在应用运行时,改变其权限规则。


    总结与建议

    总结:

    运行时权限机制大大提高了应用的安全性,不过开发者需要为此修改代码以匹配新的版本,不过好消息是,大部分常用的权限都被自动赋予了,所以,只有很小一部分代码需要修改。


    建议:

    1. 使用运行时机制时应该以版本的兼容作为前提。

    2. 不要将未适配运行时机制的程序的targetSdkVersion设置为 23 及以上。


    感谢

    特别感谢原创作者的付出,下面是作者的介绍信息:

    这里写图片描述

    展开全文
  • Android 运行时权限详解 Android 开发常常遇到的一个问题就是在Android APP 安装的过程中,会向户请求一大堆权限,不同意不会让你安装,当然我是从来不会看的直接安装,我相信你也是,所以不知不觉中,也许有些敏感...

    关于Android 运行时权限

    Android 开发常常遇到的一个问题就是在Android APP 安装的过程中,会向户请求一大堆权限,不同意不会让你安装,当然我是从来不会看的直接安装,我相信你也是,所以不知不觉中,也许有些敏感权限就这样被授予了,(比如我突然收到某个从未注册的平台的推广短信),为了 解决这个问题 Android M 推出了运行时权限,敏感权限在真正使用的时候会想用户提示,用户的安全性和隐私得到保护,仅仅需要做一些适配工作,今天这里我们来解决两个问题 :

    • 运行时权限有啥变化
    • 我们应该怎么样去适配运行时权限

    首先我们看看官网上对6.0 权限变化的解释:

    • 如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。
    • 如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。

    谷歌将新的权限分为两类,一类是正常权限,比如联网,震动一类的,这类权限跟之前一样,清单文件声明后直接授予,另一类,是危险权限,譬如:读取联系人、相机、定位等涉及用户隐私的,需要在使用时通知用户进行授权,危险权限如下:
    这里写图片描述
    这些权限被分为一组一组的,对于分组谷歌是这样解释的。

    • 如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。*
    • 如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限

    后面的demo验证上面的描述。实测下来,部分定制手机会对权限分组进行修改(比如,1加手机中 READ_PHONE_STATE,为正常权限,在手机清单文件中直接声明便可使用),所以尽量不要依赖权限组,我们在需要使用权限的时候单个请求即可。

    #####大致流程:

    Created with Raphaël 2.2.0检查权限已授予?操作请求权限?yesnoyes

    #####示例:
    下面我以打电话为例,示例整个流程:
    测试手机 Google Pixel 2

    首先我在清单文件里面声明一项权限:

     <uses-permission android:name="android.permission.CALL_PHONE" />
    

    然后写一个按钮点击执行如下方法:

     private void makeCall() {
            Uri uri = Uri.parse("tel:10086");
            Intent callIntent = new Intent(Intent.ACTION_CALL, uri);
            startActivity(callIntent);
        }
    

    显然,在老的权限系统下是没有任何问题的。

    但是在新的系统下在新的权限系统下(手机Android版本6.0以上,并且当前应用的targetSdk 大于22, 所以这边会细分为四种情况,后面会分别讨论验证)应用就直接崩溃了,报出了:

    java.lang.SecurityException: getLine1NumberForDisplay: Neither user 10348 nor current process has android.permission.READ_PHONE_STATE or android.permission.READ_SMS.
    

    所以此时我们在请求我们权限之前,首先检查是否拥有该权限,代码如下:

    int checkResult = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CALL_PHONE);
    boolean hasPermission =checkResult== PackageManager.PERMISSION_GRANTED;
    

    当没有权限的时候,我们再去请求权限,(注意,在Activity和Framgment里面请求方法不一样):
    在Activity中:

     ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE);
    

    在Fragment中:

     requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE);
    

    当请求此方法以后,用户会收到一个系统级的弹框(这个无法被自定义)提示用户是否要授予你当前请求的权限,如果用户点击授予,权限授予成功,拒绝则授予失败,这两种情况下就会回调 activity 或者 fragment的 onRequestPermissionsResult()方法

      @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "权限授予失败", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "权限授予成功", Toast.LENGTH_SHORT).show();
                //todo 搞事情
            }
        }
    
    

    当授权成功后,我们可直接使用,当我们授权失败以后,我们再次请求权限的话,依然会有系统级的弹框提示,只是多一个"不再提示"的单选框,当选中再次拒绝的话,请求权限就会直接回调授予失败。至此,权限流程基本完成,当用户想要再次开启权限的时候,就只能去设置页的应用权限去开启,如果再次关闭,则重复上述流程。

    注意点:

    • 我们请求权限入参是一个数组,意味着我们可以一次请求多项权限,系统会依次弹框,然后在权限result的回调也是一个数组,也就是我们请求的每项权限的授予结果。
    • 很多第三方厂商对系统级授权做过一些改变,可能不会有二次弹框,或者第一次请求直接就会有"不在提示"的选项(如华为 )

    ######细节: 其实很多时候,用户对于这类请求弹框是比较疑惑的,或者经常会习惯性的点拒绝,(至少我以前是这样),或者对于权限请求不太理解,比如很多时候,我们拍照的时候,请求了相机权限,用户可能会明白,但是如果这个时候再请求定位权限,至少到这里,用户就很困惑了,所以在用户拒绝此次权限请求而你在申请此项权限的时候你需要给用户一个解释,告知用户为什么需要这项权限,所以这里要请求一个新的api:

    ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.CALL_PHONE)
    

    在应用第一次请求权限被拒绝以后,这个方法会返回true,所以综合上面几个api,连起来请求权限流程应该是这样:

    //  检查是否有权限
    if (ContextCompat.checkSelfPermission(thisActivity,
                    Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
    
        // 权限没有,是否需要给用户一个解释
        if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
                Manifest.permission.READ_CONTACTS)) {
    
            // 给用户一个解释以后,再次请求权限 ,比如起一个对话框告诉用户为啥要这个权限。
        } else {
    
            // 无需解释的话我们直接请求权限
    
            ActivityCompat.requestPermissions(thisActivity,
                    new String[]{Manifest.permission.READ_CONTACTS},
                    MY_PERMISSIONS_REQUEST_READ_CONTACTS);
        }
    }
    

    ####下面在demo中验证完整流程:

    首先我写两个方法,分别是读取手机状态和打电话,分别在清单文件中声明相关权限:

    /**
         * 读取电话状态
         * 须 READ_PHONE_STATE
         */
        private void getPhoneStatus() {
            TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
            if (tm == null) {
                return;
            }
            String number = "phone " + tm.getLine1Number() + "\nime" + tm.getImei() + "\nsimSerialNumber" + tm.getSimSerialNumber();
            Snackbar.make(view, number, Snackbar.LENGTH_SHORT).show();
        }
    
        /**
         * 直接打电话
         * 须 CALL_PHONE
         */
        private void makeCall() {
            Uri uri = Uri.parse("tel:10086");
            Intent callIntent = new Intent(Intent.ACTION_CALL, uri);
            startActivity(callIntent);
        }
    

    然后添加两个按钮,分别调用这个两个方法,不同的是,我在打调用打电话的时候,添加了权限检查与请求的代码:

       @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
    
            view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getPhoneStatus();
                }
            });
    
            view.findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (checkAndRequestCallPermission()) {
                        makeCall();
                    }
                }
            });
        }
       @TargetApi(M)
        private boolean checkAndRequestCallPermission() {
            if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
    
                if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.CALL_PHONE)) {
                    showExplainDialog();
                } else {
                    requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_PERMISSION_CALL);
                }
    
                return false;
            }
            return true;
        }
    
        private void showExplainDialog() {
            new AlertDialog.Builder(getActivity())
                    .setTitle("权限解释")
                    .setMessage("因为你拒绝了,所以我们无法帮你联系移动客服了,请你点击授予")
                    .setPositiveButton("我知道了", null)
                    .setOnDismissListener(new DialogInterface.OnDismissListener() {
                        @Override
                        public void onDismiss(DialogInterface dialog) {
                            requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_PERMISSION_CALL);
                        }
                    }).create().show();
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode != REQUEST_CODE_PERMISSION_CALL) {
                return;
            }
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getActivity(), "授权失败", Toast.LENGTH_SHORT).show();
            } else {
                makeCall();
            }
        }
    

    我们来看看效果:
    这里写图片描述
    首先,我在未请求权限的情形下,去读取电话状态,在新的权限系统模型下,直接抛出异常,第二次,我去请求电话,就弹出了我们的权限授予弹框,在我们点击授予后,在回调中我们在执行拨打电话的操作(注意看 有Toast),如果我们再次点击按钮,则检查方法返回true,我们直接就拨打成功,
    此时,我们再次点击之前导致崩溃的读取读取电话状态,直接就可以读取,这里也就验证了我们之前讲到的一点,权限分组之下,一旦某一个危险权限被授予,则请求该权限组下的任意一项权限系统会立即授予。
    当然这个只是实例,实际开发当然不能这么写,还是按照谷歌的意思,按需申请,用到拍照的时候就相机,用到短信的时候,再申请短信。这样才能保证用户的目的与我们的目的一致。

    现在我把应用删掉再装一次,试试拒绝的流程:
    这里写图片描述
    当我们第一次拒绝的时候,我们调用是否给一个解释给用户的方法返回true,此时我们自定义弹框弹出,提示用户为什么要申请这项权限,当弹框消失的时候,告知行为结束,我们再次请求权限,出现系统级弹框,如果此时我选择了不再提示并且拒绝的话,直接授权失败,以后调用request 就会直接回调授权失败。
    此时我再回到设置把权限开启后又关闭,再次点击,又会回到第一次请求弹框拒绝之后的流程了,至此,新的权限系统下的请求获取流程梳理完成,目前在部分尝试定制系统下,可能会有所差异,但大致流程一致。

    官网流程描述

    解决方案:

    这也许是一句话Android权限适配的更优解决方案
    SoulPermission

    兼容性探讨:

    基于运行环境environment(当前Android手机)和 当前应用tragetSdk 版本 ,我们可细分四种状态:

    • environment> 23 tragetSdk >=23
    • environment> 23 tragetSdk <23
    • environment< 23 tragetSdk >=23
    • environment< 23 tragetSdk <23

    我们上面流程展示了运行时权限,第一种情况的流程,我们现在同样的手机,我吧应用打targetSdk 换成22试试效果:

    这里写图片描述

    我们可以直接拨打电话,进入权限管理界面试图关闭权限,会提示,此项权限为旧版打造,关闭可能无法正常使用,所以我们再次拒绝以后,点击电话,也无任何响应。
    至此我们可以分析为,只有当运行环境和app target版本同时满足时,我们才能走到运行时环境的流程,否则都是我们老的授权方式,所以,我们在执行流程的时候,可以先用这样一个方法判断:

     /**
         * 是否是老的权限系统
         */
        public static boolean isOldPermissionSystem(Context context) {
            int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
            return android.os.Build.VERSION.SDK_INT < M || targetSdkVersion < M;
        }
    

    所以,在老的版本里面权限是直接授予的(当然国内有些厂商,也在这基础上做了自己的权限系统,我也能同过相关的方式拿到权限状态,今天这里不做讨论),我们直接调用,在新的版本里面,我们再走我们的权限申请流程即可。


    我这边写了一个一句话权限库,一行代码涵盖了权限请求、版本判断等功能:

    传送门
    展开全文
  • 在Android M中权限系统被重新设计,发生了颠覆性的变化,很多人把握...这里有一切关于Android运行时权限你需要知道的,包括如何在代码中实现,如果你以前不知道这些东西,现在我将在详解之后给你一个最佳的实践方案。
  • Android运行时权限

    2017-08-03 19:02:24
    Android运行时权限 危险权限和危险权限组 原生API申请及封装 EasyPermission申请 PermissionsDispatcher申请
  • Android6.0运行时权限

    2017-06-17 16:01:41
    一、运行时权限所谓运行时权限,指的是在Android6.0及以上版本中,在app运行时才请求相关权限,从而让每项权限都在用户知情的情况下被授权(当然用户可以拒绝)。而不同于Android6.0之前在安装app告知用户获取全部...
  • 随着Android6.0的发布,也带来了一些变革,这里主要说的就是在Android6.0里面运行时权限的处理。 关于运行时权限 在旧的权限管理系统中,权限仅仅在App安装询问用户一次,用户同意了这些权限App才能被安装...
  • 本节课中会对Android 6.0系统中新增的运行时权限功能进行一次透彻的讲解,包含运行时权限的引入背景、基本用法、佳封装实践等,还会讲一些很多人并不知道的运行时权限微技巧。
  • Android6.0运行时权限解析,RxPermissions的使用,自己封装一套权限框架在Android6.0中,新增加了一个运行时权限,我相信很多人都已经知道了,估计也知道怎么用了,这篇博客很简单,就是告诉大家如何去申请运行时...
  • 运行时权限运行时权限是 Android 6.0 (SDK 23)新特性,更好的保护了用户的隐私。如果你build.gradle文件中声明targetSdkVersion为23及以上: defaultConfig { applicationId "xxxx"//you applicationId
  • 引用郭霖对android运行时权限的总结,一共9组24个权限,只有在android6.0 及以上的android机上使用到这些权限的时候,才需要进行运行时权限处理. 使用场景 前提:应用运行到android 6.0 及以上...
  • 查看日志后也确是如此,为此就记录一下关于6.0(api 23)之后的运行时权限的获取问题。虽然这个很早就有了,但是仍有部分应用其实还是没有对此做处理的。本篇是以一个开源库PermissionsDispatcher的使用来写这篇学
  • 于是,Android6.0中出现了运行时权限的概念。许多程序员前赴后继,推出了大量的优秀的第三方库,来简化运行时权限的使用。但是,我觉得我们有必要从根本上学会使用运行时权限的申请,这能增加我们对Permission的理解...
  • 转载请标明出处: ...随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个主要的变化,查看查看官网的这篇文章http://develope
  • 运行时权限 Android6.0以后Google不再允许开发者直接或许应用的权限,需要在用户知情的情况下授予权限,即需要用到权限的时候弹出一个权限提示框。 接下来我们将使用三种方式,完成运行时权限的申请: 自己...
  • 前言:像默认授予预制应用这个方法还是不好百度到的,用到的人不多,自己总结下最近的学习成果,方便...该模式将需要危险权限(请参阅受影响的权限)的 Android 应用从安装时权限模式转移至运行时权限模式:安装权...
  • 问题背景随着今年Android6.0系统的国产ROM终于来了,适配6.0已经势在必行了。最近也有很多开发者在微信私聊和咨询我遇到...如果你在Android6.0系统的手机上如果遇到了这样的错误,必定跟其运行时权限有关。open failed:
1 2 3 4 5 ... 20
收藏数 996,642
精华内容 398,656
关键字:

运行时权限