Android6.0以上权限机制及解决方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011200604/article/details/80407033

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);
   }
}
  1. 不再提示的处理:如果用户勾选了不再提示,并且拒绝了权限,那么后面不会再弹框,并且结果一直回调到没有权限,此时根据产品需求处理,

  2. 一般2种方法:在回调中弹出Dialog跳到设置界面开启权限直接弹出提示:没有权限(这种更好),因为用户就是拒绝了权限

当权限页面较多时,可以考虑封装

封装权限机制的方法

由于申请权限的回调onRequestPermissionsResult是在Activity或者Fragment的方法,只能用作回调我们没办法自己写个工具类拿到这个回调,那么参考郭霖的权限机制讲解,封装方法有三种:

  1. 自定义一个PermissionActivity,专门用于处理申请运行时权限操作。该Activity背景透明,用户无法察觉。执行完后finish掉。

  2. RxPermision 开源框架:https://github.com/tbruyelle/RxPermissions ,基本思路是透明的Fragment加入到当前的Activity来处理回调,比上面的方法更巧妙,但是这里必须使用RxJava

  3. 封装BaseActivity去实现运行时权限申请方法,然后所有Activity继承BaseActivity,需要时调用方法即可。(推荐)

封装BaseActivity

  1. 把权限检测放在BaseActivity中,以接口回调的形式通知检测结果
  2. 权限检测不一定在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之前的设备已经在设置里面有权限开关,某些用户会关掉这个权限,然后去拍照什么的,直接崩掉了。

解决方案

  1. 在代码中我们针对Android6.0的权限检测(ContextCompat.checkSelfPermission和requestPermission)按照正常的写,保证在Android6.0以上的设备正常运行。
  2. 然后在具体的操作比如拨号,拍照或者录音,加一层tyr catch,能捕获到异常最好,不能捕获到的话继续第三步。
  3. 对具体机型我们加入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);
   }



猜你喜欢

转载自blog.csdn.net/u011200604/article/details/80407033