Android6.0运行时权限

权限分为两大类:
Normal Permissions

Dangerous Permission

特点:
对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如是个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝(比如:单机的象棋对战,请求访问任何权限,我都是不同意的)。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。
通俗的说:涉及到比较隐私的权限的时不光要在清单文件中声明权限,还要在代码中动态的获取权限。系统会弹出一个对话框,提示用户。

Paste_Image.png

两种权限的大致内容如下:
Normal Permissions如下


ACCESS_NOTIFICATION_POLICY

ACCESS_WIFI_STATE

BLUETOOTH

BLUETOOTH_ADMIN

BROADCAST_STICKY

CHANGE_NETWORK_STATE

CHANGE_WIFI_MULTICAST_STATE

CHANGE_WIFI_STATE

DISABLE_KEYGUARD

EXPAND_STATUS_BAR

GET_PACKAGE_SIZE

INSTALL_SHORTCUT

INTERNET

KILL_BACKGROUND_PROCESSES

MODIFY_AUDIO_SETTINGS

NFC

READ_SYNC_SETTINGS

READ_SYNC_STATS

RECEIVE_BOOT_COMPLETED

REORDER_TASKS

REQUEST_INSTALL_PACKAGES

SET_ALARM

SET_TIME_ZONE

SET_WALLPAPER

SET_WALLPAPER_HINTS

TRANSMIT_IR

UNINSTALL_SHORTCUT

USE_FINGERPRINT

VIBRATE

WAKE_LOCK

WRITE_SYNC_SETTINGS

Dangerous Permissions:



    group:android.permission-group.CONTACTS           //联系人组

    permission:android.permission.WRITE_CONTACTS      //写入联系人

    permission:android.permission.GET_ACCOUNTS        //访问GMail账户列表

    permission:android.permission.READ_CONTACTS        //读取联系人



    group:android.permission-group.PHONE                //手机状态组

    permission:android.permission.READ_CALL_LOG        //读取通话记录

    permission:android.permission.READ_PHONE_STATE     //手机状态 如imei等

    permission:android.permission.CALL_PHONE           //拨打电话

    permission:android.permission.WRITE_CALL_LOG       //修改通话记录

    permission:android.permission.USE_SIP              //允许程序使用SIP视频服务

    permission:android.permission.PROCESS_OUTGOING_CALLS      // 允许监听、控制、取消呼出电话的权限

    permission:com.android.voicemail.permission.ADD_VOICEMAIL        //



    group:android.permission-group.CALENDAR           //日历组

    permission:android.permission.READ_CALENDAR       //读取日历

    permission:android.permission.WRITE_CALENDAR      //写入日历



    group:android.permission-group.CAMERA        //摄像头组

    permission:android.permission.CAMERA        //调用摄像头



    group:android.permission-group.SENSORS        //传感器组

    permission:android.permission.BODY_SENSORS    //传感器



    group:android.permission-group.LOCATION               //位置组

    permission:android.permission.ACCESS_FINE_LOCATION    //通过GPS定位 粗劣位置

    permission:android.permission.ACCESS_COARSE_LOCATION   //通过wifi定位 精细位置



    group:android.permission-group.STORAGE                     //内存卡组

    permission:android.permission.READ_EXTERNAL_STORAGE        //读取内存卡

    permission:android.permission.WRITE_EXTERNAL_STORAGE       //写入内存卡



    group:android.permission-group.MICROPHONE        //麦克风组

    permission:android.permission.RECORD_AUDIO        //使用麦克风 录音



    group:android.permission-group.SMS             //短息那组

    permission:android.permission.READ_SMS         //读取短信

    permission:android.permission.RECEIVE_WAP_PUSH //接收WAP PUSH信息

    permission:android.permission.RECEIVE_MMS        //允许处理、监控、接受彩信的权限

    permission:android.permission.RECEIVE_SMS        // 允许处理、监控、接受短信的权限

    permission:android.permission.SEND_SMS          //发送短信

    permission:android.permission.READ_CELL_BROADCASTS //获取小区广播

可以通过adb shell pm list permissions -d -g
进行查看。

授权的机制:
如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。比如你的app对READ_CONTACTS
已经授权了,当你的app申请WRITE_CONTACTS
时,系统会直接授权通过。此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。
不过需要注意的是,不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。

具体的操作
1.检查是否已经开启的这个权限

 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            //1.先判断是否已经有权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
                //已经有了权限

            } else {
                //没有权限,去申请全选 100是申请全选的请求码
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 100);
            }
        }

2.申请权限

//申请权限.参数:1.上下文2.申请的权限字符串数组,请求码

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

3.开启了对话框。我们要知道对话框是被点击了允许还是拒绝。需要用到申请权限的回调

 /**
     * 申请全选返回,这里可以知道用户是否拒绝了申请
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case 100: //100是上面申请权限的申请码
                if (grantResults.length > 0 && grantResults[0] == PackageManager
                        .PERMISSION_GRANTED) {
                    //用户允许权限

                } else {
                    //用户拒绝权限
                    
                }
                break;
        }
    }

对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了。

有个API值得提一下:

// 是否应该显示解释
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
       Manifest.permission.PERMISSION_GRANTED)) {
           //用户没有点击不在询问
       }
}else{
          //用户点击不在询问,这里无法开启弹窗,应该引导用户去设置开启全权限
  
}

例子很简单,不过需要注意的是,对于Intent这种方式,很多情况下是不需要授权的甚至权限都不需要的,比如:你是到拨号界面而不是直接拨打电话,就不需要去申请权限;打开系统图库去选择照片;调用系统相机app去牌照等。更多请参考Consider Using an Intent

这里封装了一个工具类

package com.example.yeqiu.test;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;

import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;


import java.util.Arrays;
import java.util.List;


public class PermissionUtils {

    private static final int REQUEST_PERMISSION_CODE = 1000;

    private Object mContext;

    private PermissionListener mListener;

    private List<String> mPermissionList;

    public PermissionUtils(@NonNull Context context){
        checkCallingObjectSuitability(context);
        this.mContext = context;

    }


    /**
     * 权限授权申请
     * @param hintMessage
     *              要申请的权限的提示
     * @param permissions
     *              要申请的权限
     * @param listener
     *              申请成功之后的callback
     */
    public void requestPermissions(@NonNull CharSequence hintMessage,
                                   @Nullable PermissionListener listener,
                                   @NonNull final String... permissions){

        if(listener != null){
            mListener = listener;
        }

        mPermissionList = Arrays.asList(permissions);

        //没全部权限
        if (!hasPermissions(mContext, permissions)) {

            //需要向用户解释为什么申请这个权限
            boolean shouldShowRationale = false;
            for (String perm : permissions) {
                shouldShowRationale =
                        shouldShowRationale || shouldShowRequestPermissionRationale(mContext, perm);
            }

            if (shouldShowRationale) {
                showMessageOKCancel(hintMessage, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        executePermissionsRequest(mContext, permissions,
                                REQUEST_PERMISSION_CODE);

                    }
                });
            }else {
                executePermissionsRequest(mContext, permissions,
                        REQUEST_PERMISSION_CODE);
            }
        }else if(mListener != null) { //有全部权限
            mListener.doAfterGrand(permissions);
        }
    }

    /**
     * 处理onRequestPermissionsResult
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    public void handleRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_PERMISSION_CODE:
                boolean allGranted = true;
                for (int grant: grantResults) {
                    if(grant != PackageManager.PERMISSION_GRANTED){
                        allGranted = false;
                        break;
                    }
                }

                if (allGranted && mListener != null) {

                    mListener.doAfterGrand((String[])mPermissionList.toArray());

                }else if(!allGranted && mListener != null){
                    mListener.doAfterDenied((String[])mPermissionList.toArray());
                }
                break;
        }
    }

    /**
     * 判断是否具有某权限
     * @param object
     * @param perms
     * @return
     */
    public static boolean hasPermissions(@NonNull Object object, @NonNull String... perms) {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }

        for (String perm : perms) {
            boolean hasPerm = (ContextCompat.checkSelfPermission(getActivity(object), perm) ==
                    PackageManager.PERMISSION_GRANTED);
            if (!hasPerm) {
                return false;
            }
        }

        return true;
    }


    /**
     * 兼容fragment
     * @param object
     * @param perm
     * @return
     */
    @TargetApi(23)
    private static boolean shouldShowRequestPermissionRationale(@NonNull Object object, @NonNull String perm) {
        if (object instanceof Activity) {
            return ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, perm);
        } else if (object instanceof Fragment) {
            return ((Fragment) object).shouldShowRequestPermissionRationale(perm);
        } else if (object instanceof android.app.Fragment) {
            return ((android.app.Fragment) object).shouldShowRequestPermissionRationale(perm);
        } else {
            return false;
        }
    }

    /**
     * 执行申请,兼容fragment
     * @param object
     * @param perms
     * @param requestCode
     */
    @TargetApi(23)
    private void executePermissionsRequest(@NonNull Object object, @NonNull String[] perms, int requestCode) {
        if (object instanceof android.app.Activity) {
            ActivityCompat.requestPermissions((Activity) object, perms, requestCode);
        } else if (object instanceof android.support.v4.app.Fragment) {
            ((android.support.v4.app.Fragment) object).requestPermissions(perms, requestCode);
        } else if (object instanceof android.app.Fragment) {
            ((android.app.Fragment) object).requestPermissions(perms, requestCode);
        }
    }

    /**
     * 检查传递Context是否合法
     * @param object
     */
    private void checkCallingObjectSuitability(@Nullable Object object) {
        if (object == null) {
            throw new NullPointerException("传入的context是null");
        }

        boolean isActivity = object instanceof android.app.Activity;
        boolean isSupportFragment = object instanceof android.support.v4.app.Fragment;
        boolean isAppFragment = object instanceof android.app.Fragment;
        if (!(isSupportFragment || isActivity || (isAppFragment && isNeedRequest()))) {
            if (isAppFragment) {
                throw new IllegalArgumentException(
                        "Target SDK needs to be greater than 23 if caller is android.app.Fragment");
            } else {
                throw new IllegalArgumentException("Caller must be an Activity or a Fragment.");
            }
        }
    }


    @TargetApi(11)
    private static Activity getActivity(@NonNull Object object) {
        if (object instanceof Activity) {
            return ((Activity) object);
        } else if (object instanceof android.support.v4.app.Fragment) {
            return ((android.support.v4.app.Fragment) object).getActivity();
        } else if (object instanceof android.app.Fragment) {
            return ((android.app.Fragment) object).getActivity();
        } else {
            return null;
        }
    }

    public static boolean isNeedRequest(){
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    public void showMessageOKCancel(CharSequence message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(getActivity(mContext))
                .setMessage(message)
                .setPositiveButton("确定", okListener)
                .setNegativeButton("取消", null)
                .create()
                .show();
    }

    public interface PermissionListener {

        void doAfterGrand(String... permission);

        void doAfterDenied(String... permission);
    }
}



使用方式:

在activity或者fragment中需要权限的地方使用

 PermissionUtils = new PermissionUtils(MainActivity.this);
                PermissionUtils.requestPermissions("请授予xx[相机],[读写]权限!",
                        new PermissionUtils.PermissionListener() {
                            @Override
                            public void doAfterGrand(String... permission) {

                            }

                            @Override
                            public void doAfterDenied(String... permission) {

                            }
                        }, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE);

            }

在权限回调里交给PermissionUtils处理

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

相关链接:http://blog.csdn.net/lmj623565791/article/details/50709663

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 之前调试的时候,出现了一个问题,就是当我打开二维码扫描界面的时候,对于一部分手机一直不会出现那个扫描框,这点我也很...
    f2a928cacb8d阅读 702评论 3 3
  • 发热放入Android6.0带来了新的权限管理方式,根据提供的官方文档,再加上自己的理解,做了以下汇总仅供大家伙参...
    PapiAP阅读 1,660评论 0 6
  • 前言 android 6.0加入了运行时权限功能后,目前通用的解决方案大体可分为2种: 在需要使用权限的地方即时申...
    kaxi4it阅读 993评论 0 12
  • 或许每个少女心里都住着一个何以琛,他高大帅气,他英俊潇洒,他有着一副孤傲的脸庞,他有着一副伟岸的身躯,冷冰冰的外表...
    左岸初雪阅读 1,045评论 14 8
  • 第一性原理原是埃隆马斯克所提出的,被硅谷追捧现在在国内被追捧,但我认为其实没有必要神化,它只是一种思考的方式,这种...
    飞马大定理阅读 977评论 2 3