hook Android 权限请求, 插入权限目的dialog显示
Posted -SOLO-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hook Android 权限请求, 插入权限目的dialog显示相关的知识,希望对你有一定的参考价值。
Permission_aim_tip
作用
因为越来越严格的隐私政策要求,需要在申请权限的时候,告知用户需要该权限的目的。为了能快速适配已有项目,需要一个能自动感知权限申请,并显示申请原因的框架。于是编写了该框架。
效果
特点
- 100%拦截fragment的权限请求
- 100%拦截 RxPermission的权限请求(因为RxPermission就是基于Fragment)
- 方便配置,使用json配置
- 集成简便,一行代码即可。
- 可定制UI
使用
注册代理
只需要在Application中注册即可使用
public class MyApp extends Application
@Override
public 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项目。
源码
原理
简单的说一句,从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);
@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);
);
以上是关于hook Android 权限请求, 插入权限目的dialog显示的主要内容,如果未能解决你的问题,请参考以下文章