适配android6.0:运行时权限检查机制

前言#

清明放假终于结束了,赶紧写点东西来脉动回来。这是一篇偏概念性的文章,文字偏多,所以别捉急,慢慢看。

现在高版本的android系统市场占有率提升的非常快,这依赖于智能手机越来越便宜,越来越普遍,新手机一般都会搭载高版本的android系统,来丰富用户的体验,但是也逐渐的暴露出了很多的问题,最严重的就是用户的安全问题。

之前很多应用会申请很多的权限,尤其是第三方sdk,我们也不知道到底他们要用这些权限做什么,只要把权限写到配置文件中,系统就默许了权限。

Google发现了这会给用户造成非常大的困扰和安全问题,例如app随意的读取手机的联系人,获取用户的地理位置等等,于是在Android 6.0 开始了新的权限机制:运行时检查,不再默认权限行为。这也是对开发者影响最大的一点,那么如果适配android 6.0呢?

正文#

运行时权限检查机制

在app运行时,使用了敏感权限,会提示用户是否要授予这个app对应的权限,用户有拒绝和同意的权利,如果拒绝最好要提示用户,拒绝会造成什么样的影响,这样用户能明白申请的原因,重新授予权限。

哪些是敏感权限


    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

这是从别处复制过来的,看的出来只要是和用户相关的权限,几乎都属于敏感权限,值得注意的是有些权限,部分手机已经进行了优化,例如我使用的360手机,读写sd卡权限就是默许的,如果你也遇到了这个情况,也不用惊讶。

如何申请权限

android 6.0 把权限的申请和回执有两处:Activity和Fragment。其他的地方目前不可以。申请的方法:

 // 检查是否应被授权
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    // 已经被授权,直接进行操作
} else {
    // 没有授权,需要进行授权申请
    // Context  申请的权限   申请的请求码
    ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}

先检查是否已经授予了权限,如果没有就去申请权限。处理结果在:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch(requestCode){
        case 0:
            // 处理用户授权的返回结果
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            } else {
                    // 授权失败
            }
            break;
        default:
            break;
    }
}

这样整个授权流程就弄清楚了,两个api,重写onRequestPermissionsResult。那么问题来了,如果直接在Activity中申请权限还好一点,如果是原来的Dialog里呢?

接口式开发

这个概念已经是老朋友了,而且我们也经常用,例如自定义View,在onClick调用自定义的点击接口,这就是接口式开发,把实际的功能实现交给其他人,控件本身作为一个媒介。

android 6.0很明显是希望用户改变原来的习惯,或者说是规范编码习惯,例如,有些Dialog的代码非常的庞大,有10个按钮,Dialog里就会有10个功能逻辑,典型的就是第三方分享(按钮真心多啊)。

Dialog大概就是对话框的意思,从概念上就能感受到,他是一个很轻量的东西,就跟跑腿的一样,老板说问问客人要吃啥,服务员问完客人在回来告诉老板,老板知道结果后,就给客人做什么。

写一个小Demo

只贴一下MainActivity的代码把:

public class MainActivity extends AppCompatActivity implements TipDialog.OnTipDialogButtonOnClickListener {

    private static final String PERMISSION = Manifest.permission.CAMERA;
    private ConstraintLayout container;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        container = (ConstraintLayout) findViewById(R.id.container);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TipDialog tipDialog = new TipDialog(MainActivity.this);
                tipDialog.setTitle("是否打开摄像头");
                tipDialog.setOnTipDialogButtonOnClickListener(MainActivity.this);
                tipDialog.show();
            }
        });

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 0:
                // 处理用户授权的返回结果
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openCamera();
                } else {
                    // 授权失败
                    Toast.makeText(MainActivity.this, "未授予摄像头权限,无法使用", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onTipDialogCancelButtonClick(TipDialog dialog) {
        dialog.dismiss();
    }

    @Override
    public void onTipDialogSureButtonClick(TipDialog dialog) {
        dialog.dismiss();
        openCamera();
    }

    private void openCamera() {
        // 检查是否应被授权
        if (ContextCompat.checkSelfPermission(MainActivity.this, PERMISSION) != PackageManager.PERMISSION_GRANTED) {
//            // 没有授权,需要进行授权申请
//            // Context  申请的权限   申请的请求码
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{PERMISSION}, 0);
        } else {
            container.addView(new FlashLightSurfaceView(this), 0, 0);
        }
    }
}

这个demo 非常简单,首先我自定义了一个FlashLightSurfaceView,他里面打开了Camera,使用摄像头前判断权限,没有的话申请权限,被拒绝就提示没有权限无法使用。

强调一点:onRequestPermissionsResult只能回调这个Activity发起的权限申请(requestPermissions)。

但是这个demo却没有什么代表性,因为他只能在原生android运行正常,例如模拟器,但是真机就很难说了。

真机运行情况#

已经有很多朋友已经踩过这个坑了,由于了国内产商都对系统进行了定制,我发现对权限这部分影响真的是太大了,我遇到主要有以下几种情况:

1、仅仅是申请权限,但是当时没有使用到权限对应的Api,不会弹出权限申请窗口。

2、当使用到对应权限的API,app会自动申请权限,弹出权限申请窗口,而这个申请结果我们是无法通过onRequestPermissionsResult来获取结果的。

3、如果直接拒绝了权限申请,仍然返回PackageManager.PERMISSION_GRANTED。

是不是很尴尬?整个权限流程已经被蹂躏的惨不忍睹,这还怎么接着搞事情?

解决办法#

我目前发现的最好的办法就是 try-catch,使用了没有权限的API,系统就会报错甚至崩溃,那我就直接在外围加上try-catch,如果抛出了问题,百分之八十都是权限的问题,那么修改一下:

 
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            if (camera == null) {
                int count = Camera.getNumberOfCameras();
                if (count > 0) {
                    camera = Camera.open();
                    camera.setPreviewDisplay(holder);
                } else {
                    Toast.makeText(getContext(), "没有找到摄像头...", Toast.LENGTH_SHORT).show();
                }
            }
        } catch (Exception e) {
            if (camera != null)
                camera.release();
            camera = null;
            Toast.makeText(getContext(), "未授予摄像头权限,无法使用", Toast.LENGTH_SHORT).show();
        }
    }

总结#

android6.0 的权限运行机制是好的,但是没想到会变成目前这么乱套的情况,给我们的适配也增加了很大的难度。目前流行的各大厂商可能也有自己的考虑,把权限这一块似乎都想把控在自己的手里,造成这样的局面,也是很尴尬。

虽然通过一些小手段可以暂时弥补,但是最关键的还是希望各位厂商大大们还是尽量保持原生的权限机智吧...

ok,那就到这里了,如果你有更好的解决android 6.0的权限适配的问题,请留下您的技巧,让大家共同进步。

Demo下载地址,仅供参考

补充#

忘了强调一个细节,在申请权限的时候,不能申请Manifest.permission_group.xxxx,否则是不会弹出权限申请的提示的。你要问我为啥,我只能猜测,设计者更希望你只申请一个,而不是一组...

你可能感兴趣的:(适配android6.0:运行时权限检查机制)