完美解决Android 6.0+运行时权限问题

如需查看具体项目例子,可以去各大应用市场下载“萌萌鸡”app。体验功能!

android 6.0之前,我们的App需要权限,只需在manifest中申明即可,用户安装后,完全不用管权限什事情。但是android 6.0出现以后,把权限管理做了加强处理,隐私更加强,在manifest申明了,在使用到相关功能时,还需重新授权方可使用,但也不是所有的权限都需要判断,因为分了普通权限和危险权限。相信大家对权限的分类有过一点了解,这里就不详细解析了

两个建议:
1.严肃对待新权限事件,因为很多细节需要处理
2.如果你代码没支持新权限,不要设置targetSdkVersion 23 ,因为可以节省大把时间。这篇文章就不需要看了

我之前就是设置targetSdkVersion 19 面对6.0的系统,毫无压力, 后来新的SDK 出现了很多新的功能,而且网络请求也是从httpclient换成了okHttp, 必须targetSdkVersion 要大于23 ,以及为了与时俱进,换成了23,那么就要严肃对待这个问题了。

我们直接来写代码:创建一个基类,BaseActivity 需要继承 AppCompatActivity 而不能继承Activity, AppCompatActivity是在appcompat-v7中,如果app里面没有appcompat-v7 ,可以在 项目build.gradle文件中添加引用
compile 'com.android.support:appcompat-v7:23.1.1' 版本当然越高越好

Activity相信大家都会封装一个baseActivity ,在里面添加以下判断权限的方法

    /**
     * 判断是否是6.0以上的系统.很多的权限都不能自动或者提示开启. 功能可能需要多个权限,需要遍历判断
     * 
     * 萌萌鸡APP需要权限判断的地方(首页图灵聊天,个人中心AR扫描,修改头像, 设置祝福语音,视频,设置手机号码通讯录,AR,)
     *
     * @param dataPermission 需要的权限 ,数组
     * @return
     */
    public  void selfPermissionGranted(Context context, PermissionCallback runnable, String[] dataPermission) {
        MyLog.i(TAG, "selfPermissionGranted");
        this.permissionRunnable = runnable;
        int targetSdkVersion = 0;
        try {
            final PackageInfo info = context.getPackageManager().getPackageInfo(getPackageName(), 0);
            targetSdkVersion = info.applicationInfo.targetSdkVersion;//获取应用的Target版本
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        boolean resultAll = true;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//Build.VERSION.SDK_INT是获取当前手机版本   Build.VERSION_CODES.M为6.0系统
            MyLog.i(TAG, "Build.VERSION.SDK_INT=" + Build.VERSION.SDK_INT);
            MyLog.i(TAG, "targetSdkVersion=" + targetSdkVersion);
            MyLog.i(TAG, "Build.VERSION_CODES.M=" + Build.VERSION_CODES.M);
            //如果系统>=6.0
            if (targetSdkVersion >= Build.VERSION_CODES.M && checkPermissionGranted(context,dataPermission)) {//如果有权限
                if (permissionRunnable != null) {
                    permissionRunnable.hasPermission();
                    permissionRunnable = null;
                }
            } else {
                MyLog.i(TAG, "!checkPermissionGranted");
   //如果没有权限,请求
//判断用户是否已经拒绝过,直接去请求权限,而不管用户是否勾选了"不再询问",我们需要用户开启权限,没有权限就没有办法玩了
//      if (context.shouldShowRequestPermissionRationale(dataPermission[i])) {
//        context.requestPermissions(dataPermission, Constans.REQUEST_PERMISSION);//
//        MyLog.i(TAG, i + "+shouldShowRequestPermissionRationale=true" );//(1)只有"不在询问"才会到这一步
//    } else {
//        MyLog.i(TAG, i + "+shouldShowRequestPermissionRationale=false" );//(1)第一次原生进入 false (2)拒绝后,再次进入,还是提示false
//        isPermissionType(context, dataPermission[i]);
//    }
                requestPermissions(dataPermission, Constans.REQUEST_PERMISSION);
            }
        } else {
            MyLog.i(TAG, "Build.VERSION.SDK_INT<6.0");
            if (permissionRunnable != null) {
                permissionRunnable.hasPermission();
                permissionRunnable = null;
            }
        }
    }

/**
 *创建一个回调,方便判断权限的处理
 */
private PermissionCallback permissionRunnable;
public interface PermissionCallback {
    void hasPermission();

    void noPermission();
}

/**
 * 检测是否开启了权限  只要有一个权限没有打开,就返回false
 *
 * @param permissions
 * @return
 */
@TargetApi(Build.VERSION_CODES.M)//这里需要用23
public boolean checkPermissionGranted(Context context, String[] permissions) {
    MyLog.i(TAG, "checkPermissionGranted");
    boolean flag = true;
    for (String p : permissions) {
        MyLog.i(TAG, "permissions=" + p.toString());
        if (context.checkSelfPermission(p) != PackageManager.PERMISSION_GRANTED) {
            flag = false;
            break;
        }
    }
    return flag;
}


/**
 * 是否已经验证了权限
 *
 * @param grantResults
 * @return
 */
public boolean verifyPermissions(int[] grantResults) {
    // At least one result must be checked.
    if (grantResults.length < 1) {
        return false;
    }
    // Verify that each required permission has been granted, otherwise return false.
    for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}
/**
 * 检测权限的回调,是否开启了权限
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (requestCode == Constans.REQUEST_PERMISSION) {
        for (int i = 0; i < permissions.length; i++) {//可能需要多个权限,需要遍历判断
            MyLog.i(TAG, "Permission=i=" + permissions[i] + "grantResults=" + grantResults.length);

            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {//如果点击开启权限
                if (verifyPermissions(grantResults))
                    if (permissionRunnable != null) {
                        permissionRunnable.hasPermission();
                        permissionRunnable = null;
                    }
            } else {
                MyLog.i(TAG, "Permission=" + permissions[i]);
                isPermissionType(this, permissions[i]);//我在回调的地方,弹出自定义对话框,引导用户去开启权限
                break;
            }
        }
    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
/**
 * 权限的提示,不同权限的提示不同
 *
 * @param context
 * @param permission
 */
public void isPermissionType(Activity context, String permission) {
    switch (permission) {
        case "android.permission.CAMERA"://相机权限
            showPermissionDialogs(context, context.getResources().getString(R.string.hint_camera_only), false);
            break;
        case "android.permission.WRITE_EXTERNAL_STORAGE"://写入sd卡权限
            Tools.showPermissionDialogs(context, context.getResources().getString(R.string.hint_sd_write), false);
            break;
        case "android.permission.RECORD_AUDIO"://录音权限
            Tools.showPermissionDialogs(context, context.getResources().getString(R.string.hint_record), false);
            break;
        case "android.permission.READ_CONTACTS"://通讯录权限
            Tools.showPermissionDialogs(context, context.getResources().getString(R.string.hint_contacts_only), false);
            break;
        default:
            break;
    }
}

/**
 * 开启应用权限打开提示对话框
 *
 * @param permissionHint   权限的提示文字
 * @param isFinishActivity 点击"确定""取消"的时候,是否finish当前页面
 */
public void showPermissionDialogs(final Activity context, String permissionHint, final boolean isFinishActivity) {
    final Dialog dialog = new Dialog(context, R.style.dialog);
    if (!dialog.isShowing()) {
        dialog.show();
    }
    MyLog.i(TAG, "show_dialog");
    dialog.setCanceledOnTouchOutside(false);// 设置点击屏幕Dialog不消失
    View localView = LayoutInflater.from(context).inflate(
            R.layout.dialog_hint_camera, null);
    dialog.setContentView(localView);
    TextView tvPermissionHint = (TextView) localView.findViewById(R.id.tvPermissionHint);
    TextView tvPermissionSure = (TextView) localView.findViewById(R.id.tvPermissionSure);
    TextView tvPermissionCancel = (TextView) localView.findViewById(R.id.tvPermissionCancel);
    tvPermissionHint.setText(permissionHint);
    tvPermissionSure.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if (dialog.isShowing()) {
                dialog.cancel();
            }
            Intent intent = new Intent();
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            Uri uri = Uri.fromParts("package",
                    context.getPackageName(), null);
            intent.setData(uri);
            context.startActivity(intent);
            if (isFinishActivity)  context.finish();
        }

    });
    tvPermissionCancel.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (dialog.isShowing()) {dialog.cancel();
            }
            if (isFinishActivity) context.finish();
            
        }
    });
}

Activity中,就在你需要权限判断的地方加下面一段代码

selfPermissionGranted(this,new BaseActivity.PermissionCallback() {
    @Override
    public void hasPermission() {
        openTuling();//已经开启了权限,进入处理
    }
    @Override
    public void noPermission() {
        //没有权限的处理
    }
},"android.permission.RECORD_AUDIO","android.permission.CALL_PHONE");//填写需要请求的权限,可能是多个

如果在Fragment中使用,直接在自己的BaseFragment写个方法调用此Activity的方法即可。

/**
 * Android M运行时权限请求封装
 * @param runnable 请求权限回调
 * @param permissions 请求的权限(数组类型),直接从Manifest中读取相应的值,比如Manifest.permission.WRITE_CONTACTS
 */
public void selfPermissionGranted(Context context,BaseActivity.PermissionCallback runnable, String... permissions){
    MyLog.i(TAG,"selfPermissionGranted");
    if(context!=null && context instanceof BaseActivity){
        MyLog.i(TAG,"getActivity()!=null");
        ((BaseActivity) getActivity()).selfPermissionGranted(context,runnable,permissions);
    }
}

最后,你在编写代码的时候,可能会遇到以下问题
(1).你之前用到了appcompat-v4,appcompat-v13而不是appcompat-v7 这里需要换到appcompat-v7,appcompat-v7中自定义属性命名不能是常用关键字(heght.width,color..)
(2)如果你baseActivity的父类是Activity换成了AppcompatActivity,那么主题同样需要换成的AppCompat主题
(3)如果你在fragment中调用,记得fragment要的父类不能用FragmentActivity而是用BaseActivity,因为AppCompatActivity extends FragmentActivity

最后我把代码封装了一个新的库:compile 'com.apeng:EsayPermissions:1.0.0'
非常方便,欢迎体验 项目地址

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

推荐阅读更多精彩内容