Permission_aim_tip

作用

因为越来越严格的隐私政策要求,需要在申请权限的时候,告知用户需要该权限的目的。为了能快速适配已有项目,需要一个能自动感知权限申请,并显示申请原因的框架。于是编写了该框架。

效果

特点

  1. 100%拦截fragment的权限请求
  2. 100%拦截 RxPermission的权限请求(因为RxPermission就是基于Fragment)
  3. 方便配置,使用json配置
  4. 集成简便,一行代码即可。
  5. 可定制UI

使用

注册代理

只需要在Application中注册即可使用

public class MyApp  extends Application {@Overridepublic void onCreate() {super.onCreate();//注册代理,一句话即可使用PermissionAimTipHelper.init(new TRSTipShowController(new RawAimTipAdapter(this, R.raw.permission_aim_description)));}
}

类说明

  1. 类名 作用 备注
    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显示相关推荐

  1. 软件获取手机的ime权限_你手机上软件的权限请求,都要同意吗,会不会泄漏你的隐私?...

    生活中,在你使用各种手机软件的时候,不知道你有没有注意到,在软件第一次安装打开的时候,有大多数软件会弹出一个权限请求的弹框,显示出应用要请求获取什么权限以及使用这些权限的一些情况说明.我相信大多数人可 ...

  2. 如何在Android 11 中正确请求位置权限?以及Android 8 - 11位置权限的变化及适配方法!

    由于现在位置信息变为了敏感数据,因此Android限制了它的使用,尤其在APP后台. 在Android 9 之前,位置权限没有按照前后台分离,APP在前台和后台使用相同的资源. 但是,Google开始 ...

  3. [Android Studio]详细讲解Android6.0以上请求应用权限(解决请求权限窗口一闪而过的问题)

    现在的主流手机的Android版本都是8.0/9.0(笔者的华为手机为9.0),所以在开发Android的时候尽量使用Android8.0的版本(SDK 26).随着Android版本的提升,系统的安 ...

  4. Android ActivityResultContracts 请求权限(封装;含android 11权限变更)

    文章目录 关联博客 Android 11 权限变更 权限申请 BasePermissionExtendFragment.kt PermissionExtend.kt 关联博客 Android Acti ...

  5. Android数据手册02:android.permission权限请求汇总

    在Android开发中,当程序执行需要操作安全敏感项时,必须在androidmanifest.xml中声明相关权限请求. 比如,声明拨打电话的权限请求方法如下: 1 <uses-permissi ...

  6. Xamarin Android权限请求

    Xamarin Android权限请求 Android权限规定了App是否可以访问特定的资源,如网络.电话和短信.在原有API 6.0之前,App在安全的时候,会请求一次权限.一旦安装后,App就可以 ...

  7. android危险权限分组,Android 6.0权限请求相关及权限分组方法

    Android M(6.0)API 23后加入了权限请求设置,APP需要使用某些权限需要主动申请. 权限分为3类,一组是Normal权限,无需申请,另一组是Dangerous,需申请,然后是特殊权限, ...

  8. Android地图权限处理,Android 使用地图时的权限请求方法

    在初始化自己位置的时候请求定位权限: Constants.ACCESS_FINE_LOCATION_COMMANDS_REQUEST_CODE是自定义的常量值==0x01 if (ContextCom ...

  9. android 权限开启回调,Android M请求onSurfaceTextureAvailable回调权限不在活动

    症状:首次启动我的应用程序崩溃java.lang.SecurityException: Lacking privileges to access camera service.我收到了"不幸 ...

最新文章

  1. Xcode调用栈时小图标代表什么意思
  2. Asp.Net Core 快速邮件队列设计与实现
  3. Oracle 20c 新特性:XGBoost 机器学习算法和 AutoML 的支持
  4. 完全备份、差异备份以及增量备份的区别
  5. win7右键没有新建文件夹了
  6. 北京地铁挤,最挤昌平线
  7. 高级文本编辑器——Kate
  8. java笔记框架部分
  9. matlab电磁场,基于matlab的电磁场分析.pdf
  10. 单层工业厂房设计原理以及知识重点
  11. java 斑马 打印不出来_通过PrintServer将原始ZPL发送到Zebra打印机不起作用
  12. linux a卡双显卡切换显卡,amd显卡驱动 双显卡切换
  13. 《SysML精粹》学习记录--第三章
  14. LZJ的python第6次打卡
  15. 前端实现对象数组的关键字搜索
  16. Docker安装OnlyOffice并配置自签证书和自己的域名证书
  17. 1分钟读懂猫咪免疫流程:怎么打疫苗,必须打的疫苗有哪些
  18. 成就系统实现(四)-测试和总结
  19. 安卓进不去系统如何备份数据
  20. 第三章 ArcGIS坐标系与投影变换

热门文章

  1. ERP功能_财务管理_发票红,蓝字的区别
  2. 我的世界java版怎么弄在线时间_我的世界怎么设置游戏内的时间为夜晚
  3. html单选框弹出输入框,JS实现点击单选按钮弹出对话框输入值
  4. nginx可视化管理工具
  5. 天猫智慧门店探索:大数据驱动的品牌零售赋能
  6. android 常用编译工具,Android 抖音常用反编译工具
  7. tcpdump抓包,并保存为文件
  8. Android使用百度定位不精确问题
  9. 光度学中的能量、通量、出度、照度、强度、亮度参数及其联系
  10. 自制chatroom_构建由Node.js驱动的Chatroom Web App:入门