我们都知道在6.0之前当我们涉及到权限部分,只需要在AndroidMainfest中配置权限就可以,但是在6.0之后Google考虑到了用户隐私性方面的问题,所以增添了动态权限!同时此项更新也是Android 6.0的主要特性 ~
在此我从基础 - 扩展 - 终结 三个阶段进行权限说明,其中每个阶段时间跨度也确实不小,基本都是半年一年;主要原因在于随着工作经验的积累,我把工作中用到的权限处理,总结在此篇内进行补充 ~ 应该性价比还是蛮高的 ~
因掘金右侧目录显示有限,故提前声明完整目录
- 基础认知 - 动态权限
- 基础 - 原始方式(基于此处进行扩展)推荐星数:⭐⭐
- 项目 - 通过接口回调处理权限申请 (2018/6/25 补入内容~ 可扩展思维逻辑,不建议使用)推荐星数:⭐⭐
- 框架 - RxPermissions 动态权限申请 ((2018/12/4 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐
- 框架 - EasyPermissions 动态权限申请 ((2020/6/23 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐
- 课外小知识
- 三种申请权限的总结概述
基础认知 - 动态权限
权限分类
包含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 补入内容~) 推荐使用!!!)推荐星数:⭐⭐⭐⭐
- 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这个框架,但是一直没看,索性最近有用到权限申请就顺带学习了下使用方式,特此记录一下 ~
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增大,这个算么?