转载本文需注明出处:微信公众号EAWorld,违者必究。
前言:
Android是一个特权分离的操作系统,其中每个应用程序都有自己唯一的系统标识。默认情况下,任何应用程序都无权执行任何会对其他应用程序、操作系统或用户产生不利影响的操作。这包括读取或写入用户的私人数据(例如,联系人或电子邮件),读取或写入其他应用程序的文件,执行网络访问,保持设备清醒等。
――引自谷歌Android开发文档
目录:
1.Android权限的演变
2.运行时权限申请
3.Android权限开源库
4.如何优雅地申请许可?
1.Android权限的演化
Android6.0之前
在Android6.0之前,应用权限只需要在代码中的AndroidManifest.xml中声明就可以获得,不需要询问用户的权限。有些app申请了很多权限,甚至有些工具类应用居然申请了短信、录音、读取手机文件等敏感权限。当然,那也是流氓软件最盛行的时代,无数应用在后台窃取用户的敏感数据。
Android6.0之后
Android6.0以后,应用权限被Google分为两类,正常权限和危险权限。正常权限可以通过在AndroidManifest.xml中声明来获得,而危险权限需要在使用前向用户申请,只有在用户同意的情况下才能使用。如果在没有应用到用户的情况下执行操作,应用会直接报错闪回。
危险和权限组:
2.运行时权限的申请
使用Android权限的原则
根据谷歌官方文档的说明,建议遵守以下四条原则:
仅使用正常操作所需的权限。请注意,库所需的权限是开放和透明的,因此系统可以显式地访问它们。
简单来说,除非你真的需要,否则不要请求允许。
如何申请权限
判断是否已获取权限
int has permission=context compat . checkselpermission(get application(),manifest . permission . write _ EXTERNAL _ STORAGE);
if(has permission==package manager。PERMISSION_GRANTED) {
//已经获得权限
}否则{
//未获得权限
}
申请权限
@ override public void onrequestpermissions result(Int request code,String[] permissions,Int[]grant results){ if(request code==requestpermissioncode){ if(grant results . length 0 grant results[0]==package manager . permission _ granted){//用户同意了权限申请}else{ //用户拒绝了权限申请,建议向用户说明权限的用途}}
在Activity中注册回调
@覆盖
public void onrequestpermissions result(int request code,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 '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,数据治理,移动架构原创技术分享。