PermissionDispatcher源码解析

501 阅读4分钟

1、概述

PermissionsDispatcher-github地址

项目中权限请求使用了 PermissionDispatcher 这个库,使用注解的方式进行权限请求。

观察其引入的依赖包含注解处理器,因此可以知道其在编译期为我们生成了代码。

1.1 基本使用

  • 引入依赖 & 注解处理器
  • 清单文件声明要使用权限
  • 使用@RuntimePermissions修饰请求权限的 Fragment 或者 Activity
  • 使用@NeedsPermission修饰请求权限的方法,说明需要的权限(数组形式)
  • build 代码生成 XXXXPermissionDispatcher.kt 文件 (kotlin)
  • 将请求权限的方法替换成 XXXWithPermissionCheck的拓展方法
  • 重写 onRequestPermissionsResult 方法,调用生成的同名的拓展方法,处理获取到权限后的逻辑

2、源码分析

KotlinPoet-官方文档

2.1、代码结构分析

  • annotation

    • 注解
  • library

    • 只有一个 PermissionUtils 类,共生成的代码中检查权限使用
  • processor

    • 注解处理器,用来生成java或者kotlin代码

从代码的组织结构可以就看出 PermissionDispatcher 工作主要就是依赖注解处理器生成的

XXXXPermissionDispatcher 文件,XXXX对应的用 @RuntimePermissions 修饰的类名称。(比如:

MainActivity对应的就是MainActivityPermissionDispatcher )。

2.2、kapt 生成代码

// This file was generated by PermissionsDispatcher. Do not modify!
@file:JvmName("MainActivity2PermissionsDispatcher")

package com.example.asmrapp

import androidx.core.app.ActivityCompat
import kotlin.Array
import kotlin.Int
import kotlin.IntArray
import kotlin.String
import permissions.dispatcher.PermissionUtils

private const val REQUEST_TAKECAMERA: Int = 0

private val PERMISSION_TAKECAMERA: Array<String> = arrayOf("android.permission.CAMERA")

fun MainActivity2.takeCameraWithPermissionCheck() {
  if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_TAKECAMERA)) {
    takeCamera()
  } else {
    ActivityCompat.requestPermissions(this, PERMISSION_TAKECAMERA, REQUEST_TAKECAMERA)
  }
}

fun MainActivity2.onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {
  when (requestCode) {
    REQUEST_TAKECAMERA ->
     {
      if (PermissionUtils.verifyPermissions(*grantResults)) {
        takeCamera()
      }
    }
  }
}

PermissionDispatcher 主要使用注解处理器并结合 JavaPoet 和 KotlinPoet 两个代码生成库来替我们生成代码(以Kotlin为例,它会为我们生成以上这样一个Kt文件,文件名取用@RuntimePermission注释的Activity或者Fragment的名称,并替其生成拓展方法,分别用来检查申请权限,以及处理获取权限后调用我们编写的代码的逻辑)。

2.3、注解处理器

class PermissionsProcessor : AbstractProcessor() {
    private val javaProcessorUnits = listOf(JavaActivityProcessorUnit(),
                                            JavaFragmentProcessorUnit())
    private val kotlinProcessorUnits = listOf(KotlinActivityProcessorUnit(), 
                                              KotlinFragmentProcessorUnit())
    /* Processing Environment helpers */
    private var filer: Filer by Delegates.notNull()

    override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        filer = processingEnv.filer
        ELEMENT_UTILS = processingEnv.elementUtils
        TYPE_UTILS = processingEnv.typeUtils
    }

    override fun getSupportedSourceVersion(): SourceVersion? {
        return SourceVersion.latestSupported()
    }

    override fun getSupportedAnnotationTypes(): Set<String> {
        return hashSetOf(RuntimePermissions::class.java.canonicalName)
    }

    override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
        // 1、
        val requestCodeProvider = RequestCodeProvider()
        // 2、获取被 @RuntimePermissions 注解修饰的类
        roundEnv.getElementsAnnotatedWith(RuntimePermissions::class.java)
                .sortedBy { it.simpleName.toString() }
                .forEach {
                    val rpe = RuntimePermissionsElement(it as TypeElement)
                    val kotlinMetadata = it.getAnnotation(Metadata::class.java)
                    if (kotlinMetadata != null) {
                        // kotlin类
                        processKotlin(it, rpe, requestCodeProvider)
                    } else {
                        // Java类
                        processJava(it, rpe, requestCodeProvider)
                    }
                }
        return true
    }

    private fun processKotlin(element: Element, rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider) {
        val processorUnit = findAndValidateProcessorUnit(kotlinProcessorUnits, element)
        val kotlinFile = processorUnit.createFile(rpe, requestCodeProvider)
        kotlinFile.writeTo(filer)
    }

    private fun processJava(element: Element, rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider) {
        val processorUnit = findAndValidateProcessorUnit(javaProcessorUnits, element)
        val javaFile = processorUnit.createFile(rpe, requestCodeProvider)
        javaFile.writeTo(filer)
    }
}

// 在units中查找第一个满足 type 是其 targetType 的子类型
fun <K> findAndValidateProcessorUnit(units: List<ProcessorUnit<K>>, element: Element): ProcessorUnit<K> {
    val type = element.asType()
    try {
        return units.first { type.isSubtypeOf(it.getTargetType()) }
    } catch (ex: NoSuchElementException) {
        throw WrongClassException(type)
    }
}
  • 注解处理器的入口类 PermissionsProcessor
  • process 入口方法中获取所有被 @RuntimePermissions 注解修饰的类
  • 通过判断被修饰的类为Java类还是Kotlin类分别调用不同的方法进行处理
  • 找到对应处理单元,调用 createFile 方法进行处理
    • 这里其实就是 Activity 找 ActivityProcessorUnit,Fragment 找 FragmentProcessorUnit
class KotlinActivityProcessorUnit : KotlinBaseProcessorUnit() {

    override fun getTargetType(): TypeMirror = typeMirrorOf("android.app.Activity")

    override fun getActivityName(targetParam: String): String = targetParam

    override fun addShouldShowRequestPermissionRationaleCondition(builder: FunSpec.Builder, permissionField: String, isPositiveCondition: Boolean) {
        val condition = if (isPositiveCondition) "" else "!"
        builder.beginControlFlow("if (%L%T.shouldShowRequestPermissionRationale(%L, *%N))", condition, permissionUtils, "this", permissionField)
    }

    override fun addRequestPermissionsStatement(builder: FunSpec.Builder, targetParam: String, permissionField: String, requestCodeField: String) {
        builder.addStatement("%T.requestPermissions(%L, %N, %N)", ClassName("androidx.core.app", "ActivityCompat"), targetParam, permissionField, requestCodeField)
    }
}

class KotlinFragmentProcessorUnit : KotlinBaseProcessorUnit() {

    override fun getTargetType(): TypeMirror = typeMirrorOf("androidx.fragment.app.Fragment")

    override fun getActivityName(targetParam: String): String = "$targetParam.requireActivity()"

    override fun addShouldShowRequestPermissionRationaleCondition(builder: FunSpec.Builder, permissionField: String, isPositiveCondition: Boolean) {
        val condition = if (isPositiveCondition) "" else "!"
        builder.beginControlFlow("if (%L%T.shouldShowRequestPermissionRationale(%L, *%N))", condition, permissionUtils, "this" /* Fragment */, permissionField)
    }

    override fun addRequestPermissionsStatement(builder: FunSpec.Builder, targetParam: String, permissionField: String, requestCodeField: String) {
        builder.addStatement("%L.requestPermissions(%L, %N)", targetParam, permissionField, requestCodeField)
    }
}
  • kotlin Activity 和 Fragment 对应的处理单元,Java 也有对应的代码
  • 上面Activity和Fragment对应的处理单元都没有发现 createFile 方法,推测在公共的基类中
// 注解处理器生成代码的逻辑
override fun createFile(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): FileSpec {
    return FileSpec.builder(rpe.packageName, rpe.generatedClassName) // 包名、类名
            .addComment(FILE_COMMENT) // 注释
            .addAnnotation(createJvmNameAnnotation(rpe.generatedClassName)) // @file:JvmName 注解
            .addProperties(createProperties(rpe, requestCodeProvider)) // 请求码 & 权限列表
            .addFunctions(createWithPermissionCheckFuns(rpe))
            .addFunctions(createOnShowRationaleCallbackFuns(rpe))
            .addFunctions(createPermissionHandlingFuns(rpe))
            .addTypes(createPermissionRequestClasses(rpe))
            .build()
}

private fun createProperties(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): List<PropertySpec> {
    val properties = arrayListOf<PropertySpec>()
    // 所有被 @NeedsPermission 修饰的方法
    rpe.needsElements.sortedBy { it.simpleString() }.forEach {
        // 添加两个属性
        properties.add(createRequestCodeProp(it, requestCodeProvider.nextRequestCode()))
        properties.add(createPermissionProperty(it))
        if (it.parameters.isNotEmpty()) {
            val hasOnRationaleParams = rpe.findOnRationaleForNeeds(it)?.parameters?.isNotEmpty()
                    ?: true
            if (hasOnRationaleParams) {
                properties.add(createPendingRequestProperty(it))
            } else {
                properties.addAll(createArgProps(it))
            }
        }
    }
    return properties
}

// 生成请求码 (一次权限请求对应一个请求码,可以包含多个权限)
private fun createRequestCodeProp(e: ExecutableElement, index: Int): PropertySpec {
    return PropertySpec.builder(requestCodeFieldName(e), Int::class.java, KModifier.CONST, KModifier.PRIVATE)
            .initializer("%L", index)
            .build()
}

// 与请求码对应的请求权限的列表
private fun createPermissionProperty(e: ExecutableElement): PropertySpec {
    val permissionValue = e.getAnnotation(NeedsPermission::class.java).permissionValue()
    val formattedValue = permissionValue.joinToString(
            separator = ", ",
            transform = { ""$it"" }
    )
    val parameterType = ARRAY.plusParameter(ClassName("kotlin", "String"))
    return PropertySpec.builder(permissionFieldName(e), parameterType, KModifier.PRIVATE)
            .initializer("arrayOf(%L)", formattedValue)
            .build()
}

// 生成 XXXXWithPermissionCheck 函数的逻辑
private fun createWithPermissionCheckFuns(rpe: RuntimePermissionsElement): List<FunSpec> {
    // 每个被 @NeedsPermission 修饰的方法
    return rpe.needsElements.map { createWithPermissionCheckFun(rpe, it) }
}

private fun createWithPermissionCheckFun(rpe: RuntimePermissionsElement, method: ExecutableElement): FunSpec {
    val builder = FunSpec.builder(withPermissionCheckMethodName(method))
            .addOriginatingElement(rpe.element)
            .addTypeVariables(rpe.ktTypeVariables)
            .receiver(rpe.ktTypeName) // 拓展方法的接收者
    if (method.enclosingElement.isInternal) {
        builder.addModifiers(KModifier.INTERNAL)
    }
    method.parameters.forEach {
        builder.addParameter(it.simpleString(), it.asPreparedType())
    }
    // 添加对应的方法体
    addWithPermissionCheckBody(builder, method, rpe)
    return builder.build()
}
  • KotlinBaseProcessorUnit 的 createFile 方法
  • FileSpec 是 kotlinpoet 中的类

生成方法体的函数有点长,单独列出来分析一下

fun requestCodeFieldName(e: ExecutableElement) = 
    "$GEN_REQUEST_CODE_PREFIX${e.simpleString().trimDollarIfNeeded().toUpperCase()}"
fun permissionFieldName(e: ExecutableElement) = 
    "$GEN_PERMISSION_PREFIX${e.simpleString().trimDollarIfNeeded().toUpperCase()}"

private fun addWithPermissionCheckBody(builder: FunSpec.Builder, needsMethod: ExecutableElement, rpe: RuntimePermissionsElement) {
    // Create field names for the constants to use
    // 请求码和权限的名称
    val requestCodeField = requestCodeFieldName(needsMethod)
    val permissionField = permissionFieldName(needsMethod)

    // if maxSdkVersion is lower than os level does nothing
    val maxSdkVersion = needsMethod.getAnnotation(NeedsPermission::class.java).maxSdkVersion
    if (maxSdkVersion > 0) {
        // Android 里面那个Build,可以用来获取SDK版本
        builder.beginControlFlow("if (%T.VERSION.SDK_INT > %L)", build, maxSdkVersion)
                .addCode(CodeBlock.builder()
                        .add("%N(", needsMethod.simpleString())
                        .add(varargsKtParametersCodeBlock(needsMethod))
                        .addStatement(")")
                        .addStatement("return")
                        .build())
                .endControlFlow()
    }

    // Add the conditional for when permission has already been granted
    val needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission::class.java).value[0]
    val activity = getActivityName()
    /*
     * if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_TAKECAMERA)) {
     *     takeCamera()
     * }
     */
    // 前面是两个特殊权限的处理,一般为null,取?后面的内容,就是上面注释中if语句
    addWithCheckBodyMap[needsPermissionParameter]?.addHasSelfPermissionsCondition(builder, activity, permissionField)
            ?: builder.beginControlFlow("if (%T.hasSelfPermissions(%L, *%N))", permissionUtils, activity, permissionField)
                
    // 调用我们编写的方法
    builder.addCode(CodeBlock.builder()
            .add("%N(", needsMethod.simpleString())
            .add(varargsKtParametersCodeBlock(needsMethod))
            .addStatement(")")
            .build()
    )
    builder.nextControlFlow("else")

                
    // 其他注解的处理
    // Add the conditional for "OnShowRationale", if present
    val onRationale = rpe.findOnRationaleForNeeds(needsMethod)
    val hasOnRationaleParams = onRationale?.parameters?.isNotEmpty() ?: true
    val hasParameters = needsMethod.parameters.isNotEmpty()
    if (hasParameters) {
        if (hasOnRationaleParams) {
            // If the method has parameters, precede the potential OnRationale call with
            // an instantiation of the temporary Request object
            val varargsCall = CodeBlock.builder()
                    .add("%N = %N(this, ",
                            pendingRequestFieldName(needsMethod),
                            permissionRequestTypeName(rpe, needsMethod)
                    )
                    .add(varargsKtParametersCodeBlock(needsMethod))
                    .addStatement(")")
            builder.addCode(varargsCall.build())
        } else {
            needsMethod.parameters.forEach {
                val code = CodeBlock.builder().addStatement("%N = %N", needsMethod.argumentFieldName(it), it.simpleString())
                builder.addCode(code.build())
            }
        }
    }
    if (onRationale != null) {
        addShouldShowRequestPermissionRationaleCondition(builder, permissionField)
        if (hasParameters) {
            if (hasOnRationaleParams) {
                // For methods with parameters, use the PermissionRequest instantiated above
                builder.addStatement("%N?.let { %N(it) }", pendingRequestFieldName(needsMethod), onRationale.simpleString())
            } else {
                builder.addStatement("%N()", onRationale.simpleString())
            }
        } else {
            if (hasOnRationaleParams) {
                // Otherwise, create a new PermissionRequest on-the-fly
                builder.addStatement("%N(%N(this))", onRationale.simpleString(), permissionRequestTypeName(rpe, needsMethod))
            } else {
                builder.addStatement("%N()", onRationale.simpleString())
            }
        }
        builder.nextControlFlow("else")
    }

    
    /*
     *   else {
     *       ActivityCompat.requestPermissions(this, PERMISSION_TAKECAMERA, REQUEST_TAKECAMERA)
     *   }
    */
    addWithCheckBodyMap[needsPermissionParameter]?.addRequestPermissionsStatement(builder = builder, activityVar = getActivityName(), requestCodeField = requestCodeField)
            ?: addRequestPermissionsStatement(builder = builder, permissionField = permissionField, requestCodeField = requestCodeField)
    if (onRationale != null) {
        builder.endControlFlow()
    }
    builder.endControlFlow()
}


override fun addRequestPermissionsStatement(builder: FunSpec.Builder, targetParam: String, permissionField: String, requestCodeField: String) {
    builder.addStatement("%T.requestPermissions(%L, %N, %N)", ClassName("androidx.core.app", "ActivityCompat"), targetParam, permissionField, requestCodeField)
}
  • 如果当前系统SDK版本大于 @NeedsPermission 中指定的 maxSdkVersion 直接 return
  • 生成检查是否已经获取权限的代码
  • 其他非必要注解生成的代码
  • 生成请求权限的代码

经过以上步骤就PermissionDispatcher就帮我们生成了权限检查和请求的代码逻辑。