Android6.0以上权限机制及解决方案
权限分类
Android权限有100多种不可能每种都去运行时授权,因此google把权限分为两类:
1.普通权限:例如网络请求等,按照老的权限机制
2.危险权限:9种共24个(电话,短信,sd卡,位置,摄像头,传感器,日历,录音,联系人),就是我们要动态申请的。
用adb命令查看危险权限列表:(tip:记住9种24类)
adb shell pm list permissions -d -g
Dangerous Permissions:
电话,
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
联系人
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
日历,
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
摄像头,
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
传感器,
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
位置,
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
sd卡,
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
录音,
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
短信
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,
而不需要用户去点击授权。比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过
兼容问题
项目中targetSdkVersion<23: 按照老的权限机制,只在manifest声明就可以了,运行不会报错,但是会有隐患:
- targetSdkVersion>=23: 这时就需要在代码中检查权限了,否则打开app去执行需要权限的操作会崩溃。如果关闭权限将会弹出提示框提示你开启权限。
2.如果app原先的targetSdkVersion低于23,现在升级到targetSdkVersion=23的app,那么里面的权限默认全部开启!除非用户卸载重装这个app,才是默认关闭所有权限
常规使用
- 首先配置build.gradle,targetSdkVersion版本应该>=23,然后导入support-v4包:
public class MyActivity extends Activity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
btn = (Button) findViewById(R.id.textView);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int check = ContextCompat.checkSelfPermission(MyActivity.this, Manifest.permission.CALL_PHONE);
if(check== PackageManager.PERMISSION_GRANTED){
call();
}else {
ActivityCompat.requestPermissions(MyActivity.this
,new String[]{Manifest.permission.CALL_PHONE}
, 1);
}
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode==1){
if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
call();
}else {
Toast.makeText(MyActivity.this,"没有拨打电话权限",Toast.LENGTH_SHORT).show();
}
}
}
private void call(){
Intent intent = new Intent();
intent.setData(Uri.parse("tel://12312341234"));
intent.setAction(Intent.ACTION_CALL);
startActivity(intent);
}
}
不再提示的处理:如果用户勾选了不再提示,并且拒绝了权限,那么后面不会再弹框,并且结果一直回调到没有权限,此时根据产品需求处理,
一般2种方法:在回调中弹出Dialog跳到设置界面开启权限直接弹出提示:没有权限(这种更好),因为用户就是拒绝了权限
当权限页面较多时,可以考虑封装
封装权限机制的方法
由于申请权限的回调onRequestPermissionsResult是在Activity或者Fragment的方法,只能用作回调我们没办法自己写个工具类拿到这个回调,那么参考郭霖的权限机制讲解,封装方法有三种:
自定义一个PermissionActivity,专门用于处理申请运行时权限操作。该Activity背景透明,用户无法察觉。执行完后finish掉。
RxPermision 开源框架:https://github.com/tbruyelle/RxPermissions ,基本思路是透明的Fragment加入到当前的Activity来处理回调,比上面的方法更巧妙,但是这里必须使用RxJava
封装BaseActivity去实现运行时权限申请方法,然后所有Activity继承BaseActivity,需要时调用方法即可。(推荐)
封装BaseActivity
- 把权限检测放在BaseActivity中,以接口回调的形式通知检测结果
- 权限检测不一定在Activity中,比如在Fragment甚至在某个工具类中,所以BaseActivity中检测权限方法应该是public static,并且通过维护一个Activity栈来获取顶层的Activity
public class BaseActivity extends Activity{
private static OnPermissionCallback callback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BaseApplication.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
BaseApplication.removeActivity(this);
}
public static void requestPermission(String[] permissions, OnPermissionCallback onPermissionCallback){
if(BaseApplication.getTopAcitivity()==null){
return;
}
callback = onPermissionCallback;
List<String> permissionsList = new ArrayList<>();
for(String permission:permissions){
if(ContextCompat.checkSelfPermission(BaseApplication.getTopAcitivity(),permission)!=PackageManager.PERMISSION_GRANTED){
permissionsList.add(permission);
}
}
if(!permissionsList.isEmpty()){
ActivityCompat.requestPermissions(BaseApplication.getTopAcitivity(),permissionsList.toArray(newString[permissionsList.size()]),1);
}else {
if(callback!=null){
callback.onGranted();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults.length>0){
List<String> deniedPermissions = new ArrayList<>();
for(int i=0;i<grantResults.length;i++){
if(grantResults[i]!=PackageManager.PERMISSION_GRANTED){
deniedPermissions.add(permissions[i]);
}
}
if(deniedPermissions.isEmpty()){
if(callback!=null){
callback.onGranted();
}
}else{
if(callback!=null){
callback.onDenied(deniedPermissions);
}
}
}
}
public interface OnPermissionCallback{
void onGranted();
void onDenied(List<String> deniedPermissions);
}
}
BaseApplication: 注意要在Manifest中注册
public class BaseApplication extends Application {
public static List<BaseActivity> activityList = new ArrayList<>();
public static void addActivity(BaseActivity activity){
activityList.add(activity);
}
public static void removeActivity(BaseActivity activity){
activityList.remove(activity);
}
public static BaseActivity getTopAcitivity(){
if(activityList.isEmpty()){
return null;
}
return activityList.get(activityList.size()-1);
}
@Override
public void onCreate() {
super.onCreate();
}
}
Android6.0以前国产机权限处理
Android是在6.0加入的权限机制,但是不少国产手机比如华为小米等,在6.0之前的设备已经在设置里面有权限开关,某些用户会关掉这个权限,然后去拍照什么的,直接崩掉了。
解决方案
- 在代码中我们针对Android6.0的权限检测(ContextCompat.checkSelfPermission和requestPermission)按照正常的写,保证在Android6.0以上的设备正常运行。
- 然后在具体的操作比如拨号,拍照或者录音,加一层tyr catch,能捕获到异常最好,不能捕获到的话继续第三步。
对具体机型我们加入if判断,对操作数据做合法性判断,比如录音生成的数据.
其他关于权限动态申请的第三方库
PermissionGrantor
PermissionsDispatcher
RxPermissions
easypermissions
关于PermissionGrantor的简单使用:
private void requestCemera() {
PermissionsUtil.requestPermission(getApplication(), new PermissionListener() {
@Override
public void permissionGranted(@NonNull String[] permissions) {
Toast.makeText(MainActivity.this, "访问摄像头", Toast.LENGTH_LONG).show();
}
@Override
public void permissionDenied(@NonNull String[] permissions) {
Toast.makeText(MainActivity.this, "用户拒绝了访问摄像头", Toast.LENGTH_LONG).show();
}
}, Manifest.permission.CAMERA);
}
private void requestReadContact() {
PermissionsUtil.TipInfo tip = new PermissionsUtil.TipInfo("注意:", "我就是想看下你的通讯录", "不让看", "打开权限");
PermissionsUtil.requestPermission(this, new PermissionListener() {
@Override
public void permissionGranted(@NonNull String[] permissions) {
JSONArray arr = null;
try {
arr = getContactInfo(MainActivity.this);
if (arr.length() == 0) {
Toast.makeText(MainActivity.this, "请确认通讯录不为空且有访问权限", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, arr.toString(), Toast.LENGTH_LONG).show();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void permissionDenied(@NonNull String[] permissions) {
Toast.makeText(MainActivity.this, "用户拒绝了读取通讯录权限", Toast.LENGTH_LONG).show();
}
}, new String[]{Manifest.permission.READ_CONTACTS}, true, tip);
}
private void requestSms() {
PermissionsUtil.requestPermission(this, new PermissionListener() {
@Override
public void permissionGranted(@NonNull String[] permissions) {
Toast.makeText(MainActivity.this, "访问消息", Toast.LENGTH_LONG).show();
}
@Override
public void permissionDenied(@NonNull String[] permissions) {
Toast.makeText(MainActivity.this, "用户拒绝了读取消息权限", Toast.LENGTH_LONG).show();
}
}, new String[]{Manifest.permission.READ_SMS}, false, null);
}