媒体与新闻

媒体报道与出版物

如何优雅地申请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{
  //未获取权限      
}

申请权限
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
MainActivity.RequestPermissionCode);

在Activity中注册回调
@Override
public 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, 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 onCreate
rxPermissions
    .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 grantedPermissions, List deniedPermissions, List alwaysDeniedPermissions);
}

使用方法

1、在根目录build.gradle中增加
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

2、增加依赖
dependencies {
    implementation 'com.github.mingyuers:PermissionAnywhere:latest.release'
}

代码下载地址

以上完整代码已开源到github:
https://github.com/mingyuers/PermissionAnywhere
欢迎大家研究学习,欢迎star,欢迎pr。

延伸

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


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



关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。长按二维码关注!

咨询