hook Android 权限请求, 插入权限目的dialog显示
Permission_aim_tip
作用
因为越来越严格的隐私政策要求,需要在申请权限的时候,告知用户需要该权限的目的。为了能快速适配已有项目,需要一个能自动感知权限申请,并显示申请原因的框架。于是编写了该框架。
效果
特点
- 100%拦截fragment的权限请求
- 100%拦截 RxPermission的权限请求(因为RxPermission就是基于Fragment)
- 方便配置,使用json配置
- 集成简便,一行代码即可。
- 可定制UI
使用
注册代理
只需要在Application中注册即可使用
public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();//注册代理,一句话即可使用PermissionAimTipHelper.init(new TRSTipShowController(new RawAimTipAdapter(this, R.raw.permission_aim_description)));}
}
类说明
类名 作用 备注 PermissionAimTipHelper 核心类,用于拦截通过ActivityCompat调用权限的过程,唯一构造参数为AimTipShowController 必须调用init方法,之后才能通过getInstance获取实例。否则会报错。 AimTipShowController 接口,在拦截到权限请求的时候,通过该类来显示提示信息。 TRSTipShowController AimTipShowController的实现类,需要两个构造参数,其中之一是AimTipAdapter,通过AimTipAdapter将用户申请的权限转换为可以显示的语义化文字。还有一个是DialogStyleData 可以指定dialog的样式,可以缺省。 AimTipAdapter 抽象类,定义了从android权限到需要显示信息的抽象过程 RawAimTipAdapter AimTipAdapter的实现类,实现了加载raw目录下的配置文件 DialogStyleData 用来保存dialog的布局文件id,和item的布局文件id ,以此来实现样式的自定义
填写配置文件
其中的R.raw.permission_aim_description 是配置文件的id (保存在raw文件夹下)。配置文件如下
[{"androidPermissionNames": ["android.permission.ACCESS_FINE_LOCATION","android.permission.ACCESS_COARSE_LOCATION"],"showPermissionName": "定位 GPS定位,WIFI定位","permissionAimDescription": "用于新闻下微站展示,自动定位区县栏目展示场景"},{"androidPermissionNames": ["android.permission.WRITE_EXTERNAL_STORAGE","android.permission.READ_EXTERNAL_STORAGE"],"showPermissionName": "内存读,写","permissionAimDescription": "用于APP写入/下载/保存/读取图片、文件等信息"},{"androidPermissionNames": ["android.permission.CAMERA"],"showPermissionName": "访问摄像头","permissionAimDescription": "用于拍照、录制视频、扫一扫AR识别等场景"},{"androidPermissionNames": ["android.permission.RECORD_AUDIO"],"showPermissionName": "录音功能","permissionAimDescription": "通过手机和耳机的麦克 用于录音、语音检索等场景"}]
配置说明
字段名称 | 用途 |
---|---|
androidPermissionNames | 用来配置对应的权限,如果用户申请的权限包括在其中。那么就会提示用户。必须是Manifest.permission中定义的常量 |
showPermissionName | 用于显示给用户看的权限名称 |
permissionAimDescription | 权限目的的描述 |
Activity中使用
直接使用Activity的requestPermissions方法,将无法拦截。需要使用以下方式请求权限才能拦截
ActivityCompat.requestPermissions(this, locationPermission, 100);
其中的ActivityCompat是Android本身的适配库
样式自定义
原理是通过指定布局ID来替换样式,只需要在布局ID中出现以下控件即可。
<?xml version="1.0" encoding="utf-8"?>
<resources><!--用来获取recycleView的id,控件必须是RecycleView--><item name="aim_tip_id_recycle_view" type="id"/><!--在item布局中用于显示权限名称,控件必须是TextView--><item name="aim_tip_id_item_title" type="id"/><!--在item布局中用于显示权限的目的,控件必须是TextView--><item name="aim_tip_id_item_content" type="id"/>
</resources>
设置样式
DialogStyleData dialogStyleData = new DialogStyleData(R.layout.custom_dialog, DialogStyleData.USE_DEFAULT_STYLE);//修改样式PermissionAimTipHelper.getInstance().setShowController(new TRSTipShowController(new RawAimTipAdapter(v.getContext(), R.raw.permission_aim_description), dialogStyleData));yleData));
运行Demo
更多使用细节请参考这个项目,这是一个Demo项目。
源码
zhuguohui/permission-aim-tip
原理
简单的说一句,从fragment中发起的权限请求最后都会转发到FragmentActivity中的requestPermissionsFromFragment方法。而这个方法的具体的实现是由ActivityCompat实现的
ActivityCompat中的实现
可以看到ActivityCompat中可以设置一个代理,来自己处置权限申请。于是我们就通过这个代理来实现。需要注意的是,我们弹出提示框,用户点击同意以后,需要将FragmentActivity中的变量mRequestedPermissionsFromFragment重新置为true就可以回调到原来的fragment。整个流程就不会有影响。
核心类源码
这一切都是通过PermissionAimTipHelper实现的,其他的弹出提示框都是简单的内容,无需赘言。
package com.trs.app.aim_tip;import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentActivity;import com.trs.app.aim_tip.dialog.AimTipShowController;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;/*** Created by zhuguohui* Date: 2022/4/14* Time: 10:14* Desc:用于提示申请权限的目的的。* 在申请权限的时候自动提示。*/
public class PermissionAimTipHelper implements ActivityCompat.PermissionCompatDelegate {static PermissionAimTipHelper instance;/*** 这个字段是FragmentActivity中所有的。如果fragment发起权限申请会被置为true。* 但是在Delegate的requestPermissions 方法执行后会必然被置为false。* 因此无法正确的回调到fragment的请求中。需要手动设置为true。*/private Field fromFragmentField;private AimTipShowController showController;private static boolean callInitMethod=false;private PermissionAimTipHelper(AimTipShowController showController) {this.showController = showController;try {fromFragmentField = FragmentActivity.class.getDeclaredField("mRequestedPermissionsFromFragment");fromFragmentField.setAccessible(true);} catch (NoSuchFieldException e) {e.printStackTrace();}}public static synchronized PermissionAimTipHelper getInstance() {if(!callInitMethod){throw new IllegalStateException("请先调用init方法进行初始化");}return instance;}public void setShowController(AimTipShowController showController) {this.showController = showController;}/*** 入口函数* @param showController*/public static synchronized void init(AimTipShowController showController) {if (instance != null) {return;}callInitMethod=true;instance = new PermissionAimTipHelper(showController);ActivityCompat.setPermissionCompatDelegate(instance);}@Overridepublic boolean requestPermissions(@NonNull Activity activity, @NonNull String[] permissions, int requestCode) {boolean fromFragment = false;FragmentActivity fragmentActivity = null;if (activity instanceof FragmentActivity && fromFragmentField != null) {fragmentActivity = (FragmentActivity) activity;//检查是否是来自fragment的请求try {fromFragment = (boolean) fromFragmentField.get(fragmentActivity);} catch (IllegalAccessException e) {e.printStackTrace();}}boolean finalFromFragment = fromFragment;FragmentActivity finalFragmentActivity = fragmentActivity;//过滤已经获取的权限,避免重复提示。String[] needPermissions=getNeedPermissions(activity,permissions);if(needPermissions.length==0) {//已经授予全部权限,不需要拦截弹出提示框。return false;}showController.showTipDialog(activity, needPermissions, requestCode, () -> {if (finalFromFragment) {//将字段重置try {fromFragmentField.set(finalFragmentActivity, true);} catch (IllegalAccessException e) {e.printStackTrace();}}requestPermissionsDefaultImpl(activity, permissions, requestCode);});return true;}private String[] getNeedPermissions(Activity activity, String[] permissions) {PackageManager packageManager = activity.getPackageManager();String packageName = activity.getPackageName();List<String> permissionList=new ArrayList<>();for (String permission : permissions) {if (checkNeedPermission(permission, packageManager, packageName)) {permissionList.add(permission);}}return permissionList.toArray(new String[]{});}private boolean checkNeedPermission(String string,PackageManager packageManager,String PackageName) {int state = packageManager.checkPermission(string, PackageName);if (PackageManager.PERMISSION_GRANTED ==state){//已经授予获取已经拒绝就不需要重复获取return false;}return true;}@Overridepublic boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) {return false;}/*** 从ActivityCompat.requestPermissions()方法,copy过来的。* 也就是默认的实现** @param activity* @param permissions* @param requestCode*/@SuppressLint("RestrictedApi")private void requestPermissionsDefaultImpl(final @NonNull Activity activity,final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {if (Build.VERSION.SDK_INT >= 23) {if (activity instanceof ActivityCompat.RequestPermissionsRequestCodeValidator) {((ActivityCompat.RequestPermissionsRequestCodeValidator) activity).validateRequestPermissionsRequestCode(requestCode);}activity.requestPermissions(permissions, requestCode);} else if (activity instanceof ActivityCompat.OnRequestPermissionsResultCallback) {Handler handler = new Handler(Looper.getMainLooper());handler.post(new Runnable() {@Overridepublic 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);}((ActivityCompat.OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(requestCode, permissions, grantResults);}});}}}
hook Android 权限请求, 插入权限目的dialog显示相关推荐
- 软件获取手机的ime权限_你手机上软件的权限请求,都要同意吗,会不会泄漏你的隐私?...
生活中,在你使用各种手机软件的时候,不知道你有没有注意到,在软件第一次安装打开的时候,有大多数软件会弹出一个权限请求的弹框,显示出应用要请求获取什么权限以及使用这些权限的一些情况说明.我相信大多数人可 ...
- 如何在Android 11 中正确请求位置权限?以及Android 8 - 11位置权限的变化及适配方法!
由于现在位置信息变为了敏感数据,因此Android限制了它的使用,尤其在APP后台. 在Android 9 之前,位置权限没有按照前后台分离,APP在前台和后台使用相同的资源. 但是,Google开始 ...
- [Android Studio]详细讲解Android6.0以上请求应用权限(解决请求权限窗口一闪而过的问题)
现在的主流手机的Android版本都是8.0/9.0(笔者的华为手机为9.0),所以在开发Android的时候尽量使用Android8.0的版本(SDK 26).随着Android版本的提升,系统的安 ...
- Android ActivityResultContracts 请求权限(封装;含android 11权限变更)
文章目录 关联博客 Android 11 权限变更 权限申请 BasePermissionExtendFragment.kt PermissionExtend.kt 关联博客 Android Acti ...
- Android数据手册02:android.permission权限请求汇总
在Android开发中,当程序执行需要操作安全敏感项时,必须在androidmanifest.xml中声明相关权限请求. 比如,声明拨打电话的权限请求方法如下: 1 <uses-permissi ...
- Xamarin Android权限请求
Xamarin Android权限请求 Android权限规定了App是否可以访问特定的资源,如网络.电话和短信.在原有API 6.0之前,App在安全的时候,会请求一次权限.一旦安装后,App就可以 ...
- android危险权限分组,Android 6.0权限请求相关及权限分组方法
Android M(6.0)API 23后加入了权限请求设置,APP需要使用某些权限需要主动申请. 权限分为3类,一组是Normal权限,无需申请,另一组是Dangerous,需申请,然后是特殊权限, ...
- Android地图权限处理,Android 使用地图时的权限请求方法
在初始化自己位置的时候请求定位权限: Constants.ACCESS_FINE_LOCATION_COMMANDS_REQUEST_CODE是自定义的常量值==0x01 if (ContextCom ...
- android 权限开启回调,Android M请求onSurfaceTextureAvailable回调权限不在活动
症状:首次启动我的应用程序崩溃java.lang.SecurityException: Lacking privileges to access camera service.我收到了"不幸 ...
最新文章
- Xcode调用栈时小图标代表什么意思
- Asp.Net Core 快速邮件队列设计与实现
- Oracle 20c 新特性:XGBoost 机器学习算法和 AutoML 的支持
- 完全备份、差异备份以及增量备份的区别
- win7右键没有新建文件夹了
- 北京地铁挤,最挤昌平线
- 高级文本编辑器——Kate
- java笔记框架部分
- matlab电磁场,基于matlab的电磁场分析.pdf
- 单层工业厂房设计原理以及知识重点
- java 斑马 打印不出来_通过PrintServer将原始ZPL发送到Zebra打印机不起作用
- linux a卡双显卡切换显卡,amd显卡驱动 双显卡切换
- 《SysML精粹》学习记录--第三章
- LZJ的python第6次打卡
- 前端实现对象数组的关键字搜索
- Docker安装OnlyOffice并配置自签证书和自己的域名证书
- 1分钟读懂猫咪免疫流程:怎么打疫苗,必须打的疫苗有哪些
- 成就系统实现(四)-测试和总结
- 安卓进不去系统如何备份数据
- 第三章 ArcGIS坐标系与投影变换
热门文章
- ERP功能_财务管理_发票红,蓝字的区别
- 我的世界java版怎么弄在线时间_我的世界怎么设置游戏内的时间为夜晚
- html单选框弹出输入框,JS实现点击单选按钮弹出对话框输入值
- nginx可视化管理工具
- 天猫智慧门店探索:大数据驱动的品牌零售赋能
- android 常用编译工具,Android 抖音常用反编译工具
- tcpdump抓包,并保存为文件
- Android使用百度定位不精确问题
- 光度学中的能量、通量、出度、照度、强度、亮度参数及其联系
- 自制chatroom_构建由Node.js驱动的Chatroom Web App:入门