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>
<item name="aim_tip_id_recycle_view" type="id"/>
<item name="aim_tip_id_item_title" type="id"/>
<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);
}
@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);
}
});
}
}
}
|