分享

hook Android 权限请求, 插入权限目的dialog显示。

 wintelsui 2023-10-11 发布于北京

Permission_aim_tip

作用

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

效果

特点

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

使用

注册代理

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

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

类说明

    1. 类名作用备注
      PermissionAimTipHelper核心类,用于拦截通过ActivityCompat调用权限的过程,唯一构造参数为AimTipShowController必须调用init方法,之后才能通过getInstance获取实例。否则会报错。
      AimTipShowController接口,在拦截到权限请求的时候,通过该类来显示提示信息。
      TRSTipShowControllerAimTipShowController的实现类,需要两个构造参数,其中之一是AimTipAdapter,通过AimTipAdapter将用户申请的权限转换为可以显示的语义化文字。还有一个是DialogStyleData 可以指定dialog的样式,可以缺省。
      AimTipAdapter抽象类,定义了从android权限到需要显示信息的抽象过程
      RawAimTipAdapterAimTipAdapter的实现类,实现了加载raw目录下的配置文件
      DialogStyleData用来保存dialog的布局文件id,和item的布局文件id ,以此来实现样式的自定义

填写配置文件

其中的R.raw.permission_aim_description 是配置文件的id (保存在raw文件夹下)。配置文件如下

json
复制代码
[ { "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方法,将无法拦截。需要使用以下方式请求权限才能拦截

java
复制代码
ActivityCompat.requestPermissions(this, locationPermission, 100);

其中的ActivityCompat是Android本身的适配库

在这里插入图片描述

样式自定义

原理是通过指定布局ID来替换样式,只需要在布局ID中出现以下控件即可。

xml
复制代码
<?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>

设置样式

java
复制代码
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实现的,其他的弹出提示框都是简单的内容,无需赘言。

Java
复制代码
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); } @Override public 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; } @Override public 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() { @Override public 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); } }); } } }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多