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

前言#

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

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

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

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

正文#

<h2>运行时权限检查机制</h2>

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

<h2>哪些是敏感权限</h2>

<!-- CALENDAR 日历组 -->
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    <!-- CAMERA 相机拍照组 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- CONTACTS 联系人组 -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <!-- LOCATION 定位组 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- MICROPHONE 麦克风组 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!-- PHONE 组 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
    <uses-permission android:name="android.permission.USE_SIP" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <!-- SENSORS 传感器组 -->
    <uses-permission android:name="android.permission.BODY_SENSORS" />
    <!-- SMS 组 -->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
    <uses-permission android:name="android.permission.RECEIVE_MMS" />
    <!-- STORAGE 存储组 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

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

<h2>如何申请权限</h2>

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里呢?

<h2>接口式开发</h2>

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

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

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

<h2>写一个小Demo</h2>

只贴一下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,否则是不会弹出权限申请的提示的。你要问我为啥,我只能猜测,设计者更希望你只申请一个,而不是一组...

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

推荐阅读更多精彩内容