Android 动态权限管理
Android 6.0(Marshmallow, 软棉花糖,Api 23),权限分为普通权限和许可权限,许可权限分类归组,该组一个权限被许可后,其他的权限均可使用。
基本介绍
-
普通权限
只需在xml申请即可,使用方法和6.0之前的一样。应用安装后会默认获得许可。
-
许可权限
具体权限分组情况可以使用以下shell命令查看
adb shell pm list permissions -d -g
同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了
-
相关方法
-
ContextCompat.checkSelfPermission()
检查应用是否拥有该权限,若已授权,返回值为PERMISSION_GRANTED,否则返回PERMISSION_DENIED。
-
ActivityCompat.requestPermissions()
该方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED。
-
AppCompatActivity.onRequestPermissionsResult()
该方法类似于Activity的OnActivityResult()的回调方法,主要接收请求授权的返回值。
-
-
需运行时动态申请的权限组
日历
android.permission-group.CALENDAR
相机
android.permission-group.CAMERA
联系人
android.permission-group.CONTACTS
定位
android.permission-group.LOCATION
耳机
android.permission-group.MICROPHONE
电话
android.permission-group.PHONE
传感器
android.permission-group.SENSORS
短信
android.permission-group.SMS
存储
android.permission-group.STORAGE
权限适配解决方案
基本方案(原生方法)
原生方案就是采用android系统提供的权限相关的方法来动态的做权限适配处理,是其他解决方案的基础
我这里来测试读取联系人和打电话这两个6.0之后要在运行时赋予的权限。
MainActivity.java文件内容如下:
package com.rainmonth.myapplication;
import android.Manifest;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// 权限允许对应的Map
private Map<Integer, Runnable> allowPermissionRunnableMap = new HashMap<>();
// 权限拒绝对应的Map
private Map<Integer, Runnable> denyPermissionRunnableMap = new HashMap<>();
private Button btCallPhone;
private Button btContact;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btCallPhone = (Button) findViewById(R.id.btn_call_phones);
btContact = (Button) findViewById(R.id.btn_contacts);
btCallPhone.setOnClickListener(this);
btContact.setOnClickListener(this);
}
/**
* 请求权限
*
* @param requestCode 请求授权的id 唯一标识即可
* @param requestPermission 请求的权限
* @param allowRunnable 同意授权后的操作
* @param denyRunnable 禁止权限后的操作
*/
protected void requestPermission(int requestCode, String requestPermission, Runnable allowRunnable, Runnable denyRunnable) {
if (allowRunnable == null) {
throw new IllegalArgumentException("allowableRunnable == null");
}
allowPermissionRunnableMap.put(requestCode, allowRunnable);
if (denyRunnable != null) {
denyPermissionRunnableMap.put(requestCode, denyRunnable);
}
//版本判断,6.0以上才予以处理
if (Build.VERSION.SDK_INT >= 23) {
//检查是否拥有权限
int checkCallPhonePermission = ContextCompat.checkSelfPermission(getApplicationContext(), requestPermission);
if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
//弹出对话框提示用户赋予该权限
ActivityCompat.requestPermissions(this, new String[]{requestPermission}, requestCode);
} else {
allowRunnable.run();
}
} else {
allowRunnable.run();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 其实还可以根据requestCode进行区分处理(不同的权限申请,采用不同的处理方式)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Runnable allowRun = allowPermissionRunnableMap.get(requestCode);
allowRun.run();
} else {
Runnable disallowRun = denyPermissionRunnableMap.get(requestCode);
disallowRun.run();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_call_phones:
requestPermission(1, Manifest.permission.CALL_PHONE, new Runnable() {
@Override
public void run() {
callPhone();
}
}, new Runnable() {
@Override
public void run() {
callPhoneDenied();
}
});
break;
case R.id.btn_contacts:
requestPermission(2, Manifest.permission.READ_CONTACTS, new Runnable() {
@Override
public void run() {
readContact();
}
}, new Runnable() {
@Override
public void run() {
readContactDenied();
}
});
break;
}
}
/**
* 模拟赋予电话权限后打电话的方法
*/
private void callPhone() {
Toast.makeText(MainActivity.this, "CALL_PHONE OK", Toast.LENGTH_SHORT).show();
}
/**
* 模拟拒绝赋予电话权限后处理的方法,一般用来做街面上的兼容处理,以提醒用户去设置相关的权限
*/
private void callPhoneDenied() {
Toast.makeText(MainActivity.this, "CALL_PHONE Denied", Toast.LENGTH_SHORT).show();
}
/**
* 模拟赋予联系权限后的方法
*/
private void readContact() {
ContentResolver cr = getContentResolver();
String str[] = {ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.PHOTO_ID};
Cursor cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, str, null, null, null);
int count = cur.getCount();
cur.close();
Toast.makeText(MainActivity.this, String.format("发现%s条", count), Toast.LENGTH_SHORT).show();
}
/**
* 模拟拒绝赋予联系*限后处理的方法,一般用来做街面上的兼容处理,以提醒用户去设置相关的权限
*/
private void readContactDenied() {
Toast.makeText(MainActivity.this, "Contact Denied", Toast.LENGTH_SHORT).show();
}
}
activity_main.xml文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.rainmonth.myapplication.MainActivity">
<Button
android:id="@+id/btn_contacts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Contacts" />
<Button
android:id="@+id/btn_call_phones"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Call PHones" />
</LinearLayout>
权限申请结果的处理主要在onRequestPermissionsResult中,而处理权限申请所用到的两个HashMap则是在requestPermissions就传递过去了。根据requestCode来在map中查找对应的Runnable对象来处理。
RxJava + RxPermissions方案
在了解了RxJava后,若果你看到上述代码就会觉得上面的基本方案肯定能有一种RxJava式的封装来解决(基本方案中其处理核心就是利用系统提供的onRequestPermissionsResult这个回调方法,RxJava可以很好的解决回调问题),没错,RxPermissions就应运而生了。
RxPermissions的源码还没时间具体分析,先看看它的使用方式。
-
引入
添加依赖
// RxJava 版本小于2.0
dependencies {
compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.0@aar'
}
// RxJava 版本2.0以上
dependencies {
compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.8.2@aar'
} -
直接申请权限(以Manifest.permission.CAMERA为例)
RxPermissions.getInstance(this)
.request(Manifest.permission.CAMERA)
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean granted) {
if (granted) { // 在android 6.0之前会默认返回true
// 已经获取权限
String jpgPath = getCacheDir() + "test.jpg";
takePhotoByPath(jpgPath, 2);
} else {
// 未获取权限
Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
}
}
}); -
条件触发获取权限(可以结合RxBinding使用)
RxView.clicks(findViewById(R.id.request_permission))
.compose(RxPermissions.getInstance(this).ensure(Manifest.permission.CAMERA))
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean granted) {
if (granted) { // 在android 6.0之前会默认返回true
// 已经获取权限
String jpgPath = getCacheDir() + "test.jpg";
takePhotoByPath(jpgPath, 2);
} else {
// 未获取权限
Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
}
}
}); -
同时请求多个权限(合并结果)
以同时申请拍照和录音权限,代码如下:
RxPermissions.getInstance(MainActivity.this).request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean granted) {
if (granted) { // 在android 6.0之前会默认返回true
// 已经获取权限
String jpgPath = getCacheDir() + "test.jpg";
takePhotoByPath(jpgPath, 2);
} else {
// 未获取权限
Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
}
}
});上面这种请求权限方式,只有所有的权限请求都同意之后,才会返回true,有一个为false,则返回false
-
同时获取多个权限(分别获取结果)
RxPermissions.getInstance(MainActivity.this).requestEach(Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO)
.subscribe(new Action1<Permission>() {
@Override
public void call(Permission permission) {
if (permission.name.equals(Manifest.permission.CAMERA)) {
if (permission.granted) {
String jpgPath = getCacheDir() + "test.jpg";
takePhotoByPath(jpgPath, 2);
} else {
// 未获取权限
Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
}
} else if (permission.name.equals(Manifest.permission.RECORD_AUDIO)) {
}
}
});
RxJava的这种链式写法很好的应用在权限请求上了,哈哈!
其他与权限申请相关的项目
使用标注的方式,动态生成类处理运行时权限,目前还不支持嵌套Fragment。
简化运行时权限处理
Google官方的例子