首先扯点别的,听说这个周末是好天气,想约她一起去公园赏赏梅花,只有我自己估计她也不一定去啊,哈哈。

在android6.0及以上系统,Android在安装一个应用的时候不再需要列出一大堆权限,让用户点击同意以后才可以安装。Instead, 当应用在运行的时候,如果要使用危险权限的时候需要明确告知用户并得到用户许可后方可进行一些Android系统认为是危险行为的操作。另外当你在程序运行过程中开启了某些权限,用户后来还是可以在应用设置里面关闭这些权限。

Android 系统危险权限列表:权限是分组的,如果一个权限组中的任意一个权限被允许了,同组的其他权限也会被允许。

权限组

权限

CALENDAR

READ_CALENDAR

CALENDAR

WRITE_CALENDAR

CAMERA

CAMERA

CONTACTS

READ_CONTACTS

CONTACTS

WRITE_CONTACTS

CONTACTS

GET_ACCOUNTS

LOCATION

ACCESS_FINE_LOCATION

LOCATION

ACCESS_COARSE_LOCATION

MICROPHONE

RECORD_AUDIO

PHONE

READ_PHONE_STATE

PHONE

CALL_PHONE

PHONE

READ_CALL_LOG

PHONE

WRITE_CALL_LOG

PHONE

ADD_VOICEMAIL

PHONE

USE_SIP

PHONE

PROCESS_OUTGOING_CALLS

SENSORS

BODY_SENSORS

SMS

SEND_SMS

SMS

RECEIVE_SMS

SMS

READ_SMS

SMS

RECEIVE_WAP_PUSH

SMS

RECEIVE_MMS

STORAGE

READ_EXTERNAL_STORAGE

STORAGE

WRITE_EXTERNAL_STORAGE

以调用系统相机拍照为例了解Android6.0的运行时权限,使用android studio 自带的模拟器,API 选择24.

调用系统相机进行拍照,如果想保存全尺寸的大图的时候(我们在系统公共存储目录DCIM下新建一个文件夹用来保存我们拍摄的图片),需要传递一个路径给系统相机用来存储拍摄的图片。

//拍照的代码
 private void takePhoto() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            //创建一个File
            photoFile = ImageUtil.createImageFile();
            if (photoFile != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    //如果是7.0及以上的系统使用FileProvider的方式创建一个Uri
                    Log.e(TAG, "Build.VERSION.SDK_INT >= Build.VERSION_CODES.N");
                    photoURI = FileProvider.getUriForFile(this, "com.hm.camerademo.fileprovider", photoFile);
                    takePictureIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    takePictureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                } else {
                    //7.0以下使用这种方式创建一个Uri
                    photoURI = Uri.fromFile(photoFile);
                }
                //将Uri传递给系统相机
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, TAKE_PHOTO);
            }
        }
    }

因为需要创建一个File 用来保存拍摄的图片,这就需要有WRITE_EXTERNAL_STORAGE的权限,所以我们在调用takePhoto()这段代码之前就应该先检查我们的App是否已经有了WRITE_EXTERNAL_STORAGE权限,如果没有,需要先申请,申请成功后再调用takePhoto()进行拍照。

1:首先:在AndroidManifest.xml 中声明我们需要的权限

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

2:调用ContextCompat.checkSelfPermission(Context context, String permission)检查是否有WRITE_EXTERNAL_STORAGE权限,如果没有就申请权限,否则直接拍照.

if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                //申请权限,REQUEST_TAKE_PHOTO_PERMISSION是自定义的常量
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_TAKE_PHOTO_PERMISSION);
            }
        } else {
            //有权限,直接拍照
            takePhoto();
        }

3:重写onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults)方法,检查权限申请是否成功。

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_TAKE_PHOTO_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //申请成功,可以拍照
                takePhoto();
            } else {
                Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

以上三步就是最简单的流程。

Android 获取相机系统权限被拒绝 获取相机权限会怎么样_Android 获取相机系统权限被拒绝

从上边的效果图中可以看到,如果当动态申请权限的时候如果用户点击了ALLOW的话,权限申请就成功,然后就可以拍照了,一切顺利,那么当用户点击了DENY结果会是咋样呢?来试一试。

Android 获取相机系统权限被拒绝 获取相机权限会怎么样_android_02

可以看到,当我们第一次点击DENY的时候,请求权限失败,然后我们点击拍照,再次申请权限的时候,系统还是会弹出一个对话框让用户进行确认,如果这时候用户点击了ALLOW那么还是可以愉快的进行拍照的。如果用户点击了,DENY,但是没有勾选Don’t ask again 的话,下次拍照进行权限申请的时候还是会弹出这个对话框,如果这时候用户点击ALLOW 的话,权限就会申请成功,可以进行拍照。但是如果用户点击了DENY,并且勾选了Don’t ask again 这是会有什么效果呢?试一试

Android 获取相机系统权限被拒绝 获取相机权限会怎么样_6-0运行时-权限_03

从上面的截图中可以看到,如果用户点击了DENY 并且勾选了Don’t ask again,那么申请权限会一直失败,也就是说用户不能再使用相机功能了,那如果用户这时候又想使用相机功能怎么办?

方法1:用户可以自己去应用设置里面把相应的权限开启,然后再回来使用拍照功能,如下图所示

Android 获取相机系统权限被拒绝 获取相机权限会怎么样_6-0运行时-权限_04

方法2:如果用户点击了DENY 并且勾选了Don’t ask again,当用户再次点击拍照的时候,我们应该主动弹出一个对话框引导用户去应用设置里面开启相应的权限,效果如下

Android 获取相机系统权限被拒绝 获取相机权限会怎么样_6-0运行时-权限_05

接下来就实现这种效果:最重要的一点就是我们怎么知道用户拒绝了我们申请额权限并且勾选了 Don’t ask again。这时候就需要用到ActivityCompat的一个方法。

public static boolean shouldShowRequestPermissionRationale( Activity activity, String permission)

这个方法的意思是:例如你需要申请WRITE_EXTERNAL_STORAGE的权限,这个方法的返回值表示是否应该向用户解释为什么你需要你正在申请的权限。如果这个方法返回true,表示你应该给出用户一个合理的解释(海参炒面,海参呢?),返回false,你不需要提示用户。

所以我们必须得知道shouldShowRequestPermissionRationale何时返回true,何时返回false。下面我用文字叙述这个流程。
1.第一次请求权限的时候,返回false,如果用户直接ALLOW 了申请的权限,直接就和谐了,没什么可说的。

2.第一次请求权限的时候,返回false,用户选择了DENY,就不能拍照了。用户第二次申请权限,返回true,这时候系统的弹出的对话框会有一个checkbox,让你选择是否Don’t ask again,如果你选择了ALLOW,那还是比较和谐的,还是可以正常拍照。

3.第一次请求权限的时候,返回false,用户选择了DENY,就不能拍照了。用户第二次申请权限,返回true,这时候系统弹出的对话框会有一个checkbox,让你选择是否Don’t ask again,如果你没有勾选checkbox并选择了DENY。第三次请求权限,返回true,这时候系统弹出的对话框会有一个checkbox,让你选择是否Don’t ask again,如果你选择了DENY,并且勾选了Don’t ask again。第四次申请 ,返回false,系统不会再弹出对话框询问你了,而你也不能拍照了。

经过上面的叙述,我们主动弹出对话框让用户开启权限的条件是:上一次请求权限的时候shouldShowRequestPermissionRationale方法返回true,而本次请求权限的时候shouldShowRequestPermissionRationale返回false。所以我们得存储上一次请求权限的时候shouldShowRequestPermissionRationale的返回值flag和本次请求权限shouldShowRequestPermissionRationale的返回值nowFlag共同作用,当flag==true&&nowFlag==false的时候主动弹出对话框。

我们把上一次请求权限,shouldShowRequestPermissionRationale返回的结果存储在SharedPreferences中,

public class SpUtil {

    private static SharedPreferences hmSpref;
    private static SharedPreferences.Editor editor;
    private static SpUtil spUtil;
    private final String FLAG = "flag";

    private SpUtil() {
        hmSpref = App.getInstance().getSharedPreferences("hmSpref", Context.MODE_PRIVATE);
        editor = hmSpref.edit();
    }

    public static SpUtil getInstance() {
        if (spUtil == null) {
            synchronized (SpUtil.class) {
                if (spUtil == null) {
                    spUtil = new SpUtil();
                }
            }
        }
        return spUtil;
    }

    public void putFlag(boolean flag) {
        editor.putBoolean(FLAG, flag);
        editor.commit();
    }

    //第一次请求权限的时候,返回的flag是false
    public boolean getFlag() {
        return hmSpref.getBoolean(FLAG, false);
    }

}

修改申请权限的方法

private void takePhotoRequestPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        //判断是否需要主动弹出对话框
            if (SpUtil.getInstance().getFlag() &&
                    !ActivityCompat.shouldShowRequestPermissionRationale(this,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                           //满足条件弹出自定义dialog
                if (dialog == null) {
                    dialog = MyDialog.newInstance("相机故障", "需要开启读写数据权限才可以使用拍照功能");
                    dialog.setOnAllowClickListener(new MyDialog.OnAllowClickListener() {
                        @Override
                        public void onClick() {
                            //用户点击 GO SETTING 的时候跳转到应用设置界面
                            startAppSetting();
                        }
                    });
                }
                dialog.show(getSupportFragmentManager(), "dialog");
            } else {
 //保存 shouldShowRequestPermissionRationale的返回值  
 SpUtil.getInstance().putFlag(ActivityCompat.shouldShowRequestPermissionRationale(this,
 Manifest.permission.WRITE_EXTERNAL_STORAGE));
                //直接申请权限
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_TAKE_PHOTO_PERMISSION);
            }
        } else {
            takePhoto();
        }
    }
//使用startActivityForResult的方式启动应用设置界面
public void startAppSetting() {
        Intent in = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        in.setData(uri);
        startActivityForResult(in, REQUEST_TAKE_PHOTO_PERMISSION);
    }

重写onActivityResult 方法

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_TAKE_PHOTO_PERMISSION:
                //在这里再次检查权限进行拍照
                takePhotoRequestPermission();
                break;
            default:
                break;
        }
    }

结尾:流程基本就是这样,本文的目的旨在了解申请权限的流程,如果要是同时申请多个权限就有有点麻烦。如果要在项目中使用的,可以直接使用github上的一个开源的权限申请库,googlesamples/easypermissions
https://github.com/googlesamples/easypermissions

参考网址:
【1】https://translate.google.com.hk/?hl=zh-CN&tab=wT#zh-CN/zh-CN/Rationale

【2】https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en
【3】https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous

附录 Normal Permissions
只要在AndroidManifest.xml文件中声明了正常权限,在安装的时候权限就会开启,而且用户不能关闭权限。

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_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.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.GET_PACKAGE_SIZE
android.permission.INSTALL_SHORTCUT
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_IGNORE_BATTERY_OPTIMIZATIONS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_ALARM
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.TRANSMIT_IR
android.permission..UNINSTALL_SHORTCUT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS