Android 6.0 运行时权限处理问题

简介: 序自从升级到Android M以来,最大的改变就是增加了运行时权限RuntimePermission,6.0以上的系统如果没有做适配,运行了targetSDK=23的App时就会报权限错误。我们知道6.0以下的系统是按照的时候权限申请的,6.0和之后的版本是我们想要使用某个app的权限,去动态申请的,这也是基于安全上的考虑吧(比如:单机的象棋对战,请求访问通讯录权限等不合理的权限,这肯定是有

自从升级到Android M以来,最大的改变就是增加了运行时权限RuntimePermission,6.0以上的系统如果没有做适配,运行了targetSDK=23的App时就会报权限错误。我们知道6.0以下的系统是按照的时候权限申请的,6.0和之后的版本是我们想要使用某个app的权限,去动态申请的,这也是基于安全上的考虑吧(比如:单机的象棋对战,请求访问通讯录权限等不合理的权限,这肯定是有问题的)。

为了保护用户的隐私,谷歌官方将权限分为了两类,一个是正常权限(Normal Permissions),这类权限不涉及用户隐私,是不需要用户进行授权的,比如访问网络,手机震动等。还有一类是危险权限(Dangerous Permissions),一般是涉及到用户隐私的,需要用户进行授权,比如操作SD卡的写入,相机,录音等。

我们来看一张权限的清单文件:


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


权限如何申请

那么对于我们开发者来说,怎么适配6.0呢?按着官方的api走就行:

1,在AndroidManifest文件中添加需要的权限。


2,检查权限

if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
}else{
    //
}

建议这些检查权限的代码可以写到基类里面去。


3,申请授权

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

说明:第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以第二个参数看出,6.0是一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。


4,处理权限申请回调

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }
    }
}

不过这里有个情况,对于用户上次拒绝的权限,在下次需要这个权限的时候,系统怎么处理的了?

if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.READ_CONTACTS)) 
    // Show an expanation to the user *asynchronously* -- don't block
    // this thread waiting for the user's response! After the user
    // sees the explanation, try again to request the permission.

}

所以完整的处理逻辑:

if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

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

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

项目实践

权限工具类:

public class PermissionUtils {

    private static final ArrayMap<String, Integer> MIN_SDK_PERMISSIONS;

    static {
        MIN_SDK_PERMISSIONS = new ArrayMap<>(8);
        MIN_SDK_PERMISSIONS.put("com.android.voicemail.permission.ADD_VOICEMAIL", 14);
        MIN_SDK_PERMISSIONS.put("android.permission.BODY_SENSORS", 20);
        MIN_SDK_PERMISSIONS.put("android.permission.READ_CALL_LOG", 16);
        MIN_SDK_PERMISSIONS.put("android.permission.READ_EXTERNAL_STORAGE", 16);
        MIN_SDK_PERMISSIONS.put("android.permission.USE_SIP", 9);
        MIN_SDK_PERMISSIONS.put("android.permission.WRITE_CALL_LOG", 16);
        MIN_SDK_PERMISSIONS.put("android.permission.SYSTEM_ALERT_WINDOW", 23);
        MIN_SDK_PERMISSIONS.put("android.permission.WRITE_SETTINGS", 23);
    }

    private static volatile int targetSdkVersion = -1;

    public static boolean checkPermissions(int... grantResults) {
        if (grantResults.length == 0) {
            return false;
        }
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    public static boolean hasSelfPermissions(Context context, String... permissions) {
        for (String permission : permissions) {
            if (permissionExists(permission) && !hasSelfPermission(context, permission)) {
                return false;
            }
        }
        return true;
    }

    private static boolean permissionExists(String permission) {
        Integer minVersion = MIN_SDK_PERMISSIONS.get(permission);
        return minVersion == null || Build.VERSION.SDK_INT >= minVersion;
    }


    private static boolean hasSelfPermission(Context context, String permission) {
        try {
            return PermissionChecker.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
        } catch (RuntimeException t) {
            return false;
        }
    }

    public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
        for (String permission : permissions) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                return true;
            }
        }
        return false;
    }

    @TargetApi(Build.VERSION_CODES.DONUT)
    public static int getTargetSdkVersion(Context context) {
        try {
            if (targetSdkVersion != -1) {
                return targetSdkVersion;
            }
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
        } catch (PackageManager.NameNotFoundException ignored) {
        }
        return targetSdkVersion;
    }
}

这里可以根据实际情况进行优化和扩展

基类:

public class BasePermissionActivity extends AppCompatActivity {

    private PermissionHandler mHandler=null;
    private static int requesrCode=001;

    /**
     * 请求权限
     */
    protected void requestPermission(String[] permissions, PermissionHandler handler) {
        if (PermissionUtils.hasSelfPermissions(this, permissions)) {
            handler.onGranted();
        } else {
            mHandler = handler;
            ActivityCompat.requestPermissions(this, permissions, requesrCode);
        }
    }


    protected void requestPermission( PermissionHandler handler,String permissions) {
        if (PermissionUtils.hasSelfPermissions(this, permissions)) {
            handler.onGranted();
        } else {
            mHandler = handler;
            ActivityCompat.requestPermissions(this, new String[]{permissions} , requesrCode);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (mHandler == null) return;
        if (PermissionUtils.checkPermissions(grantResults)) {
            mHandler.onGranted();
        } else {
            if (!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
                if (!mHandler.onNeverRequest()) {
                    Toast.makeText(this, "权限已被拒绝,请在设置-应用-权限中打开", Toast.LENGTH_SHORT).show();
                }

            } else {
                mHandler.onDenied();
            }
        }
    }


    public abstract class PermissionHandler {
        //权限通过
        public abstract void onGranted();

        //权限拒绝
        public void onDenied() {
        }

        //不再询问
        public boolean onNeverRequest() {
            return false;
        }
    }
}

测试:

public class MainActivity extends BasePermissionActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init() {
        request1();
    }

    private void request1() {
        requestPermission(new String[]{Manifest.permission.CAMERA}, new PermissionHandler() {
            @Override
            public void onGranted() {
                Intent intent = new Intent(); //调用照相机
                intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivity(intent);
            }

            @Override
            public void onDenied() {
                Toast.makeText(MainActivity.this, "拒绝", Toast.LENGTH_SHORT).show();
            }
        });
    }


    private void request2() {
        requestPermission(new String[]{Manifest.permission.CAMERA}, new PermissionHandler() {
            @Override
            public void onGranted() {
                Intent intent = new Intent(); //调用照相机
                intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivity(intent);
            }

            @Override
            public void onDenied() {
                Toast.makeText(MainActivity.this, "拒绝", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
代码: https://github.com/xiangzhihong/permissionDemo



最后附上鸿洋封装的比较好的库:https://github.com/lovedise/PermissionGen



目录
相关文章
|
Java 物联网 API
Android 6.0 运行时权限管理实践
Android 6.0 运行时权限管理实践
|
Android开发
Android RuntimePermissions运行时权限:批量权限申请
Android RuntimePermissions运行时权限:批量权限申请 绝大多数情况一个APP不可能只有单个权限,往往需要运行时批量申请n多个权限。
1166 0
|
开发工具 Android开发
Android RuntimePermissions运行时权限:单个运行时权限申请简例
Android RuntimePermissions运行时权限:单个运行时权限申请简例 Android运行时权限申请的框架结构和步骤比较简单和固定,一般现状代码启动后检查当前的Android SDK版本是否大于等于23,在SDK版本大于等于23时候,才启动运行时权限申请。
1129 0
|
Android开发 Java 数据安全/隐私保护
Android动态获取运行时权限RxPermissions
 Android动态获取运行时权限RxPermissions 新版的Android权限控制更加严格,一般需要在APP的运行时动态获取,如果按照谷歌官方的方法比葫芦画瓢获取动态运行时权限,代码比较繁琐,如果和业务逻辑再搅和在一起,代码的可读性变得比较差,因此一些第三方的运行时权限获取库因运而生。
1545 0
|
API Android开发
Android6.0运行时权限解析,RxPermissions的使用,自己封装一套权限框架
Android6.0运行时权限解析,RxPermissions的使用,自己封装一套权限框架 在Android6.0中,新增加了一个运行时的权限,我相信很多人都已经知道了,估计也知道怎么用了,这篇博客很简单,就是告诉大家如何去申请运行时权限和RxPermission这个权限框架的使用,同时根据现有的技术封装思想,去封装一个自己可用的权限框架,好的,我们继续往下看 一.
1960 0
|
API Android开发 UED
谈谈Android 6.0运行时权限理解
前言  谷歌在2015年8月份时候,发布了Android 6.0版本,代号叫做“棉花糖”(Marshmallow ),其中的很大的一部分变化,是在用户权限授权上,或许是感觉之前默认授权的不合理,现在6.0出来,使得用户权限授权变得合理。
1069 0
|
2天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
22 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
25天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0