Android运行时请求权限封装
@
本文目的:“借助透明Activity封装一个易于调用的权限请求模块”
1 介绍
Android权限的校验和申请比较简单,但在实际项目中使用时还要进行系统版本的适配,最不友好的是权限的申请结果需要在 onRequestPermissionsResult 中进行判断,如果项目中有多个地方需要申请权限,或者申请权限的代码在自定义的组件中,而申请结果判断则在activity或fragment中,那代码就会显得很乱,对于后期维护也是很不方便,因此我们就来封装一个简单的权限申请模块。
2 测试用例设计
首页我们还是先设计一个权限申请的测试用例步骤:
1、首先判断所需要的权限是否已经过用户授权;
2、如果没有经过用户授权,则申请权限;
3、如果用户授权,则调用相关功能;
4、如果用户拒绝授权,则提示信息。
解析流程:
输入:需要的权限
输出:授权/拒绝
测试用例的代码实现:
String[] permisstions = new String[]; //申请所需要权限
PermissionHelper.request(context,permisstions,new Listener(){
void granted(){
//授权后执行
}
void denied(){
//拒绝后执行
}
});
测试用例实现了输入、输出,其他步骤就需要在模块中实现,对调用方透明。
3 实现
首先我们先实现PermissionHelper类,功能是判断权限是否被授权,如果没有被授权则申请权限。
/**
* 权限帮助类
* Created by lidong on 2019/10/25.
*/
public class PermissionHelper {
/**
* 请求权限
*
* @param context context
* @param permissions 权限
* @param listener 监听
*/
public static void request(Context context, String[] permissions, PermissionListener listener) {
//判断权限,如果所有权限全被授权,直接返回
if (checkPermission(context, permissions)) {
if (listener != null) {
listener.granted();
}
return;
}
//申请权限
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { //6.0以下没有申请权限,直接返回拒绝
if (listener != null) {
listener.denied();
}
} else { //6.0及以上申请权限
//为了统一回调需要借用一个activity
PermissionActivity.open(context, permissions, listener);
}
}
/**
* 校验权限
*
* @param context context
* @param permissions 权限
* @return 所有权限已被授权返回true,否则返回false
*/
protected static boolean checkPermission(Context context, String[] permissions) {
for (String per : permissions) {
int result = PermissionChecker.checkSelfPermission(context, per);
if (result != PermissionChecker.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 权限监听
*/
public interface PermissionListener {
/**
* 所有权限已被授权
*/
void granted();
/**
* 一个或多个权限被拒绝
*/
void denied();
}
}
这里为了方便接收申请权限的结果,我们打开一个新的activity,在新activity中进行权限申请,需要将申请的权限和回调监听传入activity中:
public static void open(Context context, String[] permissions, PermissionHelper.PermissionListener listener) {
Intent intent = new Intent(context, PermissionActivity.class);
intent.putExtra("data", permissions);
context.startActivity(intent);
mPermissionListener = listener;
}
PermissionActivity类:
/**
* 权限申请
* Created by lidong on 2019/10/25.
*/
public class PermissionActivity extends Activity {
private static PermissionHelper.PermissionListener mPermissionListener;
private String[] permissions;
private AlertDialog mAlertDialog;
private boolean first = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
permissions = getIntent().getStringArrayExtra("data");
}
private void requestPermissions(){
ActivityCompat.requestPermissions(this, permissions, 1);
}
@Override
protected void onResume() {
super.onResume();
if (first) {
first = false;
requestPermissions();
return;
}
//切换页面回来重新校验一下
showMissingPermissionDefiniteDialog();
}
/**
* 拒绝授权 显示提示对话框
*/
private void showMissingPermissionDefiniteDialog() {
if (mAlertDialog != null && mAlertDialog.isShowing()) {
mAlertDialog.dismiss();
}
List<String> deniedList = new ArrayList<>();
for (String permission : permissions) {
if (PermissionChecker.checkSelfPermission(this, permission)
== PermissionChecker.PERMISSION_GRANTED) {
continue;
}
deniedList.add(permission);
}
if (deniedList.size() == 0) {
granted();
return;
}
List<String> tipList = transformTip(deniedList);
StringBuilder tip = new StringBuilder();
int i = 0;
for (String temp : tipList) {
if (i > 0) {
tip.append(",");
}
tip.append(temp);
i++;
}
AlertDialog.Builder builder = new AlertDialog.Builder(PermissionActivity.this);
builder.setMessage("应用程序需要以下权限:\n\r" + tip);
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
denied();
}
});
builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
gotoSetting();
}
});
builder.setCancelable(false);
mAlertDialog = builder.show();
}
private List<String> transformTip(List<String> list) {
Map<String, String> permissionList = new HashMap<>();
permissionList.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, "「存储空间」");
permissionList.put(Manifest.permission.READ_PHONE_STATE, "「电话」");
permissionList.put(Manifest.permission.CAMERA, "「相机」");
permissionList.put(Manifest.permission.RECORD_AUDIO, "「录音」");
permissionList.put(Manifest.permission.ACCESS_FINE_LOCATION, "「位置」");
permissionList.put(Manifest.permission.ACCESS_COARSE_LOCATION, "「位置」");
List<String> tipList = new ArrayList<>();
for (String temp : list) {
String tip = permissionList.get(temp);
if (!tipList.contains(tip)) {
tipList.add(tip);
}
}
return tipList;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//判断所有权限是否被授权
if (isGranted(grantResults) && PermissionHelper.checkPermission(this, permissions)) {
granted();
} else {
showMissingPermissionDefiniteDialog();
}
}
/**
* 授权回调
*/
private void granted(){
if (mPermissionListener != null) {
mPermissionListener.granted();
}
finish();
}
/**
* 拒绝回调
*/
private void denied(){
if (mPermissionListener != null) {
mPermissionListener.denied();
}
finish();
}
private boolean isGranted(int[] grantResult) {
for (int result : grantResult) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 跳转到当前应用对应的设置页面
*/
private void gotoSetting() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0, 0);
mPermissionListener = null;
}
public static void open(Context context, String[] permissions, PermissionHelper.PermissionListener listener) {
Intent intent = new Intent(context, PermissionActivity.class);
intent.putExtra("data", permissions);
context.startActivity(intent);
mPermissionListener = listener;
}
}
在 activity 的 onRequestPermissionsResult 方法中,校验授权结果,如果一个或多个权限未被授权,则显示提示对话框,对话框有“设置”按钮跳转到应用程序详情页面,可以进入权限设置页面修改权限。
为了兼容在程序以外的地方手动修改权限,再切换回当前页面的情况,我们在 onResume 方法里增了页面切换回来后重新判断权限是否被授权。
到这里已经封装完成了。
4 用例测试
现在我们完善一下测试用例:
String[] permissions=new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE,
};
PermissionHelper.request(context, permissions, new PermissionHelper.PermissionListener() {
@Override
public void granted() {
showToast("已授权");
}
@Override
public void denied() {
showToast("已拒绝");
}
});
运行测试用例,发现申请权限的activity是白色背景,我们可以在 AndroidManifest.xml 文件中将 activity 设置成全屏透明窗体,用户体验也会好点。
好了,以后无论在哪里需要申请权限,我们只需要这几行代码就够了!
5 总结
申请权限的封装,主要是利用一个透明的activity来完成需要在 activity 中接收结果的情况。其实微信的支付sdk也有用到透明的activity。使用透明activity可以用来将调用和回调代码组装一起,增加代码的可读性和维护性。
END
本文的技术设计和实现都是基于作者工作中的经验总结,如有错误,请留言指正,谢谢。
Android运行时请求权限封装的更多相关文章
- Android 运行时权限处理
引言 Android 6.0 (API 23) 开始引入了运行时权限检查 (Permissions at Run Time),用户不需要在安装时同意授予应用权限,而是在应用运行时动态去申请所需要的权限 ...
- Android 运行时权限处理(from jianshu)
https://www.jianshu.com/p/e1ab1a179fbb 翻译的国外一篇文章. android M 的名字官方刚发布不久,最终正式版即将来临! android在不断发展,最近的更新 ...
- Android之运行时相机权限和联系人权限获取
原文:Android之运行时相机权限和联系人权限获取 本文链接:http://blog.csdn.net/qq_16628781/article/details/61623502 Android之运行 ...
- [快手(AAuto)学习笔记]如何让程序在运行时请求管理员权限(UAC)
作者:ffsystem 作为(糟糕的)程序猿,习惯写代码解决一些简单事务.正常用批处理就能解决大部分工作,复杂一点用AutoIt 3. 有时候要分发给别人,就需要一个界面.外行你程序写得如何他看不懂, ...
- Qt for android运行时出错 Error: Target id 'android--1' is not valid
[提问]windows7下Qt for android运行时出错 Error: Target id 'android--1' is not valid[复制链接] 上一主题下一主题 离线yijun ...
- Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78003184 前段时间在看雪论坛发现了<发现一个安卓万能脱壳方法>这篇 ...
- Android 运行时权限及APP适配
Android 6.0起,Android加强了权限管理,引入运行时权限概念.对于: 1. Android 5.1(API 22)及以前版本,应用权限必须声明在AndroidManifest.xml中, ...
- Android运行时权限
Android 6.0加入了运行时权限这一概念.对于危险权限,应用必须在使用的时候进行申请.可以使用命令行查看危险权限:adb shell pm list permissions -d -g CALE ...
- Android运行时权限开启问题
参考: http://www.cnblogs.com/whoislcj/p/6072718.html(重点这篇) https://www.jianshu.com/p/b4a8b3d4f587 http ...
- Android运行时注入浅析与使用
背景 最近接触新项目,项目中引入了Android Annotation(AA)依赖注入开源框架,代码中大片的注解代码,对于没用过注解框架(或者说没有如此大面积的使用)的我来说确实看得很费力,于是花时间 ...
随机推荐
- C#阿里境外服务器部署企业邮箱发邮件代码
static string accountName = "发件人邮箱"; static string password = "发件人邮箱密码"; static ...
- Angular – CLI
前言 一年半没有写 Angular 了. 现在又要开始写了. 复习过程中也顺便整理一下笔记呗. 介绍 CLI 是 Angular 的辅助工具. 输入一些 command 它会帮你 create 一些有 ...
- Vue Cli 创建项目在 GitHub 部署 history 路由模式
1.修改打包路径 在 vue.config.js 中添加 publicPath 配置,其中 teambition-vue 是你项目的 github 名字.否则会找不到资源. module.expo ...
- 2021年6月国产数据库排行榜:OceanBase、PolarDB会师TiDB、openGauss,入局开源阵营,逐鹿生态建设
"首夏犹清和,芳草亦未歇",时至六月,百花齐放.百家争鸣的国产数据库市场依旧延续着如骄阳般火热的态势.不过从最新一期的 国产数据库流行度排行榜 Top 10 中不难发现,一个词足以 ...
- .NET 白板书写预测-曲线拟合
白板软件书写速度是其最核心的功能,注册StylusPlugin从触摸线程拿触摸点数据并在另一UI线程绘制渲染是比较稳妥的方案,具体的可以查看小伙伴德熙的2019-1-28-WPF-高性能笔 - lin ...
- nestjs 登录和验证码结合验证 svgCaptcha 包 session 会话标识
// ps: 现在用户验证使用 token jwt 了 代替了 session // session 是服务器为每个用户建立的唯一标识 以区分用户 会话标识 // session 是express中的 ...
- 介绍this指向问题
this是js底层定义的变量,代表了代码的指向环境 a 函数的this是window b 方法的this是调用的对象 c 构造函数和原型对象上的方法的this指向实例化对象 d 箭头函数不会自己创建t ...
- 不要慌,FastGPT 告诉我这是技术性调整,利好大 A!
一觉醒来,股市又变天了,到处一片哀嚎,我看了下前几天牛市的赚钱名单,咱们公众号的粉丝没有一个在里面,说实话很失望,希望大家多做些有意义的事情,而不是整天虚度光阴.一个个平时看着都挺厉害,也没赚到钱,我 ...
- 在 Azure CNI 中启用 Calico WireGuard
作者:Peter Kelly 译者:Wendi Wang 注:本文已取得作者本人的翻译授权! 去年6月,Tigera 宣布首次在 K8s 上支持用于集群内加密传输的开源 VPN - WireGuard ...
- ToDesk云电脑推出Web端,这意味着什么?
在数字化转型的浪潮中,云计算技术正在以前所未有的速度改变着我们的生活方式和工作模式.作为云计算领域的一股新生力量,ToDesk云电脑凭借其卓越的性能和便捷的使用体验,一经上线,便赢得了众多用户的青睐. ...