android6.0运行时权限

一.概述

为了保护系统的完整性和用户隐私权,android6.0(API 级别 23)引入了运行时权限的概念。
1.概念:
android6.0运行时权限是使用户可直接在运行时管理应用权限的一种新的权限模式。
2.变化:

对象\系统版本Android6.0以下Android6.0以后
用户用户只有在同意权限列表之后才能完成应用的安装用户可为所安装的各个应用分别授予或撤销权限,对应用的功能进行更多控制
开发者只需在Manifest中申明所需要的权限在Manifest申明权限后,还需在运行时检查和请求权限,用户允许后方可使用

Android6.0以后,用户可以随时进入应用的“Settings”屏幕调用权限。
所以对于开发者,要求以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,必须在运行时检查和请求权限,否则会发生崩溃。
3.意义:
这种模式下用户可为所安装的各个应用分别授予或撤销权限,用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。
4.使用 adb 工具从命令行管理权限:
按组列出权限和状态:
$ adb shell pm list permissions -d -g

授予或撤销一项或多项权限:
$ adb shell pm [grant|revoke] …

二.权限分类

系统权限分为几个保护级别。其中两个最重要保护级别是正常权限(Normal Permissions)和危险权限(Dangerous Permissions):
1.Normal Permissions:
(1)概念:
这类权限是指一般不涉及用户隐私或其他应用操作,系统自动向应用授予的权限。比如手机震动、蓝牙,访问网络等。
(2)特点:
只需要在Manifest中声明,不需要每次使用时都检查权限,用户不能取消以上授权,除非用户卸载App。
(3)正常权限列表:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
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_IGNORE_BATTERY_OPTIMIZATIONS
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

2.Dangerous Permissions:
(1)概念:
这类权限是指一般涉及到用户隐私的,需要用户进行授权后才可使用的权限。比如读取sdcard、访问通讯录等。
(2)特点:
对于此类权限,如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,除了在Manifest中申明,还需要动态申请权限。
(3)权限组:
任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。
同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如:当WRITE_CONTACTS被授权了,当App申请READ_CONTACTS权限时,系统会直接授权通过。
申请权限的时候系统弹出的Dialog上面的文本是对整个权限组的说明,而不是单个权限。
(4) 危险权限和权限组:
这里写图片描述

三.相关API解析

以最简单的电话权限申请步骤为例解析相关API。
1.声明权限:
需要在AndroidManifest.xml中声明所需要的权限。

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

2.检查权限:
调用ContextCompat.checkSelfPermission方法来检查用户是否授权。

if (ContextCompat.checkSelfPermission(this, 
                android.Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            //用户未授权
        } else {
            //用户已经授权
        }
返回值返回值说明
PackageManager.PERMISSION_GRANTED表示应用具有此权限
PackageManager.PERMISSION_DENIED表示应用不具有此权限

3.请求权限:

      if (ContextCompat.checkSelfPermission(this, android.Manifest.permission
                .CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {//用户未授权
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CALL_PHONE)) {
                //给用户一个申请权限的解释
            } else {
                //请求权限
                ActivityCompat.requestPermissions(this, new
                        String[]{android.Manifest.permission.CALL_PHONE},
                        REQUEST_PERMISSION_CALL_PHONE);
            }

        } else {//用户已经授权
        }

(1)requestPermissions方法:
调用requestPermissions(Context context, String[] permissions, int requestCode)方法用来向用户请求一个或多个权限。

参数参数说明
ContextContext对象
String[] permissions需要申请的权限数组(支持一次一个或多个权限的请求)
requestCode请求码,主要用户回调的时候进行判断

(2)shouldShowRequestPermissionRationale方法:
主要用于给用户一个申请权限的解释。即用户在上一次已经拒绝过此权限申请,应用又弹框申请此权限,使用该方法来向用户解释需要授权的原因,避免用户勾选“不再询问”拒绝权限,导致应该中一些功能无法正常使用。

方法返回值情况
true第一次请求权限时,用户拒绝了。
false第二次请求权限时,用户拒绝了,并选择了“不再询问”的选项

4.请求权限的回调:
根据onRequestPermissionsResult回调方法中的结果来判断用户是否授权。

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION_CALL_PHONE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //权限被用户同意
            } else {
                //权限被用户拒绝
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

其中从int[] grantResults中可以得到授权结果。
在Fragment中申请权限API和在Activity中申请权限API不完全相同,但大同小异,此处不再进行说明。

四.完整案例

电话权限申请的完整代码:当用户点击“不再询问”选项后,跳转至设置页面提示用户手动授权。以在Activity中申请权限为例:

public class MainActivity extends AppCompatActivity {
    private static int REQUEST_PERMISSION_CALL_PHONE = 100;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView )findViewById(R.id.textView);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestPermission();
            }
        });
    }

    /**
     * 申请电话权限
     */
    private void requestPermission(){
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission
                .CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {//用户未授权
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CALL_PHONE)) {
                //给用户一个申请权限的解释
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("说明")
                        .setMessage("需要使用电话权限,拨打电话")
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                //继续请求权限
                                ActivityCompat.requestPermissions(MainActivity.this, new
                                        String[]{android.Manifest.permission.CALL_PHONE}, REQUEST_PERMISSION_CALL_PHONE);
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Toast.makeText(MainActivity.this, "未授权", Toast.LENGTH_LONG).show();
                                return;
                            }
                        })
                        .create()
                        .show();
            } else {
                //请求权限
                ActivityCompat.requestPermissions(this, new
                                String[]{android.Manifest.permission.CALL_PHONE},
                        REQUEST_PERMISSION_CALL_PHONE);
            }

        } else {//用户已经授权
            callPhone();
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION_CALL_PHONE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //权限被用户同意
                callPhone();
            } else {
                //权限被用户拒绝
                if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, android.Manifest.permission.CALL_PHONE)) {
                    //用户点击了“不在询问”选项,则跳到设置页提醒用户手动设置权限
                    Toast.makeText(MainActivity.this, "需要手动打开权限才能使用此功能", Toast.LENGTH_LONG).show();
                    intentSetting();
                } else {
                    Toast.makeText(MainActivity.this, "未授权", Toast.LENGTH_LONG).show();
                }
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     * 拨打电话
     */
    private void callPhone(){
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10086");
        intent.setData(data);
        startActivity(intent);
    }

    /**
     * 跳到设置页面
     */
    private void intentSetting(){
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivityForResult(intent, 0);
    }
}

五.代码封装

一个应用可能需要多次申请权限,虽然权限处理并不复杂,但是需要编写很多重复的代码,比较繁琐,故需要代码封装。
1.相关开源项目:

开源项目名称特点
PermissionsDispatcher使用标注的方式,动态生成类处理运行时权限
RxPermissions基于RxJava的运行时权限检测框架
PermissionGen使用起来很简单,但是运行时反射会影响效率
MPermissions在PermissionGen基础上使用编译时注解提高效率
Grant简化运行时权限的处理,比较灵活
android-RuntimePermissions由Google官方提供

2.自己封装工具类:
此工具类支持在Activity和Fragment中申请权限:
运行效果:
这里写图片描述
因上传gif大小限制,在fragment中申请权限未录制,如有需要,请下载源码进行查看。
(1)工具类:

public class PermissionUtil {

    public static int testCode = 100;
    public static int fragmentCode = 101;
    private PermissionCallback callback;
    public int code;
    private static final String TAG = "PermissionUtil";
    private static final String NO_LONGER_ASK = "no_longer_ask";
    private Activity activity;
    private Fragment fragment;


    public static <T extends Object> PermissionUtil getInstance(T t) {
        PermissionUtil permission = new PermissionUtil();
        if (t == null) {
            throw new NullPointerException("NullPointerException");
        } else if (t instanceof Activity) {
            permission.activity = (Activity) t;
        } else if (t instanceof Fragment) {
            permission.fragment = (Fragment) t;
            permission.activity = permission.fragment.getActivity();
        } else {
            throw new IllegalArgumentException("IllegalArgumentException");
        }

        return permission;
    }

    private PermissionUtil() {
    }

    /**
     * @param isNeedShowRequestPermissionRationale 当用户第一次拒绝申请权限后再次申请权限是否弹出解释弹框
     * @param permissions                          申请的权限数组
     * @param requestCode                          请求码
     * @param permissionCallback                   检查权限回调
     */
    public void requestPermissions(boolean isNeedShowRequestPermissionRationale, String[] permissions, int requestCode, PermissionCallback permissionCallback) {
        callback = permissionCallback;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (permissions != null && permissions.length > 0) {
                String[] denyPermissions = getDenyPermissions(permissions);
                if (denyPermissions.length > 0) {
                    if (isNeedShowRequestPermissionRationale) {
                        if (shouldShowRequestPermissionRationale(denyPermissions)) {
                            showRequestPermissionRationale(denyPermissions, callback, requestCode);
                        } else {
                            handlerPermission(requestCode, denyPermissions);
                        }
                    } else {
                        handlerPermission(requestCode, denyPermissions);
                    }
                } else {
                    callback.permittedPermissions();
                }
            } else {
                Log.d(TAG, "requestPermissions:permissions is null");
            }
        } else {
            callback.permittedPermissions();
        }
    }

    private void handlerPermission(int requestCode, String[] denyPermissions) {
        if (fragment != null) {
            fragment.requestPermissions(denyPermissions, requestCode);
        } else {
            ActivityCompat.requestPermissions(activity, denyPermissions, requestCode);
        }
    }

    /**
     * 请求权限结果处理
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     * @param callback
     */
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, PermissionCallback callback) {
        if (requestCode == code) {
            String[] denyPermissions = getNotVerifyPermissions(grantResults, permissions);
            if (denyPermissions.length > 0) {
                callback.rejectPermission(denyPermissions);
                intentSetting(denyPermissions);
            } else {
                callback.permittedPermissions();
            }
        }
    }

    /**
     * 根据申请权限结果,得到未授权的权限数组
     *
     * @param grantResults 存储权限结果数组
     * @param permissions  存储权限数组
     * @return 未授权的权限数组
     */
    public String[] getNotVerifyPermissions(int[] grantResults, String[] permissions) {
        List<String> denyPermissions = new ArrayList<>();
        if (grantResults.length > 0) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                    denyPermissions.add(permissions[i]);
                }
            }
        }
        return denyPermissions.toArray(new String[denyPermissions.size()]);
    }

    /**
     * 检查权限得到未授权的权限数组
     *
     * @param permissions 申请授权的权限数组
     * @return 未授权的权限数组
     */
    private String[] getDenyPermissions(String[] permissions) {
        List<String> denyPermissions = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                denyPermissions.add(permission);
            }
        }
        return denyPermissions.toArray(new String[denyPermissions.size()]);
    }

    /**
     * 判断是否有权限需要显示解释弹框
     *
     * @param permissions
     * @return
     */
    private boolean shouldShowRequestPermissionRationale(String[] permissions) {
        if (fragment != null) {
            for (String permission : permissions) {
                if (fragment.shouldShowRequestPermissionRationale(permission)) {
                    return true;
                }
            }
        } else {
            for (String permission : permissions) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 显示解释弹框
     *
     * @param callback
     * @param requestCode
     */
    private void showRequestPermissionRationale(final String[] permissions, final PermissionCallback callback, final int requestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setMessage("需要开启权限才能使用此功能")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //继续请求权限
                        handlerPermission(requestCode, permissions);
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(callback != null){
                            callback.rejectPermission(permissions);
                        }
                    }
                })
                .create()
                .show();
    }

    /**
     * 申请权限的结果回调接口
     */
    public interface PermissionCallback {
        void permittedPermissions(); //允许申请的权限

        void rejectPermission(String[] rejectPermissions);//申请的权限中有未授权的权限,参数为未授权的权限集合
    }

    /**
     * 当用户点击不再询问之后,再次请求权限提示用户进入设置页手动授权
     *
     * @param permissions
     */
    public void intentSetting(String[] permissions) {
        if (!shouldShowRequestPermissionRationale(permissions)) {
            if (getFlag()) {
                showGoToSettingDialog(permissions);
            }
            saveFlag(true);
        }
    }

    /**
     * 跳到系统的设置页面
     */
    private void gotoSetting() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
        intent.setData(uri);
        activity.startActivityForResult(intent, 0);
    }

    /**
     * 用户点击不再询问后,再次申请权限弹框
     *
     * @param permissions 请求的权限
     */
    private void showGoToSettingDialog(final String[] permissions) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setMessage("需要在设置页面手动设置权限才可使用此功能")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gotoSetting();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (callback != null) {
                            callback.rejectPermission(permissions);
                        }
                    }
                })
                .create()
                .show();
    }

    private void saveFlag(boolean flag) {
        SharedPreferences sharedPreferences = activity.getSharedPreferences(
                "userInfo", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(NO_LONGER_ASK, flag);
        editor.commit();
    }

    private boolean getFlag() {
        SharedPreferences sharedPreferences = activity.getSharedPreferences(
                "userInfo", Context.MODE_PRIVATE);
        return sharedPreferences.getBoolean(NO_LONGER_ASK, false);
    }

    public void showRejectMsg() {
        if (fragment != null) {
            Toast.makeText(fragment.getActivity(), "授权后,才能使用此功能", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(activity, "授权后,才能使用此功能", Toast.LENGTH_SHORT).show();
        }
    }
}

完整代码:https://github.com/lweiandroid/PermissionDemo.git

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值