从Android 6.0开始,权限不再是在manifest文件中粘贴一下即可,这时候权限也正式走进大家的视野。项目的6.0适配就是我做的,当时没有仔细总结,最近在另一个项目添加权限的时候发现,同一个功能都没有添加申请权限的代码,一个会挂一个不会,花了几个小时在这个小问题上。因此多花点时间总结一下权限问题。

  1. Android系统权限的概念

Android是一个权限分隔的操作系统,每个应用都有独特的系统标识。一般情况下,应用没有权限执行对其它应用、系统、用户可能有不利影响的操作。每个应用都在应用沙盒中运行,因此当应用需要使用沙盒未提供的功能时,需要申请权限,比如读写sd卡、访问网络、访问其它应用的数据、读写联系人、调用摄像头等。

权限在AndroidManifest.xml文件中声明,Android 6.0以前,有的APP一股脑声明了各种各样的权限,用户可能没有细看就安装了,于是这些APP就可以为所欲为,偷鸡摸狗,无法无天。Android 6.0把权限分成正常权限和危险权限,AndroidManifest中声明的正常权限系统会自动授予,而危险权限则需要在使用的时候用户明确授予。

换句话说,就是Android 6.0以上的系统在第一次使用危险权限的时候,需要向用户申请,征得用户的同意。如果还是在没有权限的情况下执行操作,就会获得Crash大礼包,错误日志为java.lang.SecurityException: Permission Denial。因此,应用对危险权限的申请,需要相应的处理。

  1. 危险权限和对应的权限组

危险权限都属于权限组,应用在向用户申请危险权限时,系统会弹对话框,描述应用要访问的权限组,这时候用户如果同意授权,则权限组包含的所有权限都会被系统授予。比如,应用被授予READ_EXTERNAL_STORAGE权限之后,如果再申请WRITE_EXTERNAL_STORAGE权限,系统会立即授予该权限。

危险权限表格如下

权限组权限
CALENDARREAD_CALENDAR

WRITE_CALENDAR
CAMERACAMERA
CONTACTSREAD_CONTACTS

WRITE_CONTACTS

GET_ACCOUNTS
LOCATIONACCESS_FINE_LOCATION

ACCESS_COARSE_LOCATION
MICROPHONERECORD_AUDIO
PHONEREAD_PHONE_STATE

CALL_PHONE

READ_CALL_LOG

WRITE_CALL_LOG

ADD_VOICEMAIL

USE_SIP

PROCESS_OUTGOING_CALLS
SENSORSBODY_SENSORS
SMSSEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVE_WAP_PUSH

RECEIVE_MMS
STORAGEREAD_EXTERNAL_STORAGE

WRITE_EXTERNAL_STORAGE
  1. 申请权限的正确姿势
  • 需要申请的所有权限在AndroidManifest文件中声明
        
  • 使用时检查权限,没有权限则申请
        //使用兼容库就无需判断系统版本        int hasWriteStoragePermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);        if (hasWriteStoragePermission == PackageManager.PERMISSION_GRANTED) {                        //拥有权限,执行操作            initScan();        }else{                        //没有权限,向用户请求权限            ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MyApplication.CODE_FOR_WRITE_PERMISSION);        }
  • 覆写onRequestPermissionsResult方法
    @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {                //通过requestCode来识别是否同一个请求        if (requestCode == CODE_FOR_WRITE_PERMISSION){            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){                //用户同意,执行操作                initScan();            }else{                //用户不同意,向用户展示该权限作用                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {                    new AlertDialog.Builder(thisActivity)                            .setMessage(R.string.storage_permissions_remind)                            .setPositiveButton("OK", (dialog1, which) ->                                    ActivityCompat.requestPermissions(this,                                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                                            EventConstConfig.CODE_FOR_CAMERA_PERMISSION))                            .setNegativeButton("Cancel", null)                            .create()                            .show();                }            }        }    }

shouldShowRequestPermissionRationale方法返回值分几种情况,怎么使用看应用的具体交互需求。

  1. 第一次请求该权限,返回false。
  2. 请求过该权限并被用户拒绝,返回true。
  3. 请求过该权限,但用户拒绝的时候勾选不再提醒,返回false。
  1. 申请权限的几个小细节
  • 使用兼容库
    checkSelfPermission、requestPermissions等几个权限相关的方法用v4包里的可以兼容6.0以下版本,否则需要包一层版本判断。

  • targetSDKVersion的问题
    我遇到的问题就是这个,有个细节没注意到。Android系统触发动态申请权限的条件其实有两个,设备系统版本在Android 6.0以上并且targetSDKVersion>=23。因此其实在targetSDKVersion版本小于23的情况下,即使在6.0以上的设备运行也不会挂,但会在安装时列出所有权限,同6.0以下的设备。官方建议保持targetSDKVersion在最新的版本。

  1. 使用第三方库easypermissions
    申请权限的第三方库有很多,但没遇到特别简洁的,也就没有特地去研究和使用,避免导入太多第三方库,easypermissions这个库是其中一个项目用到的,这里简单的提一下。
  • 导入easypermissions
compile 'pub.devrel:easypermissions:0.1.9'
  • 检查权限,没有就进行申请
        if (!EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {            EasyPermissions.requestPermissions(this, getString(R.string.camera_peemission_tip), CAMERA_REQUEST_CODE, Manifest.permission.CAMERA);        }
  • 覆写方法
    @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        // Forward results to EasyPermissions        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);    }    @Override    public void onPermissionsGranted(int requestCode, List perms) {//        light();    }    @Override    public void onPermissionsDenied(int requestCode, List perms) {        Dialog dialog = new AlertDialog.Builder(this, R.style.MyAlertDialogStyle)                .setTitle(R.string.tips)                .setMessage(R.string.camera_peemission_tip)                .setPositiveButton(R.string.to_open, (dialog2, which) -> {                    startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS));                })                .setNegativeButton(R.string.cancel, (dialog3, which) -> {                    dialog3.dismiss();                })                .create();        dialog.show();    }

更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. android内置闹铃功能的简单介绍
  3. Android(安卓)SDK开发指南(翻译)系列一:最佳实践(二)-- 反应速度设
  4. Android所有系统资源图标android.R.drawable.xxx查看(纯java)
  5. Android(安卓)双击返回键退出程序的方法总结
  6. Android自带的toolbox分析及扩展
  7. Android(安卓)系统 root 破解原理分析
  8. Android(安卓)7.0 Nougat 不得不知的 11 项新功能
  9. Android打开自启动设置页面

随机推荐

  1. android的EventBus模式 解决各种handler,a
  2. android动态生成表格,使用的是TABLELAYOUT
  3. 编译最小的Android x86 image
  4. 一个hello程序的android内核模块编译方法
  5. Android中用GridView实现九宫格
  6. 浅析android中的Bundle类
  7. Android碎片化机制
  8. 在ubuntu下搭建Android的开发环境
  9. 技术/源码/开源网站
  10. Android培训班(18)