龙空技术网

如何优雅地申请Android运行时权限

EAWorld 82

前言:

目前看官们对“android访问权限”大概比较关切,同学们都想要学习一些“android访问权限”的相关内容。那么小编也在网络上网罗了一些有关“android访问权限””的相关知识,希望大家能喜欢,小伙伴们一起来了解一下吧!

​转载本文需注明出处:微信公众号EAWorld,违者必究。

前言:

Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系统标识。在默认情况下任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读取或写入用户的私有数据(例如联系人或电子邮件)、读取或写入其他应用程序的文件、执行网络访问、使设备保持唤醒状态等。

----引用自谷歌Android开发文档

目录:

1、Android权限的演化

2、运行时权限的申请

3、Android权限开源库

4、如何优雅地申请权限

1.Android权限的演化

Android6.0之前

Android6.0之前,应用权限仅需在代码里AndroidManifest.xml中声明便可以获得,不需要征求用户的同意。有的App一股脑申请了大量的权限,甚至一些工具类应用居然申请短信、录音、读取手机文件等敏感权限。当然,那也是流氓软件最盛行的年代,无数应用在后台偷鸡摸狗,盗取用户敏感数据。

Android6.0之后

Android6.0之后,应用权限被谷歌分成了两类,正常权限和危险权限。正常权限在AndroidManifest.xml中声明即可获得,危险权限则需要在使用前向用户申请,征得用户的同意后才可以使用。若没有向用户申请就执行操作,应用直接报错闪退。

危险权限和权限组:

2.运行时权限的申请

使用Android权限的原则

根据谷歌官方文档的说明,建议遵守以下四点原则:

仅使用应用正常工作所需的权限注意库所需的权限公开透明让系统以显式方式访问

简单来说,除非真的需要,否则不要请求获取权限。

如何申请权限

判断是否已获取权限

int hasPermission=ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);if (hasPermission == PackageManager.PERMISSION_GRANTED) {    //已获取权限}else{    //未获取权限      }

申请权限

@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {  if (requestCode == RequestPermissionCode){    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){      //用户同意了权限申请    }else{      //用户拒绝了权限申请,建议向用户解释权限用途    }  }}

在Activity中注册回调

@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {    if (requestCode == RequestPermissionCode){        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){            //用户同意了权限申请        }else{            //用户拒绝了权限申请,建议向用户解释权限用途        }    }}
3.Android权限开源库

通过上述示例看到申请权限代码比较繁琐,需要判断权限、申请权限、在Activity中注册权限申请结果的回调。社区中有很多运行时权限的开源库,下面github上star比较多的这四个。

PermissionsDispatcher

本库基于注解来实现,且支持Java/Kotlin。因为是在你实现的方法上加注解来请求权限,所以代码相对要简洁一些,我们基本上要使用到以下几个注解。

同样,在写完申请完权限后执行的方法后,同样要在Activity的onRequestPermissionsResult中注册回调。

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {              super.onRequestPermissionsResult(requestCode, permissions, grantResults)              // NOTE: delegate the permission handling to generated function              onRequestPermissionsResult(requestCode, grantResults)        }

RxPermissions

同样也是一个优秀的开源库,这个库提供了如同RxJava风格的权限申请方法,代码简洁,只需要AppCompatActivity即可初始化,并可以在任意位置调用。但需要引入RxJava库。

final RxPermissions rxPermissions = new RxPermissions(this);// Must be done during an initialization phase like onCreaterxPermissions      .request(Manifest.permission.CAMERA)      .subscribe(granted -> {          if (granted) { // Always true pre-M               // I can control the camera now          } else {             // Oups permission denied        }});

easypermissions

googlesamples中提供的方法,使用EasyPermissions.requestPermissions申请权限,同时也需要在Activity的onRequestPermissionsResult中注册回调。

AndPermission

仅支持androidx,同样需要Activity来初始化,代码也比较简洁。

AndPermission.with(this)   .runtime()    .permission(Permission.Group.STORAGE)    .onGranted(permissions -> {      // Storage permission are allowed.  })    .onDenied(permissions -> {      // Storage permission are not allowed.  })    .start();

总的来说,每个库都有各自的优缺点,大家可以根据具体需求选用最适合自己项目的库。

4.如何优雅地申请权限

吐槽:开源库代码繁琐,文档有限,问题解答不及时。。。

各自项目有着不同的需求,这些丰富的开源库可能仍然无法满足我们的要求,不仅是权限申请,其他功能也是一样。接下来将手把手带大家造一个简化权限申请代码的轮子。

整体思路

绝大多数开源库在申请权限的时候要在Activity中onRequestPermissionsResult注册回调,这一点我是很反感的,代码侵入性太大了。

假如我封装了一个获取定位的接口,这是一个独立的方法,一般来说会写在LocationUtils.java中,而且任何人任何类类都可能调用我的方法,这就导致LocationUtils是没有Activity去接收onRequestPermissionsResult回调的数据。相信这也是大多数开发者遇到的主要问题之一。

所以,在应用中,我可以加载一个Fragment(和RxPermissions思路类似),在fragment中申请权限,onRequestPermissionsResult回调也放在这个fragment中。这样我在任何位置,只要有Activity存在,都可以加载这个fragment去请求权限,请求完成后再移除这个fragment。

public static void requestPermission(final Activity context, final String[] permissions, PermissionCallback permissionCallback) {          permissionFragment.setOnAttachCallback(new FragmentAttachCallback() {                         @Override                          public void onAttach() {                                    permissionFragment.requestPermission(permissions);                          }       });       permissionFragment.setOnPermissionCallback(permissionCallback);       FragmentTransaction fragmentTransaction = context.getFragmentManager().beginTransaction();       //让我在评论区看到你们的777      fragmentTransaction.add(permissionFragment, "permissionFragment@777").commit();

当然我们也可以借助getTopActivity方法,让权限库自己去获取栈顶的Activity,这样只需要传入需要申请的权限和权限结果的回调即可。

PermissionAnywhere.requestPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}                          , permissionCallback);

其中PermissionCallback中会回调用户点击同意的权限,用户点击拒绝的权限,用户点击不再提示且拒绝的权限三种。

public interface PermissionCallback {      void onComplete(List<String> grantedPermissions, List<String> deniedPermissions, List<String> alwaysDeniedPermissions);}

使用方法

1、在根目录build.gradle中增加

allprojects {      repositories {           ...            maven { url '; }      }}

2、增加依赖

dependencies {      implementation 'com.github.mingyuers:PermissionAnywhere:latest.release'}

代码下载地址

以上完整代码已开源到github:

欢迎大家研究学习,欢迎star,欢迎pr。

延伸

其实也可以使用1px的Activity进行权限申请,这样能否实现在Application中申请权限?会不会引申出别的问题呢?欢迎大家在留言区讨论。

关于作者:明月,现任普元移动团队资深开发工程师,长期致力于IT技术研究,产品设计和开发等工作,擅长Java、NodeJs、ReactNative等领域技术。先后参加深圳登、太保等移动项目的实施,参与Mobile 8.0移动平台的设计开发工作。

关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。

标签: #android访问权限