我们都知道在6.0之前当我们涉及到权限部分,只需要在AndroidMainfest中配置权限就可以,但是在6.0之后Google考虑到了用户隐私性方面的问题,所以增添了动态权限!同时此项更新也是Android 6.0的主要特性 ~
在此我从基础 - 扩展 - 终结 三个阶段进行权限说明,其中每个阶段时间跨度也确实不小,基本都是半年一年;主要原因在于随着工作经验的积累,我把工作中用到的权限处理,总结在此篇内进行补充 ~ 应该性价比还是蛮高的 ~
简化版解释 :同组权限只需申请一次 ;多处权限申请,只要一次通过,其他地方皆可使用!
细节化解释 :权限是分组的,如上图中的打电话包含7个权限,当我们用到电话权限组其中的某些权限时,只需要申请一次即可!!!因为申请一次的情况下,如果用户已经授权,那么电话权限组内的所有权限都已经默认用户已经同意授权!!!没有必要说读取通讯录请求一次,打电话再请求一次,因为如果第一次用户已经授权的话,授权的是你的权限组,而不是单独的某一个权限!
简化版解释:编译版本的sdk23以前不用管动态权限;Android6.0以前不用管动态权限;但是超过Android6.0、sdk23就必须申请动态权限,不然崩溃!!!
细节化解释:当我们编译使用的是sdk23以前版本的话是有一定几率规避动态权限的问题!但是如果编译的sdk超过sdk23(Android6.0)的话,就需要进行动态权限申请了(早学早用,毕竟是必备的一项技能,都是一些老东西了 - - ~)
以动态申请电话权限为例
<uses-permission android:name="android.permission.CALL_PHONE" />
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;
}
}
}
<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>
在Github有很多好的三方框架,可以快速实现的动态权限的处理,在这里我想先补充的内容是目前我项目中Base内的权限封装~
项目封装 (接口回调方式)
提前知悉(直接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表示有这个权限了,在这里可以直接写自己的逻辑
}
}
});
原作者的Git地址 - RxPermissions
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.0.2'
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
<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这个框架,但是一直没看,索性最近有用到权限申请就顺带学习了下使用方式,特此记录一下 ~
原作者的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);
}
}