彻底掌握@AfterPermissionGranted:Android权限处理的优雅实现

彻底掌握@AfterPermissionGranted:Android权限处理的优雅实现

【免费下载链接】easypermissions Simplify Android M system permissions 【免费下载链接】easypermissions 项目地址: https://gitcode.com/gh_mirrors/ea/easypermissions

你是否还在为Android权限请求后的繁琐回调处理而烦恼?是否经常忘记在权限授予后手动调用目标方法?本文将系统讲解EasyPermissions库中@AfterPermissionGranted注解的工作原理与使用技巧,带你告别权限回调地狱,实现简洁优雅的权限处理逻辑。读完本文,你将能够:

  • 理解@AfterPermissionGranted注解的核心原理
  • 掌握注解的基本使用方法与请求码规范
  • 解决多权限场景下的注解使用冲突
  • 实现权限请求与业务逻辑的解耦
  • 处理特殊场景下的权限回调问题

一、Android权限处理的痛点与解决方案

Android 6.0(API 23)引入动态权限(Runtime Permissions)机制后,应用不得不处理复杂的权限请求流程。传统实现方式需要开发者:

  1. 检查权限状态
  2. 请求权限
  3. onRequestPermissionsResult中处理回调
  4. 根据结果调用相应业务逻辑

这种方式导致大量模板代码与业务逻辑交织,尤其在多个权限请求场景下,回调处理逻辑会变得异常混乱。

传统权限处理代码示例:

// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
        != PackageManager.PERMISSION_GRANTED) {
    // 请求权限
    ActivityCompat.requestPermissions(this, 
            new String[]{Manifest.permission.READ_SMS}, 
            RC_SMS_PERM);
} else {
    // 已有权限,执行操作
    doSmsTask();
}

// 处理权限回调
@Override
public void onRequestPermissionsResult(int requestCode, 
        String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == RC_SMS_PERM) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限被授予,执行操作
            doSmsTask();
        } else {
            // 权限被拒绝,处理拒绝逻辑
            showPermissionDeniedDialog();
        }
    }
}

EasyPermissions库的@AfterPermissionGranted注解通过AOP(面向切面编程) 思想,将权限检查、请求与授权后处理逻辑封装为一个整体,大幅简化了权限处理流程。

二、@AfterPermissionGranted注解原理解析

2.1 注解定义与核心属性

@AfterPermissionGranted注解的定义非常简洁,位于pub.devrel.easypermissions包中:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {
    int value(); // 请求码,用于标识权限请求
}
  • @Retention(RetentionPolicy.RUNTIME): 注解在运行时可见,允许通过反射获取注解信息
  • @Target(ElementType.METHOD): 注解只能应用于方法
  • value(): 权限请求码,必须是唯一的整数值,用于匹配权限请求与回调

2.2 工作流程解析

@AfterPermissionGranted注解的工作流程可以概括为以下四个步骤:

mermaid

2.3 核心实现机制

EasyPermissions通过反射机制实现注解功能,关键代码位于EasyPermissions.javarunAnnotatedMethods方法:

private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
    Class clazz = object.getClass();
    // 处理AndroidAnnotations生成的子类
    if (isUsingAndroidAnnotations(object)) {
        clazz = clazz.getSuperclass();
    }

    while (clazz != null) {
        for (Method method : clazz.getDeclaredMethods()) {
            AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
            if (ann != null && ann.value() == requestCode) {
                // 检查方法是否无参数
                if (method.getParameterTypes().length > 0) {
                    throw new RuntimeException(
                        "Cannot execute method " + method.getName() + 
                        " because it has input parameters.");
                }
                try {
                    if (!method.isAccessible()) {
                        method.setAccessible(true); // 允许访问私有方法
                    }
                    method.invoke(object); // 执行目标方法
                } catch (IllegalAccessException | InvocationTargetException e) {
                    Log.e(TAG, "runDefaultMethod exception", e);
                }
            }
        }
        clazz = clazz.getSuperclass(); // 检查父类方法
    }
}

当权限请求成功后,EasyPermissions会:

  1. 遍历当前对象及其父类的所有方法
  2. 查找带有@AfterPermissionGranted注解且请求码匹配的方法
  3. 验证方法是否无参数(注解只能用于无参方法)
  4. 通过反射执行该方法

三、@AfterPermissionGranted注解使用详解

3.1 基础使用步骤

使用@AfterPermissionGranted注解只需三个步骤:

步骤1:添加依赖

在项目的build.gradle中添加EasyPermissions依赖:

dependencies {
    implementation 'pub.devrel:easypermissions:3.0.0'
}
步骤2:实现PermissionCallbacks接口

Activity或Fragment需要实现EasyPermissions.PermissionCallbacks接口以处理权限回调:

public class MainFragment extends Fragment implements EasyPermissions.PermissionCallbacks {
    // 实现接口方法
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "权限已授予: " + requestCode);
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "权限被拒绝: " + requestCode);
        // 处理权限被拒绝的情况
    }
}
步骤3:重写权限请求结果方法

将权限请求结果委托给EasyPermissions处理:

@Override
public void onRequestPermissionsResult(int requestCode, 
        @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    // 将结果传递给EasyPermissions
    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
步骤4:使用@AfterPermissionGranted注解标记方法
// 定义请求码常量
private static final int RC_SMS_PERM = 122;

@AfterPermissionGranted(RC_SMS_PERM)
private void smsTask() {
    // 检查权限
    if (EasyPermissions.hasPermissions(requireContext(), Manifest.permission.READ_SMS)) {
        // 有权限,执行操作
        Toast.makeText(getActivity(), "执行SMS操作", Toast.LENGTH_LONG).show();
    } else {
        // 无权限,请求权限
        EasyPermissions.requestPermissions(this, getString(R.string.rationale_sms),
                RC_SMS_PERM, Manifest.permission.READ_SMS);
    }
}

3.2 请求码规范与管理

请求码(requestCode)是@AfterPermissionGranted注解的核心参数,用于关联权限请求与回调方法。良好的请求码管理可以避免冲突并提高代码可读性。

推荐的请求码管理方式:

public class PermissionCodes {
    // 基础权限请求码区间
    public static final int BASE_PERMISSION = 100;
    
    // 具体权限请求码
    public static final int SMS_PERM = BASE_PERMISSION + 1;
    public static final int CAMERA_PERM = BASE_PERMISSION + 2;
    public static final int LOCATION_PERM = BASE_PERMISSION + 3;
    public static final int STORAGE_PERM = BASE_PERMISSION + 4;
    
    // 避免与系统请求码冲突,建议使用100以上数值
    public static final int MAX_PERMISSION = BASE_PERMISSION + 100;
}

请求码使用原则:

  • 唯一性:不同权限请求使用不同请求码
  • 区间化:按功能模块划分请求码区间
  • 常量化:使用常量定义请求码,避免硬编码
  • 文档化:为每个请求码添加注释说明用途

3.3 单权限请求示例

以读取短信权限为例,完整实现如下:

public class SmsFragment extends Fragment implements EasyPermissions.PermissionCallbacks {
    private static final String TAG = "SmsFragment";
    private static final int RC_SMS_PERM = 122;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_sms, container, false);
        // 绑定按钮点击事件
        v.findViewById(R.id.btn_read_sms).setOnClickListener(v1 -> smsTask());
        return v;
    }

    @AfterPermissionGranted(RC_SMS_PERM)
    private void smsTask() {
        String[] perms = {Manifest.permission.READ_SMS};
        if (EasyPermissions.hasPermissions(requireContext(), perms)) {
            // 执行SMS读取操作
            readSmsMessages();
        } else {
            // 请求权限,显示 rationale 对话框
            EasyPermissions.requestPermissions(this, 
                    "需要SMS权限以读取短信内容", // rationale信息
                    RC_SMS_PERM, 
                    perms);
        }
    }

    private void readSmsMessages() {
        // 实际的SMS读取逻辑
        Log.d(TAG, "读取短信...");
        // ...
    }

    // 权限回调实现...
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "权限授予: " + requestCode);
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "权限拒绝: " + requestCode);
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            new AppSettingsDialog.Builder(this).build().show();
        }
    }
}

3.4 多权限请求场景

对于需要同时请求多个权限的场景,@AfterPermissionGranted注解同样适用:

private static final int RC_MULTIPLE_PERM = 123;

@AfterPermissionGranted(RC_MULTIPLE_PERM)
private void performCameraAndStorageTask() {
    String[] perms = {
        Manifest.permission.CAMERA,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    
    if (EasyPermissions.hasPermissions(requireContext(), perms)) {
        // 所有权限已授予,执行操作
        takePhotoAndSave();
    } else {
        // 请求多个权限
        EasyPermissions.requestPermissions(
            this,
            "需要相机和存储权限以拍摄和保存照片",
            RC_MULTIPLE_PERM,
            perms
        );
    }
}

多权限请求的处理流程:

mermaid

四、高级使用技巧与最佳实践

4.1 权限请求合理化(Rationale)

当用户拒绝过一次权限请求后,Android推荐显示一个合理化(rationale)对话框,解释为什么需要该权限。EasyPermissions支持自定义rationale信息:

EasyPermissions.requestPermissions(
    new PermissionRequest.Builder(this, RC_CAMERA_PERM, perms)
        .setRationale("需要相机权限以拍摄照片")
        .setPositiveButtonText("确定")
        .setNegativeButtonText("取消")
        .setTheme(R.style.MyPermissionDialog)
        .build()
);

Rationale对话框的最佳实践:

  • 简洁明了地说明权限用途
  • 避免技术术语,使用用户易懂的语言
  • 说明拒绝权限的后果
  • 提供明确的操作按钮

4.2 永久拒绝权限的处理

当用户勾选"不再询问"并拒绝权限后,应用需要引导用户到设置中手动授予权限:

@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
    Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
    
    // 检查是否有永久拒绝的权限
    if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
        // 显示设置对话框
        new AppSettingsDialog.Builder(this)
            .setTitle("权限被禁用")
            .setRationale("需要启用相机权限才能拍摄照片。请进入设置启用权限。")
            .setPositiveButton("设置")
            .setNegativeButton("取消")
            .build()
            .show();
    }
}

// 处理从设置返回的结果
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
        // 从设置返回,重新检查权限
        smsTask();
    }
}

4.3 与ViewModel结合使用

在MVVM架构中,可以将权限请求逻辑放在ViewModel中,但需要注意上下文生命周期管理:

public class MyViewModel extends ViewModel {
    private WeakReference<Fragment> fragmentRef;
    
    public void setFragment(Fragment fragment) {
        this.fragmentRef = new WeakReference<>(fragment);
    }
    
    // 权限检查方法
    public boolean hasCameraPermission() {
        Fragment fragment = fragmentRef.get();
        if (fragment == null || fragment.getContext() == null) {
            return false;
        }
        return EasyPermissions.hasPermissions(
            fragment.getContext(), 
            Manifest.permission.CAMERA
        );
    }
    
    // 权限请求方法
    public void requestCameraPermission() {
        Fragment fragment = fragmentRef.get();
        if (fragment == null) return;
        
        String[] perms = {Manifest.permission.CAMERA};
        if (!hasCameraPermission()) {
            EasyPermissions.requestPermissions(
                fragment,
                "需要相机权限",
                PermissionCodes.RC_CAMERA_PERM,
                perms
            );
        }
    }
}

4.4 方法访问修饰符的影响

@AfterPermissionGranted注解可以用于不同访问修饰符的方法,但需要注意反射调用的限制:

@AfterPermissionGranted(RC_EXAMPLE)
public void publicMethod() { ... } // 可以正常调用

@AfterPermissionGranted(RC_EXAMPLE)
protected void protectedMethod() { ... } // 可以正常调用

@AfterPermissionGranted(RC_EXAMPLE)
private void privateMethod() { ... } // 可以调用,EasyPermissions会设置setAccessible(true)

@AfterPermissionGranted(RC_EXAMPLE)
static void staticMethod() { ... } // 不支持,静态方法无法通过实例反射调用

注意事项:

  • 注解方法不能有参数
  • 注解方法不能是静态方法
  • 私有方法可以被调用(EasyPermissions会通过反射设置可访问性)
  • 方法返回值会被忽略,建议使用void返回类型

五、常见问题与解决方案

5.1 注解方法未被调用

可能原因与解决方法:

  1. 请求码不匹配

    • 确保@AfterPermissionGranted注解的value与requestPermissions的requestCode一致
    • 检查请求码是否超出Integer范围或与其他请求冲突
  2. 未实现PermissionCallbacks接口

    • 确保Activity/Fragment实现了EasyPermissions.PermissionCallbacks
    • 检查是否在onRequestPermissionsResult中调用了EasyPermissions.onRequestPermissionsResult
  3. 反射调用失败

    • 检查方法是否有参数(注解方法必须无参)
    • 检查方法是否是静态方法(不支持静态方法)
    • 检查是否有安全管理器限制反射访问

调试技巧:onPermissionsGranted回调中添加日志,确认权限确实被授予:

@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
    Log.d(TAG, "权限已授予 - 请求码: " + requestCode);
    Log.d(TAG, "授予的权限: " + TextUtils.join(", ", perms));
}

5.2 多次调用问题

当权限已授予时,直接调用带注解的方法会导致方法被执行两次:一次是直接调用,一次是注解触发。解决方案是始终通过检查权限来控制流程:

错误示例:

// 错误:直接调用带注解的方法
button.setOnClickListener(v -> smsTask());

@AfterPermissionGranted(RC_SMS_PERM)
private void smsTask() {
    // 已有权限时会直接执行
    performSmsOperation();
}

正确示例:

button.setOnClickListener(v -> smsTask());

@AfterPermissionGranted(RC_SMS_PERM)
private void smsTask() {
    // 始终先检查权限
    if (EasyPermissions.hasPermissions(requireContext(), perms)) {
        performSmsOperation(); // 只有检查通过才执行
    } else {
        EasyPermissions.requestPermissions(...);
    }
}

5.3 Fragment中使用的注意事项

在Fragment中使用时,需要特别注意传递正确的上下文和请求权限的方式:

Fragment中请求权限的正确方式:

// 正确:使用Fragment的requestPermissions方法
EasyPermissions.requestPermissions(this, rationale, requestCode, perms);

// 错误:使用Activity的requestPermissions方法
EasyPermissions.requestPermissions(getActivity(), rationale, requestCode, perms);

获取上下文的注意事项:

// 正确:使用requireContext()获取非空上下文
if (EasyPermissions.hasPermissions(requireContext(), perms)) { ... }

// 避免:在Fragment中使用getActivity(),可能为null
if (getActivity() != null && EasyPermissions.hasPermissions(getActivity(), perms)) { ... }

六、@AfterPermissionGranted注解的实现原理

6.1 注解处理器工作流程

EasyPermissions通过运行时反射实现@AfterPermissionGranted注解的功能,核心流程如下:

mermaid

6.2 关键代码解析

1. 权限结果处理

EasyPermissions.onRequestPermissionsResult()方法处理权限请求结果,并在权限全部授予时触发注解方法调用:

public static void onRequestPermissionsResult(int requestCode, 
        @NonNull String[] permissions, @NonNull int[] grantResults,
        @NonNull Object... receivers) {
    // 分离授予和拒绝的权限
    List<String> granted = new ArrayList<>();
    List<String> denied = new ArrayList<>();
    for (int i = 0; i < permissions.length; i++) {
        if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
            granted.add(permissions[i]);
        } else {
            denied.add(permissions[i]);
        }
    }
    
    // 通知接收者
    for (Object object : receivers) {
        // 处理授予的权限
        if (!granted.isEmpty() && object instanceof PermissionCallbacks) {
            ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
        }
        
        // 处理拒绝的权限
        if (!denied.isEmpty() && object instanceof PermissionCallbacks) {
            ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
        }
        
        // 如果所有权限都被授予,执行注解方法
        if (!granted.isEmpty() && denied.isEmpty()) {
            runAnnotatedMethods(object, requestCode);
        }
    }
}

2. 反射调用注解方法

runAnnotatedMethods()方法通过反射查找并调用所有匹配请求码的注解方法:

private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
    Class<?> clazz = object.getClass();
    
    // 处理AndroidAnnotations生成的子类
    if (isUsingAndroidAnnotations(object)) {
        clazz = clazz.getSuperclass();
    }
    
    // 遍历类层次结构
    while (clazz != null) {
        for (Method method : clazz.getDeclaredMethods()) {
            AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
            if (ann != null && ann.value() == requestCode) {
                // 检查方法是否有参数
                if (method.getParameterTypes().length > 0) {
                    throw new RuntimeException(
                        "Cannot execute method " + method.getName() + 
                        " because it has input parameters.");
                }
                
                try {
                    // 允许访问私有方法
                    if (!method.isAccessible()) {
                        method.setAccessible(true);
                    }
                    // 调用方法
                    method.invoke(object);
                } catch (Exception e) {
                    Log.e(TAG, "反射调用方法失败: " + method.getName(), e);
                }
            }
        }
        clazz = clazz.getSuperclass(); // 检查父类
    }
}

七、总结与最佳实践

@AfterPermissionGranted注解为Android权限处理提供了一种优雅的解决方案,通过反射机制自动关联权限请求与业务逻辑,大幅简化了代码。以下是使用该注解的最佳实践总结:

7.1 编码规范

  1. 请求码管理

    • 使用常量定义请求码
    • 按功能模块分组请求码
    • 避免硬编码请求码值
  2. 方法设计

    • 注解方法只包含权限检查和业务逻辑调用
    • 业务逻辑与权限检查分离
    • 确保注解方法无参数且返回类型为void
  3. 权限检查

    • 始终在注解方法中先检查权限
    • 不要假设权限一定已授予
    • 处理权限检查可能返回的异常

7.2 架构建议

  1. 权限封装 将权限相关逻辑封装到BaseActivity/BaseFragment中:
public abstract class BasePermissionFragment extends Fragment 
        implements EasyPermissions.PermissionCallbacks {
    
    @Override
    public void onRequestPermissionsResult(int requestCode, 
            @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }
    
    // 提供默认的权限拒绝处理
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            new AppSettingsDialog.Builder(this).build().show();
        }
    }
    
    // 抽象方法留给子类实现
    @Override
    public abstract void onPermissionsGranted(int requestCode, @NonNull List<String> perms);
}
  1. 权限与业务逻辑分离
@AfterPermissionGranted(RC_LOCATION_PERM)
private void startLocationTask() {
    if (hasLocationPermissions()) {
        // 只做权限检查和方法调用
        mLocationManager.startTracking();
    } else {
        requestLocationPermissions();
    }
}

// 业务逻辑单独实现  
public class LocationManager {
    public void startTracking() {
        // 位置跟踪逻辑
    }
}

7.3 性能考量

虽然反射会带来一定的性能开销,但在权限请求这一低频操作场景下,其影响可以忽略不计。EasyPermissions通过以下方式优化反射性能:

  1. 只在权限授予时执行反射查找
  2. 缓存类层次结构遍历结果
  3. 避免在主线程执行过多反射操作

通过合理使用@AfterPermissionGranted注解,我们可以在不牺牲性能的前提下,大幅提升代码可读性和可维护性。

掌握@AfterPermissionGranted注解,不仅能够简化Android权限处理代码,更能帮助开发者构建更加清晰、优雅的应用架构。告别繁琐的权限回调处理,让代码回归简洁本质,从使用@AfterPermissionGranted注解开始。

【免费下载链接】easypermissions Simplify Android M system permissions 【免费下载链接】easypermissions 项目地址: https://gitcode.com/gh_mirrors/ea/easypermissions

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值