Android6.0权限适配及兼容库的实现

简介: Android6.0权限适配及兼容库的实现

从6.0 MarshMallow开始,Android支持动态权限管理,即有些权限需要在使用到的时候动态申请,根据用户的选择需要有不同的处理,具体表现可以看下图:

image.png

本文并不关心权限适配的原理,原理可以参考Android权限管理原理
,这里只是针对6.0中的表现做适配,先思考以下几个问题:


  • 为什么6.0权限需要适配
  • 什么权限需要动态适配
  • 怎样动态适配权限
  • 怎么样实现第三方库,简化代码及适配流程   权限兼容库 PermissionCompat
  • 对于国产ROM的影响


为什么6.0需要权限适配


6.0之前Android的权限都是在安装的时候授予的,6.0之后,为了简化安装流程,并且方便用户控制权限,Android允许在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,当运行在Android 6.0 +的手机上时,就会调用6.0相关的API,达到动态控制权限的目的。但是,如果仅仅是将targetSdkVersion设置为23,而在代码层面没有针对Android 6.0做适配,就可能在申请系统服务的时候,由于权限不足,引发崩溃。


  • targetSDKVersion:该属性用于通知系统,您已针对目标版本进行测试,标识App能够适配的系统版本,有些新的API是只有新的系统才有的。


什么权限需要动态适配


并非所有的权限都需要动态申请,Android6.0将权限分为两种,普通权限跟敏感(危险)权限,普通权限是不需要动态申请的,但是敏感权限需要动态申请。


  • 1、普通权限(Normal permissions):不会泄露用户隐私,同时也不会导致手机安全问题。如网络请求权限、WIFI状态等,这类权限只需要在Manifest列出来,之后,系统会自动赋给APP权限:
  • ACCESS_NETWORK_STATE
  • ACCESS_NOTIFICATION_POLICY
  • ACCESS_WIFI_STATE
  • BLUETOOTH
  • BLUETOOTH_ADMIN
  • 2、敏感权限(Dangerous permissions):与�普通权限对应,可能会影响用户的隐私,存储数据等,比如拍照、存储、通讯录、地理GPS等,这类权限需要在Manifest列出来,在需要的的时候,显示的请求用户准许。
  • CALENDAR
  • CAMERA
  • CONTACTS
  • LOCATION
  • PHONE
  • SENSORS
  • SMS
  • STORAGE


敏感权限的请求是按照分组进行提醒的,并非仅仅针对一条,比如通讯录的读取权限与写权限,只要一个权限获到,下次请求权限的时候会自动提供,当然也要请求。否则还是有问题。


  • 3、特殊权限(Special Permissions) --不在本文分析范围
    There are a couple of permissions that don't behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS


怎样动态适配权限


对于敏感权限的适配有一个原则,那就是实时检查,因为权限随时可能被回收,比如用户可以在设置里面把权限给取消,但是APP并不一定知道,因此每次都需要检查,一旦没有,就需要请求,之后,根据返回结果处理后续逻辑。


实现步骤


  • 1、在Manifest中列出来
    无论普通权限还是敏感权限,都需要在Manifest中列出来,同时也是对6.0之前的版本的一种兼容。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.snail.labaffinity">
  <uses-permission android:name="android.permission.CAMERA"/>
  <uses-permission android:name="android.permission.CALL_PHONE"/>
  • 2、需要时,显示的请求


在权限没被授予前提下,系统会显示授权对话框,让用户操作,目前授权对话框不可定制,不过可以在申请之前添加一些解释,告诉用户为什么需要该权限,但是Google提醒,不要做过多的解释,可能会使用户感到厌烦,用法如下:

ActivityCompat.requestPermissions(target.getActivity(), permissions,

requestCode);


public static void requestPermissions(final @NonNull Activity activity,
        final @NonNull String[] permissions, final int requestCode) {
        if (Build.VERSION.SDK_INT >= 23) {
                   ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);
      } else if (activity instanceof OnRequestPermissionsResultCallback) {
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                final int[] grantResults = new int[permissions.length];
                PackageManager packageManager = activity.getPackageManager();
                String packageName = activity.getPackageName();
                final int permissionCount = permissions.length;
                for (int i = 0; i < permissionCount; i++) {
                    grantResults[i] = packageManager.checkPermission(
                            permissions[i], packageName);
                }
                ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                        requestCode, permissions, grantResults);
            }
        });
    }
}

3、处理授权回调


  • 兼容6.0之前的处理:在这里只需要处理获得权限即可,因为6.0之前只存在Install权限,一旦安装,所有权限都是默认授予的,虽然国内ROM对权限管理做了自己的一些定制,但基本都是兼容的。
  • 需要对6.0的授权成功、失败、永不询问做处理
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(this.mOnGrantedListener != null) {
    <!--6.0之前-->
        if(PermissionUtils.getTargetSdkVersion(this) < 23 && !PermissionUtils.hasSelfPermissions(this, permissions)) {
            this.mOnGrantedListener.onGranted(this, permissions);
            return;
        }            
        //<!--6.0之后-->  需要根据结果进行验证
        if(PermissionUtils.verifyPermissions(grantResults)) {
            this.mOnGrantedListener.onGranted(this, permissions);
        } else if(!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
            this.mOnGrantedListener.onNeverAsk(this, permissions);
        } else {
            this.mOnGrantedListener.onDenied(this, permissions);
        }
    }
  }


具体APP中不同的实现方案


  • 1、简单的封装回调
  • 2、基于APT,采用注解方式简化编码逻辑,自动封封回调


先看一下直接回调的方式


采用最直接的回调


首先在基类Activity或者Fragment中统一设置授权回调监听,这里我们用一个

 public class BasePermissionCompatActivity extends AppCompatActivity {
    private SparseArray<OnGrantedListener<BasePermissionCompatActivity>> mOnGrantedListeners = new SparseArray<>();
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        OnGrantedListener<BasePermissionCompatActivity> listener = mOnGrantedListeners.get(requestCode);
        if (listener == null)
            return;
        if (PermissionUtils.verifyPermissions(grantResults)) {
            listener.onGranted(this, permissions);
        } else {
            if (PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
                listener.onDenied(this, permissions);
            } else {
                listener.onNeverAsk(this, permissions);
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mOnGrantedListeners.clear();
        mOnGrantedListeners = null;
    }
    public void requestPermissions(final @NonNull String[] permissions, OnGrantedListener<BasePermissionCompatActivity> onGrantedListener) {
        int requestCode = getNextRequestCode();
        ActivityCompat.requestPermissions(this, permissions, requestCode);
        mOnGrantedListeners.put(requestCode, onGrantedListener);
    }
    private static int sNextCode;
    private static int getNextRequestCode() {
        return sNextCode++;
    }
}

之后在需要时候的请求,并根据结果处理后续逻辑即可。

   requestPermissions(activity, P_CAMERA, new OnGrantedListener() {
        // 根据permissions自行处理,可合并,可分开
        @Override
        public void onGranted(SecondActivity target, String[] permissions,int requestCode) {
        }
        @Override
        public void onDenied(SecondActivity target, String[] permissions,int requestCode) {
        }
        @Override
        public void onNeverAsk(SecondActivity target, String[] permissions,int requestCode) {
        }
        @Override
        public void onShowRationale(SecondActivity target, String[] permissions,int requestCode) {
    });


上面的方法比较直接,灵活,不过每次都要自己实现回调监听Listener,接下来看第二种实现,基于APT,通过注解的方式,自动添加Listener,这种实现参考了ButterKnife的实现方式。


基于APT与注解,编译过程中生成代码,自动添加回调


  • 1、基于APT,定义一系列Annotation,并动态生成辅助Listener类
  • 2、添加Android支持库,在基类统一处理回调,
  • 3、添加工具类,连接绑定Listener与Activity(Fragment)


相应的实现分三个库:


  • 注解库
  • APT生成支持库
  • Android支持库


注解库:


image.png

主要用来定义一些回调方法注解、及请求实体的类注解

* ActivityPermission
* FragmentPermission
* OnDenied
* OnGranted
* OnGrantedListener
* OnNeverAsk
* OnShowRationale


APT生成支持库


主要用来在编译阶段,动态生Listener类

PermissionProcessor.java

部分参考代码:

@AutoService(Processor.class)
public class PermissionProcessor extends AbstractProcessor {
    private Elements elementUtils;
    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(OnDenied.class);
        annotations.add(OnGranted.class);
        annotations.add(OnNeverAsk.class);
        annotations.add(OnShowRationale.class);
        return annotations;
    }
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        elementUtils = env.getElementUtils();
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!checkIntegrity(roundEnv))
            return false;
        Set<? extends Element> elementActivities = roundEnv.getElementsAnnotatedWith(ActivityPermission.class);
        Set<? extends Element> elementFragments = roundEnv.getElementsAnnotatedWith(FragmentPermission.class);
        return makeListenerJavaFile(elementActivities) && makeListenerJavaFile(elementFragments);
    }
   ...


Android支持库


主要会封装了一些工具类,基类以及对回调的处理

* BasePermissionCompatActivity.java
* BasePermissionCompatFragment.java
* PermissionCompat.java
* PermissionUtils.java

参考代码:

public class PermissionCompat {
    private static int sNextRequestCode;
    static final Map<Class<?>, OnGrantedListener> BINDERS = new LinkedHashMap<>();
    // 分批次请求权限
    public static void requestPermission(BasePermissionCompatActivity target, String[] permissions) {
        Class<?> targetClass = target.getClass();
        try {
           // 找到监听Listener类,并实例一个
            OnGrantedListener<BasePermissionCompatActivity> listener = findOnGrantedListenerForClass(targetClass, permissions);
            if (PermissionUtils.hasSelfPermissions(target, permissions)) {
                listener.onGranted(target, permissions);
            } else if (PermissionUtils.shouldShowRequestPermissionRationale(target, permissions)) {
                // 拒绝过,再次请求的时候,这个函数是否有必要,不在询问后,返回false,第一次返回false,
                //listener.onShowRationale(target, permissions);
                startRequest(target, listener, permissions);
            } else {
                startRequest(target, listener, permissions);
            }
        } catch (Exception e) {
            throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
        }
    }
    private static void startRequest(BasePermissionCompatActivity target, OnGrantedListener listener, final @NonNull String[] permissions) {
        target.setOnGrantedListener(listener);
        ActivityCompat.requestPermissions(target, permissions, getNextRequestCode());
    }


使用


  • 1、Activity继承BasePermissionCompatActivity
  • 2、用注解写回调函数,支持权限分组,跟单独处理,但是每个分组都要写自己的回调函数(目前回调函数,不支持参数)
  • 3、回调必需配套,也就是一个权限必须对应四个函数,否则编译不通过
  • 4、请求的权限必须有回调函数,不然报运行时错误--崩溃
  @ActivityPermission
  public class PermssionActivity extends BasePermissionCompatActivity {
      。。。
      @OnGranted(value = {Manifest.permission.CAMERA})
      void granted() {
          LogUtils.v("granted");
      }
      @OnDenied(value = {Manifest.permission.CAMERA})
      void onDenied() {
          LogUtils.v("onDenied");
      }
      @OnNeverAsk(value = {Manifest.permission.CAMERA})
      void OnNeverAsk() {
          LogUtils.v("OnNeverAsk");
      }
      @OnShowRationale(value = {Manifest.permission.CAMERA})
      void OnShowRationale() {
          LogUtils.v("OnShowRationale");
      }    
      <!--何时的时机调用-->
      @OnClick(R.id.get)
      void get() {
          PermissionCompat.requestPermission(this, new String[]{Manifest.permission.CAMERA});
      }
  }


国产ROM兼容性


6.0之前权限管理即不是原生功能又没有制定相应标准,每个厂家的实现都是完全不同的,虽然4.3 Google官方试图推出AppOpsManager来动态适配权限管理,但由于不成熟,一直到6.0也没走向前台。不过,看6.0之前国内ROM的表现,基本是在每个服务内部触发鉴权请求,对原生权限的判断并没多大影响,所以兼容没太大问题。


提醒:记得区分是不是自己的App要权限,比如拍照,是调用系统相机,还是自己open Camera,两者是有区别的。


目录
相关文章
|
20天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
4天前
|
Android开发 开发者
Android打开开发者权限
Android打开开发者权限
13 0
|
4天前
|
Android开发
Android 9.0中sdcard 的权限和挂载问题
Android 9.0中sdcard 的权限和挂载问题
10 0
|
4天前
|
Android开发
Android修改默认system/bin/下可执行程序拥有者和权限,使用实例,只有root和系统app权限才能执行某个命令。
Android修改默认system/bin/下可执行程序拥有者和权限,使用实例,只有root和系统app权限才能执行某个命令。
12 0
|
4天前
|
Android开发
android 12 U盘 /mnt/media_rw 下读取文件异常 没有权限
android 12 U盘 /mnt/media_rw 下读取文件异常 没有权限
10 0
|
4天前
|
XML Android开发 数据安全/隐私保护
android 11后文件读写访问权限申请
android 11后文件读写访问权限申请
12 0
|
4天前
|
Android开发
安卓动态申请权限
安卓动态申请权限
15 0
|
5天前
|
Android开发 C++
Android S HAL库的编译
Android S HAL库的编译
10 0
|
6天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
18天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
55 1