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

本文介绍了一个用于在Android应用中拦截权限申请并显示自定义提示的框架PermissionAimTip。该框架100%拦截fragment和RxPermission的权限请求,通过json配置文件提供权限目的描述,支持UI定制,使用简单,只需一行代码即可集成。通过在Application中注册代理并配置权限描述文件,实现权限申请时显示用户友好的提示信息。
摘要由CSDN通过智能技术生成

Permission_aim_tip

作用

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

效果

在这里插入图片描述

特点

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

类说明

    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文件夹下)。配置文件如下

[
  {
    "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);
    }

    @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);
                }
            });
        }
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值