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

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

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

    • 基础认知 - 动态权限
      • 权限分类
      • 前情提要
      • 处理方式
    • 基础 - 原始方式(基于此处进行扩展)推荐星数:⭐⭐
    • 项目 - 通过接口回调处理权限申请 (2018/6/25 补入内容~ 可扩展思维逻辑,不建议使用)推荐星数:⭐⭐
    • 框架 - RxPermissions 动态权限申请 ((2018/12/4 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐
    • 框架 - EasyPermissions 动态权限申请 ((2020/6/23 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐
    • 课外小知识
      • 你是否真的想打电话?
        • 拨号界面
        • 直接拨号
    • 三种申请权限的总结概述
        • 常见场景

基础认知 - 动态权限

权限分类

包含9类27种,其中就有相机,电话,信息等相关权限
Android6.0特性 - 动态权限之实战演练_第1张图片

前情提要

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

细节化解释 :权限是分组的,如上图中的打电话包含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

<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增大,这个算么?

常见场景

14年老文章

判断应用是否具有某个权限

PackageManager pm = getPackageManager();
		boolean permission = (PackageManager.PERMISSION_GRANTED == 
				pm.checkPermission("android.permission.RECORD_AUDIO", "packageName"));
		if (permission) {
			showToast("有这个权限");
		}else {
			showToast("木有这个权限");
		}

获取某个应用的权限清单

	try {
			PackageInfo pack = pm.getPackageInfo("packageName",PackageManager.GET_PERMISSIONS);
			String[] permissionStrings = pack.requestedPermissions;
			showToast("权限清单--->" + permissionStrings.toString());
		} catch (NameNotFoundException e) {
			e.printStackTrace();
	}

16年老文章

检测权限类

/**
 * 检查权限的工具类
 * 

* Created by wangchenlong on 16/1/26. */ public class PermissionsChecker { private final Context mContext; public PermissionsChecker(Context context) { mContext = context.getApplicationContext(); } // 判断权限集合 public boolean lacksPermissions(String... permissions) { for (String permission : permissions) { if (lacksPermission(permission)) { return true; } } return false; } // 判断是否缺少权限 private boolean lacksPermission(String permission) { return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_DENIED; } }

首页

假设首页需要使用权限, 在页面显示前, 即onResume时, 检测权限,
如果缺少, 则进入权限获取页面; 接收返回值, 拒绝权限时, 直接关闭.

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 0; // 请求码

    // 所需的全部权限
    static final String[] PERMISSIONS = new String[]{
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.MODIFY_AUDIO_SETTINGS
    };

    @Bind(R.id.main_t_toolbar) Toolbar mTToolbar;

    private PermissionsChecker mPermissionsChecker; // 权限检测器

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

        setSupportActionBar(mTToolbar);

        mPermissionsChecker = new PermissionsChecker(this);
    }

    @Override protected void onResume() {
        super.onResume();

        // 缺少权限时, 进入权限配置页面
        if (mPermissionsChecker.lacksPermissions(PERMISSIONS)) {
            startPermissionsActivity();
        }
    }

    private void startPermissionsActivity() {
        PermissionsActivity.startActivityForResult(this, REQUEST_CODE, PERMISSIONS);
    }

    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // 拒绝时, 关闭页面, 缺少主要权限, 无法运行
        if (requestCode == REQUEST_CODE && resultCode == PermissionsActivity.PERMISSIONS_DENIED) {
            finish();
        }
    }
}

核心权限必须满足, 如摄像应用, 摄像头权限就是必须的, 如果用户不予授权, 则直接关闭.

授权页
授权页, 首先使用系统默认的授权页, 当用户拒绝时, 指导用户手动设置, 当用户再次操作失败后, 返回继续提示. 用户手动退出授权页时, 给使用页发送授权失败的通知.

/**
 * 权限获取页面
 * 

* Created by wangchenlong on 16/1/26. */ public class PermissionsActivity extends AppCompatActivity { public static final int PERMISSIONS_GRANTED = 0; // 权限授权 public static final int PERMISSIONS_DENIED = 1; // 权限拒绝 private static final int PERMISSION_REQUEST_CODE = 0; // 系统权限管理页面的参数 private static final String EXTRA_PERMISSIONS = "me.chunyu.clwang.permission.extra_permission"; // 权限参数 private static final String PACKAGE_URL_SCHEME = "package:"; // 方案 private PermissionsChecker mChecker; // 权限检测器 private boolean isRequireCheck; // 是否需要系统权限检测 // 启动当前权限页面的公开接口 public static void startActivityForResult(Activity activity, int requestCode, String... permissions) { Intent intent = new Intent(activity, PermissionsActivity.class); intent.putExtra(EXTRA_PERMISSIONS, permissions); ActivityCompat.startActivityForResult(activity, intent, requestCode, null); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getIntent() == null || !getIntent().hasExtra(EXTRA_PERMISSIONS)) { throw new RuntimeException("PermissionsActivity需要使用静态startActivityForResult方法启动!"); } setContentView(R.layout.activity_permissions); mChecker = new PermissionsChecker(this); isRequireCheck = true; } @Override protected void onResume() { super.onResume(); if (isRequireCheck) { String[] permissions = getPermissions(); if (mChecker.lacksPermissions(permissions)) { requestPermissions(permissions); // 请求权限 } else { allPermissionsGranted(); // 全部权限都已获取 } } else { isRequireCheck = true; } } // 返回传递的权限参数 private String[] getPermissions() { return getIntent().getStringArrayExtra(EXTRA_PERMISSIONS); } // 请求权限兼容低版本 private void requestPermissions(String... permissions) { ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE); } // 全部权限均已获取 private void allPermissionsGranted() { setResult(PERMISSIONS_GRANTED); finish(); } /** * 用户权限处理, * 如果全部获取, 则直接过. * 如果权限缺失, 则提示Dialog. * * @param requestCode 请求码 * @param permissions 权限 * @param grantResults 结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE && hasAllPermissionsGranted(grantResults)) { isRequireCheck = true; allPermissionsGranted(); } else { isRequireCheck = false; showMissingPermissionDialog(); } } // 含有全部的权限 private boolean hasAllPermissionsGranted(@NonNull int[] grantResults) { for (int grantResult : grantResults) { if (grantResult == PackageManager.PERMISSION_DENIED) { return false; } } return true; } // 显示缺失权限提示 private void showMissingPermissionDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(PermissionsActivity.this); builder.setTitle(R.string.help); builder.setMessage(R.string.string_help_text); // 拒绝, 退出应用 builder.setNegativeButton(R.string.quit, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(PERMISSIONS_DENIED); finish(); } }); builder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startAppSettings(); } }); builder.show(); } // 启动应用的设置 private void startAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName())); startActivity(intent); } }

你可能感兴趣的:(#,5.0-10.0,新特性,兼容,Android,动态权限,权限申请)