Android6.0特性 - 动态权限之实战演练

133 阅读9分钟

我们都知道在6.0之前当我们涉及到权限部分,只需要在AndroidMainfest中配置权限就可以,但是在6.0之后Google考虑到了用户隐私性方面的问题,所以增添了动态权限!同时此项更新也是Android 6.0的主要特性 ~

在此我从基础 - 扩展 - 终结 三个阶段进行权限说明,其中每个阶段时间跨度也确实不小,基本都是半年一年;主要原因在于随着工作经验的积累,我把工作中用到的权限处理,总结在此篇内进行补充 ~ 应该性价比还是蛮高的 ~

因掘金右侧目录显示有限,故提前声明完整目录

基础认知 - 动态权限

权限分类

包含9类27种,其中就有相机,电话,信息等相关权限
这里写图片描述

前情提要

  • 简化版解释 :同组权限只需申请一次 ;多处权限申请,只要一次通过,其他地方皆可使用!

细节化解释 :权限是分组的,如上图中的打电话包含7个权限,当我们用到电话权限组其中的某些权限时,只需要申请一次即可!!!因为申请一次的情况下,如果用户已经授权,那么电话权限组内的所有权限都已经默认用户已经同意授权!!!没有必要说读取通讯录请求一次,打电话再请求一次,因为如果第一次用户已经授权的话,授权的是你的权限组,而不是单独的某一个权限!

  • 简化版解释:编译版本的sdk23以前不用管动态权限;Android6.0以前不用管动态权限;但是超过Android6.0、sdk23就必须申请动态权限,不然崩溃!!!

细节化解释:当我们编译使用的是sdk23以前版本的话是有一定几率规避动态权限的问题!但是如果编译的sdk超过sdk23(Android6.0)的话,就需要进行动态权限申请了(早学早用,毕竟是必备的一项技能,都是一些老东西了 - - ~)

处理方式

  • 我们首先在AndroidMainfest中注册权限
  • 当涉及到用户的隐私时,我们就是先查看是否授权,如无授权我们就像用户发起权限请求;当发起授权时,大多时候会出现的是一个弹框,在用户同意与拒绝的时候执行各自操作,当用户授权之后就不会再次请求让用户授权。

基础 - 原始方式(基于此处进行扩展)推荐星数:⭐⭐

以动态申请电话权限为例

  • AndroidMainfest权限添加
  <!--声明权限-->
  <uses-permission android:name="android.permission.CALL_PHONE" />
  • ManiActivity
package com.example.administrator.permisson;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.security.Permission;
import java.util.jar.Manifest;
import static android.R.attr.onClick;

public class MainActivity extends AppCompatActivity {

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

        mCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
         //首先我们查看自己的权限是否已经授权,如果没有的话,我们发起授权的请求
            if(ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CALL_PHONE)!=PackageManager.PERMISSION_GRANTED){
	   /**在动态权限中有一个方法shouldShowRequestPermissionRationale用来监听用户拒绝时候的操作, 有时候我们在这里会进行二次弹框,让用户进行授权(有时候有点偏强制),然后发起请求授权,这里我们并没有写出,大家可以按需求  进行写入*/
	     	ActivityCompat.requestPermissions(MainActivity.this,new String[]{android.Manifest.permission.CALL_PHONE},1);
                }else{
                    //执行拨打电弧操作
                    call();
                }
            }
        });
    }
    
    //当拥有拨打权限的时候,直接执行拨打电话的操作
    private void call() {
        Intent intent=new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:10010"));
        try {
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();;
        }
    }

    /**请求权限的结果-进行回调*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
                //我们发现,上面我们请求权限的时候是以数组的形式请求的,所以这里我们首先判断了授权数组的长度,同时校验角标为0的值是否是我们上面申请的权限(其实因为是数据容器,我们可以多个权限请求,但是我们并不建议这样做)
                if(grantResults.length>0&&grantResults[0]==getPackageManager().PERMISSION_GRANTED){
                    call();
                }else{
                    Toast.makeText(this,"权限已经打开",Toast.LENGTH_LONG).show();
                }
                break;
            default:
                break;
        }
    }
}
  • activity_main
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.administrator.permisson.MainActivity">

    <TextView
        android:id="@+id/tv_call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="6.0电话权限测试" />
</RelativeLayout>

项目 - 通过接口回调处理权限申请 (2018/6/25 补入内容~ 可扩展思维逻辑,不建议使用)推荐星数:⭐⭐

在Github有很多好的三方框架,可以快速实现的动态权限的处理,在这里我想先补充的内容是目前我项目中Base内的权限封装~

项目封装 (接口回调方式)

  • BaseActivity(电话权限示例)

提前知悉(直接copy在类内既可)

 //接口声明
 private OnChechCallPhonePermission mOnCheckCallPhonePermission;
 //requestCode声明
 protected final int PERMISSIONS_CALL_PHONE_STATE = 4;

1.创建接口

    /**
     * 读写手机状态权限申请后回调
     **/
    public interface OnChechCallPhonePermission {
        /**
         * @param haspermission true 允许  false 拒绝
         **/
        void onCheckCallPhonePression(boolean haspermission);
    }

2.权限判断

    /**
     * android6.0动态权限申请:打电话
     **/
    public void checkCallPhonePression(OnChechCallPhonePermission callback) {
        mOnCheckCallPhonePermission = callback;
        int pression = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
        if (Build.VERSION.SDK_INT < 23 || pression == PackageManager.PERMISSION_GRANTED) {
            mOnCheckCallPhonePermission.onCheckCallPhonePression(true);
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, PERMISSIONS_READ_PHONE_STATE);
        }
    }

3.权限申请

  @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        boolean permit = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
        switch (requestCode) {
		 case PERMISSIONS_CALL_PHONE_STATE:
                if (mOnCheckCallPhonePermission != null) {
                    mOnCheckCallPhonePermission.onCheckCallPhonePression(permit);
                }
                break;  
            default:
                break;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
  • 使用方式
	  //通过activity调用,fragment就使用getActivity,activity直接调既可
        MainActivity activity = (MainActivity) this.getActivity();
        activity.checkCallPhonePression(new BaseActivity.OnChechCallPhonePermission () {
            @Override
            public void onChechCallPhonePermission (boolean haspermission) {
                if (haspermission) {
                  //haspermission表示有这个权限了,在这里可以直接写自己的逻辑
                }
            }
        });

框架 - RxPermissions 动态权限申请 ((2018/12/4 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐

原作者的Git地址 - RxPermissions

  • build (project ) 引入Maven库
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  • build(app)加入依赖
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.0.2'
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
  • Manifesst清单文件注册 (注册所需权限)
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
  • 使用方式(申请所需权限)
RxPermissions rxPermissions = new RxPermissions(this);
//Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA 文件存储权限、相机权限
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA).subscribe(new Observer<Boolean>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Boolean aBoolean) {
                    	//有权限的状态
                        if (aBoolean) {
                            showBottomDialog();
                        } 
                      //无权限的状态
					else{
                            Toast.makeText(Activity.this,“当前未授权”), Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });

框架 - EasyPermissions 动态权限申请 ((2020/6/23 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐

之前有同事使用easypermissions这个框架,但是一直没看,索性最近有用到权限申请就顺带学习了下使用方式,特此记录一下 ~

原作者的Git地址 - RxPermissions

build(app)加入依赖

  implementation 'pub.devrel:easypermissions:2.0.1'

以申请读写权限为示例

格外注意:

@AfterPermissionGranted:授权成功后会继续执行使用该注解的方法

onPermissionsGranted :授权成功的监听回调

如同时使用@AfterPermissionGranted,onPermissionsGranted 去监听授权成功的回调的话,对应方法会执行俩次! 所以俩者选其一使用即可!!!

1:此方法一般在某个事件之下,检测文件权限,查看用户当前是否由此权限

requestCode:RC_READ_PHOTO (int值,权限请求标识码,后续会用到)
permission:READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE (需要申请的权限)

    public static final int RC_READ_PHOTO = 0X02;
    /**
     * 检测文件权限
     */
    @AfterPermissionGranted(RC_READ_PHOTO)
    private void checkExternalStoragePermissions() {
        String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
        if (EasyPermissions.hasPermissions(this, perms)) {
            //已有权限
            Toast.makeText(this, "已具备读写权限", Toast.LENGTH_SHORT).show();
        } else {
            //无权限,去申请权限
            EasyPermissions.requestPermissions(this, "需读写权限", RC_READ_PHOTO, perms);
        }
    }

2.重写onRequestPermissionsResult方法,将权限申请的中转到EasyPermissions

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //将结果转发给EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

3.监听权限是否申请成功

    /**
     * 申请成功时调用
     *
     * @param requestCode 请求权限的唯一标识码
     * @param perms       一系列权限
     */
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        switch (requestCode) {
            case RC_READ_PHOTO:
               Toast.makeText(this, "读写权限 - 申请成功", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }

    /**
     * 申请拒绝时调用
     *
     * @param requestCode 请求权限的唯一标识码
     * @param perms       一系列权限
     */
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        switch (requestCode) {
            case RC_READ_PHOTO:
               Toast.makeText(this, "读写权限 - 申请被拒绝", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }

课外小知识

你是否真的想打电话?

注意:我们常规在拨打电话中存在误区,认为打电话就要申请权限,这个看起来没错,但是你要注意你的电话是打出了?还是只跳到了拨打电话的界面呢?

打电话有两种实现方法:

拨号界面

第一种方法,拨打电话跳转到拨号界面,无需申请权限拨打电话,代码如下

Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:" + "135xxxxxxxx");
intent.setData(data);
startActivity(intent);

直接拨号

第二种方法,拨打电话直接进行拨打,但是有些第三方rom(例如:MIUI),不会直接进行拨打,而是要用户进行选择是否拨打,代码如下

Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "135xxxxxxxx");
intent.setData(data);
startActivity(intent);

俩者区别 - 关于拨打电话权限的建议

  • 第一种方法不需要申请权限,可以直接跳转到拨号界面(我经常用到的是这一种,所以都没必要处理动态权限~~!)
  • 第二种方法需要在AndroidMenifest文件里加上这个权限:,在Android6.0中,还要在代码中动态申请权限

三种申请权限的总结概述

  • 第一种方式

    优点: 6.0动态权限的原始状态,必须要了解!虽说不是最底层,但是在三方库越来越多的场景下,你只要了解到他最初的使用场景与方式,就是你自身的优势

    缺点:代码量相对多了一点点,同时处理如用户拒绝权限申请,发起再次权限申请的过程相对麻烦~

  • 第二种方式

    优点:在了解基础的情况下;进行基类封装;主要通过接口回调处理;调用相对简单;场景处理也相对完善

    缺点:代码量因为集中在基类,所以代码量比较大;同时因为使用的接口回调的机制,假若不了解这种机制的话,相对理解比较麻烦

  • 第三种方式 (框架皆属于第三种方式)

    优点:代码简洁,场景处理完善

    缺点:真没发现什么缺点,如果非要找的话… 依赖包导致apk增大,这个算么?