Android - 6.0动态权限申请封装

运行时权限

此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。

对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。

如需了解有关在您的应用中支持新权限模式的详情,请参阅使用系统权限。如需了解有关如何评估新模式对应用的影响的提示,请参阅权限最佳做法

新增检查方法 checkSelfPermission()

| 参数 |
permission String:正在检查的权限的名称。
这个值绝对不能null。
| 返回 |
| int | PackageManager.PERMISSION_GRANTED如果您有权限,或者如果没有。PackageManager.PERMISSION_DENIED
价值是PERMISSION_GRANTED或PERMISSION_DENIED。

新增请求权限方法 requestPermissions()

| 参数 |
permissions String:请求的权限。我必须非空并且不是空的。
requestCode int:特定于应用程序的请求代码以与报告的结果匹配。应该>=0onRequestPermissionsResult(int, String[], int[])
| 抛出 |
IllegalArgumentException 如果requestCode为负数。

官方有个地方说可以使用ContextWrapper.checkSelfPermission(String) 。其实这个方法很多地方都能使用到。
好,Android M 为什么会新增这两方法呢.因为在Android 6.0 (API 23) 开始用户开始在应用运行时向其授予权限,而不是在应用安装时授予。这种权限机制可以让用户更好的管理应用的权限,保障用户隐私。从此之后...

系统权限分为两种

正常权限

不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
android.permission.ACCESS LOCATIONEXTRA_COMMANDS
android.permission.ACCESS NETWORKSTATE
android.permission.ACCESS NOTIFICATIONPOLICY
android.permission.ACCESS WIFISTATE
android.permission.ACCESS WIMAXSTATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE NETWORKSTATE
android.permission.CHANGE WIFIMULTICAST_STATE
android.permission.CHANGE WIFISTATE
android.permission.CHANGE WIMAXSTATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND STATUSBAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET PACKAGESIZE
android.permission.INTERNET
android.permission.KILL BACKGROUNDPROCESSES
android.permission.MODIFY AUDIOSETTINGS
android.permission.NFC
android.permission.READ SYNCSETTINGS
android.permission.READ SYNCSTATS
android.permission.RECEIVE BOOTCOMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST INSTALLPACKAGES
android.permission.SET TIMEZONE
android.permission.SET_WALLPAPER
android.permission.SET WALLPAPERHINTS
android.permission.SUBSCRIBED FEEDSREAD
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE SYNCSETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT

危险权限

会授予应用访问用户机密数据的权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_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
permission:android.permission.ACCESS_COARSE_LOCATION

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
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS

阅读到这里,小伙伴会有疑问.为什么危险权限一组一组。这里解释一下,比如 permission-group.SMS 中的其中一条READ_SMS被授权了。那么在使用其他同组权限的时候就不会去向用户发起请求,我们使用checkSelfPermission得到的结果也是授权的。

兼容性的问题

在Android 6.0 以前只要在AndroidManifest.xml上申明了权限,应用就默认带有权限.(在原生系统中)。有些厂商的手机一样可以让用户关闭掉权限。那么...在API<23的情况下关掉权限.再使用权限.那么只有死路条.所以。采取良好的方案就是Try 。

封装思路

其实每写一篇简书前我都会看其他人写的文章,这样就可以把每个人的优势结合在一起。我之前做适配的时候也在Github上找过动态权限申请的库,也可以说成是工具类把。我去看了源码。又一个声称可以在任何地方都可以使用到权限,后来我就用上了这个。其实当时我并不是很理解,因为权限当时我去找资料了是Activity才能发起了.回调固然回到Activity,后来我翻看他的源码...是new 了一个Activity出来.还有一些工具类是让你一行代码调用.然后在onRequestPermissionsResult也要写一行工具类的代码.我觉得这种比较麻烦。后来我就想到了。将使用到的方法功能封装在一个Activity里面。让BaseActivity去继承这个PermissionRequestActivity。一个方法使用,给一个回调出来,授权权限是否成功。

PermissionRequestActivity


import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;

/**
 * Created by 卖火柴的小女孩 - Jc on 2018/8/21.
 */

public class PermissionRequestActivity extends FragmentActivity {

    private static final int PERMISSION_REQUEST_CODE = 1088;

    private String mPermissionDes;
    private CallBack mCallBack;


    /**
     * 权限申请使用方法
     *
     * @param permissionDes 权限说明
     * @param callBack      申请回调
     * @param permissions   申请权限
     */
    protected void requestPermission(String permissionDes, CallBack callBack, @NonNull String... permissions) {
        mCallBack = callBack;
        mPermissionDes = permissionDes;
        if (checkPermission(permissions))
            mCallBack.hasPermission();
        else {
            ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
        }
    }

    /**
     * 判断系统版本大于6.0的时候
     *
     * @param permissions 申请权限
     * @return
     */
    protected boolean checkPermission(@NonNull String... permissions) {
        //大于6.0的时候需要动态申请权限.小于6.0的时候如果用户手动关闭权限,程序即崩 需要做Try处理
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return checkSelfPermissions(permissions);
        }
        return true;
    }

    /**
     * 检查权限是否已经授权
     *
     * @param permissions 申请权限
     * @return
     */
    private boolean checkSelfPermissions(@NonNull String... permissions) {
        boolean flag = true;
        for (String p : permissions) {
            if (ActivityCompat.checkSelfPermission(this, p) != PackageManager.PERMISSION_GRANTED) {
                flag = false;
                break;
            }
        }
        return flag;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        boolean hasAllGranted = true;
        for (int i = 0; i < grantResults.length; ++i) {
            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                hasAllGranted = false;
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
                    showDialogPrompt();
                } else {
                    //权限申请被拒绝 ,但用户未选择'不再提示'选项
                    mCallBack.lossPermission();
                }
                break;
            }
        }
        if (hasAllGranted) {
            mCallBack.hasPermission();
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     * 由于用户手动关闭权限提示。APP需要做人性化提示
     */
    private void showDialogPrompt() {
        new AlertDialog.Builder(this)
                .setTitle("权限申请")
                .setMessage(mPermissionDes)
                .setCancelable(false)
                .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //引导用户至设置页手动授权
                        getAppSetting();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //引导用户手动授权,权限请求失败
                        mCallBack.lossPermission();
                    }
                }).show();
    }

    /**
     * 跳转设置 应用设置界面
     *
     * @return
     */
    private Intent getAppSetting() {
        Intent localIntent = null;
        if (Build.VERSION.SDK_INT >= 9) {
            localIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            localIntent.setData(Uri.fromParts("package", getPackageName(), null));
            localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        } else if (Build.VERSION.SDK_INT <= 8) {
            localIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            localIntent.setAction(Intent.ACTION_VIEW);
            localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
            localIntent.putExtra("com.android.settings.ApplicationPkgName", getPackageName());
            localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        startActivity(localIntent);
        return localIntent;
    }


    /**
     * 权限申请回调
     */
    interface CallBack {
        void hasPermission();

        void lossPermission();
    }

}

使用

requestPermission("获取手机信息-获取手机号码、IMEI、IMSI权限\n读写手机存储-读写手机存储", new CallBack() {
                @Override
                public void hasPermission() {
                    String IMEI = getIMEI(MainActivity.this);
                    ((TextView) findViewById(R.id.tv)).setText(IMEI);
                }

                @Override
                public void lossPermission() {
                    Toast.makeText(MainActivity.this, "权限申请被拒绝", Toast.LENGTH_SHORT).show();
                }
            }, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE);

代码讲解

其实代码不多,只有100多行。
方法: requestPermission
参数:String permissionDes -- 用于用户选择'不再提示'按钮提示语
CallBack callBack -- 应用是否带有权限或是否授权权限成功
@NonNull String... permissions -- 需要授权或检查的权限

这个方法一进去 我做了一个判断 checkPermission(permissions) , 先去检查是否有权限,如果有权限的话.那么就不去授权权限了.

方法 :checkPermission() 进来判断是否有权限的时候这个方法有一个API 就是刚才最上面开始讲的checkSelfPermission 这个需要 Build.VERSION.SDK_INT >= Build.VERSION_CODES.M..所以不满足这个条件的都以TRUE返回出去,表明应用在6.0以下的设备默认有这些权限。但是刚才在兼容性的时候讲过了.会崩。

接下来检测到没有权限,使用了这一段代码

ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);

这一行就是向用户发起权限授权的代码了.接下来 我们看 onRequestPermissionsResult 。 这个方法是向用户请求权限的回调结果。这个方法里面我做了处理,我检测了用户是否点击了 ' 不再提示 ' 的按钮。(有些手机没有这个按钮,有些手机有,当选择了此按钮之后 再次发起授权请求,系统将不再向用户发起权限授权请求.而是直接返回拒绝的信息回来.)那么这时我们就需要做一个友好的提示了。如果用户是由于手动点击了这个不再提示的按钮而导致第二次或第n次没有收到授权的消息.那我们应用需要给用户一个提示框showDialogPrompt(),我这里写了一个最简单的弹窗。很多APP都有自己主题的弹窗.主要就是提示用户。告知用户 他的权限被他自己手动不再提示了.那么我们给他一个系统设置界面的跳转getAppSetting(); 然后这个方法的Build.VERSION.SDK_INT <= 8 我就不解释了.有兴趣的就自己去百度。

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

推荐阅读更多精彩内容

  • 那些要动态权限(这么记比较危险的权限,获取硬件和用户信息权限) 不过现在as 中会主动提示加动态权限 calend...
    菩提大师阅读 2,708评论 1 8
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,018评论 25 707
  • 故乡对面有座山,相貌平平。我与它却有着一段不解之缘。 开门平眼望去便能看见对面那一座山,山不高一眼尽收眼底。故乡对...
    南林瑶阅读 326评论 2 5
  • 我写日记大约有10年左右时间,主要是叙事的,感悟的,听了叶老师的100讲,第一次听到晨间日记。于是就开始写了,开始...
    赵远东阅读 351评论 0 1