注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。本章的内容主要介绍注解的种类、如何自定义以及如何解析自定义注解。
1. 注解的定义
我们熟知的Retrofit就是以注解的形式提供接口的注册的,这里拿我们最常使用的@GET注解来进行说明:
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
这个注解可以分为两个部分:
-
第一部分就是用来注解@GET注解的注解,这些注解称之为元注解
-
第二部分就是@GET注解的结构定义了
元注解有以下几种:
-
@Inherited
注解可以被继承
-
@Documented
注解可以被JavaDoc工具记录
-
@Repeatable
JDK 8 新增,允许一个注解在同一声明类型上多次使用
-
@Retention
注解的保留策略
取值是一个RetentionPolicy枚举类型,分别表示不同级别的保留策略,而根据保留策略的不同,处理该注解的方式也不相同
-
SOURCE
源码级,注解信息只会保留到源代码中,编译器会在编译时丢弃调注解信息,因此不会保留到.class文件中
-
CLASS
编译时注解,默认值,注解信息会一直保留到.class文件阶段,但不会保存到运行时阶段。所以,需要在编译时通过注解处理器处理。
-
RUNTIME
运行时注解,注解信息在class文件阶段以及运行时阶段都会保留,因此可以通过反射获取注解信息。
-
@Target
注解可以修饰的范围
取值是一个ElementType枚举类型的数组,有以下枚举值可取:
-
TYPE
修饰类、接口(包括注解类型)、枚举类型
-
FIELD
修饰成员变量(包括枚举常量)
-
METHOD
修饰方法
-
PARAMETER
修饰参数
-
CONSTRUCTOR
修饰构造器
-
LOCAL_VARIABLE
修饰局部变量
-
ANNOTATION_TYPE
修饰注解类型
-
PACKAGE
修饰包
-
TYPE_PARAMETER
Type parameter declaration,1.8新增
-
TYPE_USE
Use of a type,1.8新增
自定义注解类型使用@interface关键字,这和定义一个接口非常像。注解只有成员变量,没有方法,注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型。成员变量可以使用default关键词指定默认值。
因此,开头的Retrofit中@GET注解的含义为:可以被JavaDoc工具记录的修饰方法的运行时注解,该注解接受一个参数String类型的value,默认值为""。
2. 注解的处理
在上面我们知道了,@Retention注解可以设定自定义注解的保留策略,这3个策略的生命周期长度为SOURCE<CLASS<RUNTIME。生命周期短的能起作用的地方,生命周期长的一定也能起作用。
一般如果需要在运行时去动态获取注解信息,那只能用RetentionPolicy.RUNTIME;如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用RetentionPolicy.CLASS;如果只是做一些检查性的操作,比如@Override和@SuppressWarnings,则可选用RetentionPolicy.SOURCE。
当设定为RetentionPolicy.RUNTIME时,这个注解就是运行时注解。同样地,设定为RetentionPolicy.CLASS,这个注解就是编译时注解。
如果没有处理注解的工具,那么注解也不会有什么大的作用。对于不同的注解有不同的注解处理器。针对运行时注解会采用反射机制处理,针对编译时注解会采用注解处理器AbstractProcessor来处理。
2.1 运行时注解的处理
运行时注解由于注解信息在运行时也会保留,所以一般会采用反射机制进行处理。
@GET注解的处理过程在Refrofit的ServiceMethod.java中,每一个ServiceMethod都对应一个网络请求的接口,在我们首次调用网络请求的时候会创建该对象。我们看看其建造者中相关代码:
static final class Builder<T, R> {
final Retrofit retrofit;
final Method method;
final Annotation[] methodAnnotations;
...
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
...
}
public ServiceMethod build() {
...
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
...
}
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
}
...
}
}
这里通过Method.getAnnotations()反射方法获取了该方法的所有注解,然后遍历注解,找到自定义的@GET注解,最后调用@GET注解的value获得设置的值。
2.2 编译时注解的处理
编译时注解由于注解信息只保存到.class文件阶段,所以一般会在编译时进行处理。处理的结果一般是生成一个辅助文件参与后续编译。
通常,设计编译时注解处理器需要以下几步
- 定义注解
- 编写注解处理器
- 注册注解处理器
- 应用注解
在第2步中,通常需要通过注解生成辅助文件参与后续编译,所以涉及到生成辅助文件的类库。生成辅助文件的类库根据生成文件类型的不同,分为com.squareup:javapoet和com.squareup:kotlinpoet。
2.2.1 自定义注解
新建一个Java Library的Module,命名为annotations。然后自定义一个注解:
RuntimePermissions.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface RuntimePermissions {
String[] permissions();
}
2.2.2 编写注解处理器¶
新建一个Java Library的Module,命名为processor。同时,让该模块依赖annotations模块:
processor/build.gradle
implementation project(':annotations')
接下来编写注解处理器,自定义的注解处理器需要继承AbstractProcessor,并实现4个方法:
PermissionProcessor.kt
class PermissionProcessor : AbstractProcessor() {
private lateinit var filer: Filer
private lateinit var elementUtils: Elements
private lateinit var typeUtils: Types
private lateinit var messager: Messager
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
filer = processingEnv.filer
elementUtils = processingEnv.elementUtils
typeUtils = processingEnv.typeUtils
messager = processingEnv.messager
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return hashSetOf(RuntimePermissions::class.java.canonicalName)
}
override fun getSupportedSourceVersion(): SourceVersion? {
return SourceVersion.latestSupported()
}
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
messager.printMessage(Diagnostic.Kind.WARNING, ">>> process begin")
roundEnv.getElementsAnnotatedWith(RuntimePermissions::class.java)
.filter { it.kind == ElementKind.CLASS }
.forEach {
messager.printMessage(
Diagnostic.Kind.WARNING,
"printMessage: $it, value=${it.getAnnotation(RuntimePermissions::class.java).permissions}")
}
messager.printMessage(Diagnostic.Kind.WARNING, ">>> process end")
return true
}
}
上面的代码就是一个非常简单但完整的注解处理器了,这四个方法的作用如下:
-
init
初始化方法,在这里我们初始化一些工具类,比如Filer、Elements、Types、Messager等
-
getSupportedAnnotationTypes
指定注解处理器可以哪些注解
-
getSupportedSourceVersion
用来指定你使用的Java版本,通常这里返回SourceVersion.latestSupported()
-
process
处理器开始处理注解的方法,在这里写扫描、评估和处理注解的代码,以及生成辅助文件。
在Java 7 以后,也可以使用注解来代替getSupportedAnnotationTypes方法和getSupportedSourceVersion方法,如下所示:
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("xyz.yorek.annotations.RuntimePermissions")
class PermissionProcessor : AbstractProcessor() {
private lateinit var filer: Filer
private lateinit var elementUtils: Elements
private lateinit var typeUtils: Types
private lateinit var messager: Messager
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
filer = processingEnv.filer
elementUtils = processingEnv.elementUtils
typeUtils = processingEnv.typeUtils
messager = processingEnv.messager
}
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
...
return true
}
}
在上面我们提到了四个工具类:Filer、Elements、Types、Messager,它们都有独特的作用:
- Elements:一个用来处理Element的工具类,Element就是代表源码中的句子,可以理解为DOM树的形式。通过Element.getEnclosingElement()可以获得父元素,通过Element.getEnclosedElements()可以遍历子元素。
Element元素实例如下:
package com.example; // PackageElement
public class Foo { // TypeElement
private int a; // VariableElement
private Foo other; // VariableElement
public Foo () {} // ExecuteableElement
public void setA ( // ExecuteableElement
int newA // VariableElement
) {}
}
-
Types:一个用来处理TypeMirror的工具类,可以通过TypeMirror获取类的相关信息;调用Element.asType()方法可以获得TypeMirror
-
Filer:正如这个名字所示,使用Filer你可以创建辅助文件
-
Messager: 提供给注解处理器一个报告错误、警告以及提示信息的途径。它不是日志工具,而是展示给注解处理器使用者的。
回到我们的示例注解处理器中,现在我们知道了自定义处理器是干什么的了。
- 首先,该注解处理器会处理RuntimePermissions注解,支持最新的Java版本
- 在处理注解时(process方法),首先会获取所有RuntimePermissions注解修饰的Element,这里面可能是类、方法、变量等。所以我们需要过滤掉所有不符合的Element,只留下类的Element。
- 最后,调用Messager打印出注解修饰的类的类名以及注解的值。
这个自定义注解器非常简单,复杂一点的就需要生成辅助文件了。根据辅助文件的格式的不同(.kt或.java),可以使用两个不同的类库com.squareup:javapoet和com.squareup:kotlinpoet。
2.2.3 注册注解处理器¶
接下来我们需要在processor库的main目录下面,新建一个resources/目录,在该目录下继续创建META-INF/services/目录,最后在META-INF/services/文件夹中创建javax.annotation.processing.Processor文件,内容就是自定义注解处理器的全名:
META-INF/services/javax.annotation.processing.Processor
xyz.yorek.processor.PermissionProcessor
这些步骤可以对应如下shell(在processor模块根目录下执行):
mkdir -p src/main/resources/META-INF/services/
echo xyz.yorek.processor.PermissionProcessor > src/main/resources/META-INF/services/javax.annotation.processing.Processor
如果嫌麻烦,可以使用Google开源的AutoService来完成。首先在processor库中添加如下依赖:
...
apply plugin: 'kotlin-kapt'
dependencies {
...
implementation 'com.google.auto.service:auto-service:1.0-rc6'
kapt "com.google.auto.service:auto-service:1.0-rc6"
}
在PermissionProcessor类上添加@AutoService(Processor::class)即可,这样AutoService会自动为我们生成注册文件:
@AutoService(Processor::class)
class PermissionProcessor : AbstractProcessor() {
...
}
2.2.4 应用注解¶
回到app模块中,因为我们需要使用注解以及注解处理器,所以先配置一下build.gradle:
app/build.gradle
...
apply plugin: 'kotlin-kapt'
...
dependencies {
...
implementation project(':annotations')
kapt project(':processor')
}
然后在MainActivity.java上配置一下我们的自定义注解:
@RuntimePermissions(permissions = ["Hello", "World"])
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
配置完毕后,clean后make一下,就能看到在注解器中配置的信息了。如下面第47行所示:
Executing tasks: [:processor:assemble, :processor:testClasses, :app:assembleDebug]
> Task :annotations:compileJava
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
> Task :processor:kaptGenerateStubsKotlin
> Task :processor:kaptKotlin
> Task :processor:compileKotlin
> Task :processor:compileJava NO-SOURCE
> Task :processor:processResources
> Task :processor:classes
> Task :processor:inspectClassesForKotlinIC
> Task :processor:jar
> Task :processor:assemble
> Task :processor:kaptGenerateStubsTestKotlin NO-SOURCE
> Task :processor:kaptTestKotlin
Annotation processors discovery from compile classpath is deprecated.
Set 'kapt.includeCompileClasspath = false' to disable discovery.
Run the build with '--info' for more details.
> Task :processor:compileTestKotlin NO-SOURCE
> Task :processor:compileTestJava NO-SOURCE
> Task :processor:processTestResources NO-SOURCE
> Task :processor:testClasses UP-TO-DATE
> Task :annotations:processResources NO-SOURCE
> Task :annotations:classes
> Task :annotations:jar
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:compileDebugRenderscript NO-SOURCE
> Task :app:checkDebugManifest UP-TO-DATE
> Task :app:generateDebugBuildConfig UP-TO-DATE
> Task :app:mainApkListPersistenceDebug
> Task :app:generateDebugResValues
> Task :app:generateDebugResources
> Task :app:mergeDebugResources
> Task :app:createDebugCompatibleScreenManifests
> Task :app:processDebugManifest
> Task :app:processDebugResources
> Task :app:kaptGenerateStubsDebugKotlin
> Task :app:kaptDebugKotlin
w: warning: >>> process begin
w: warning: printMessage: xyz.yorek.component.MainActivity, value=[Ljava.lang.String;@2a0c97c4
w: warning: >>> process end
w: warning: >>> process begin
w: warning: >>> process end
> Task :app:compileDebugKotlin
> Task :app:prepareLintJar UP-TO-DATE
> Task :app:generateDebugSources UP-TO-DATE
> Task :app:javaPreCompileDebug
> Task :app:compileDebugJavaWithJavac
> Task :app:compileDebugSources
> Task :app:mergeDebugShaders
> Task :app:compileDebugShaders
> Task :app:generateDebugAssets
> Task :app:mergeDebugAssets
> Task :app:checkDebugDuplicateClasses
> Task :app:transformClassesWithDexBuilderForDebug
> Task :app:validateSigningDebug
> Task :app:mergeExtDexDebug
> Task :app:mergeDexDebug
> Task :app:signingConfigWriterDebug
> Task :app:mergeDebugJniLibFolders
> Task :app:transformNativeLibsWithMergeJniLibsForDebug
> Task :app:transformNativeLibsWithStripDebugSymbolForDebug
> Task :app:processDebugJavaRes NO-SOURCE
> Task :app:transformResourcesWithMergeJavaResForDebug
> Task :app:packageDebug
> Task :app:assembleDebug
Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/5.1.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 8s
38 actionable tasks: 34 executed, 4 up-to-date
PermissionDispatcher源码解析
PermissionDispatcher是一个基于注解的动态权限请求框架。其主要工作原理就是在编译时获取指定注解的内容,然后生成辅助文件参与编译。
下面首先介绍一下PermissionDispatcher的模块关系图,以及各个模块的作用:
1. sample模块
MainActivity.kt in samplekotlin
package permissions.dispatcher.samplekotlin
import android.Manifest
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import permissions.dispatcher.*
import permissions.dispatcher.samplekotlin.camera.CameraPreviewFragment
import permissions.dispatcher.samplekotlin.contacts.ContactsFragment
@RuntimePermissions
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonCamera: Button = findViewById(R.id.button_camera)
buttonCamera.setOnClickListener {
showCameraWithPermissionCheck()
}
val buttonContacts: Button = findViewById(R.id.button_contacts)
buttonContacts.setOnClickListener {
showContactsWithPermissionCheck()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.CAMERA)
fun showCamera() {
// NOTE: Perform action that requires the permission. If this is run by PermissionsDispatcher, the permission will have been granted
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss()
}
@OnPermissionDenied(Manifest.permission.CAMERA)
fun onCameraDenied() {
// NOTE: Deal with a denied permission, e.g. by showing specific UI
// or disabling certain functionality
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show()
}
@OnShowRationale(Manifest.permission.CAMERA)
fun showRationaleForCamera(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_camera_rationale, request)
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
fun onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_ask_again, Toast.LENGTH_SHORT).show()
}
@NeedsPermission(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun showContacts() {
// NOTE: Perform action that requires the permission.
// If this is run by PermissionsDispatcher, the permission will have been granted
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, ContactsFragment.newInstance())
.addToBackStack("contacts")
.commitAllowingStateLoss()
}
@OnPermissionDenied(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun onContactsDenied() {
// NOTE: Deal with a denied permission, e.g. by showing specific UI
// or disabling certain functionality
Toast.makeText(this, R.string.permission_contacts_denied, Toast.LENGTH_SHORT).show()
}
@OnShowRationale(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun showRationaleForContacts(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_contacts_rationale, request)
}
@OnNeverAskAgain(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun onContactsNeverAskAgain() {
Toast.makeText(this, R.string.permission_contacts_never_ask_again, Toast.LENGTH_SHORT).show()
}
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
.setCancelable(false)
.setMessage(messageResId)
.show()
}
}
kt辅助文件
// This file was generated by PermissionsDispatcher. Do not modify!
@file:JvmName("MainActivityPermissionsDispatcher")
package permissions.dispatcher.samplekotlin
import androidx.core.app.ActivityCompat
import java.lang.ref.WeakReference
import kotlin.Array
import kotlin.Int
import kotlin.IntArray
import kotlin.String
import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.PermissionUtils
private const val REQUEST_SHOWCAMERA: Int = 0
private val PERMISSION_SHOWCAMERA: Array<String> = arrayOf("android.permission.CAMERA")
private const val REQUEST_SHOWCONTACTS: Int = 1
private val PERMISSION_SHOWCONTACTS: Array<String> = arrayOf("android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS")
fun MainActivity.showCameraWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_SHOWCAMERA)) {
showCamera()
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCAMERA)) {
showRationaleForCamera(MainActivityShowCameraPermissionRequest(this))
} else {
ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA)
}
}
}
fun MainActivity.showContactsWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_SHOWCONTACTS)) {
showContacts()
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCONTACTS)) {
showRationaleForContacts(MainActivityShowContactsPermissionRequest(this))
} else {
ActivityCompat.requestPermissions(this, PERMISSION_SHOWCONTACTS, REQUEST_SHOWCONTACTS)
}
}
}
fun MainActivity.onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {
when (requestCode) {
REQUEST_SHOWCAMERA ->
{
if (PermissionUtils.verifyPermissions(*grantResults)) {
showCamera()
} else {
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCAMERA)) {
onCameraNeverAskAgain()
} else {
onCameraDenied()
}
}
}
REQUEST_SHOWCONTACTS ->
{
if (PermissionUtils.verifyPermissions(*grantResults)) {
showContacts()
} else {
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCONTACTS)) {
onContactsNeverAskAgain()
} else {
onContactsDenied()
}
}
}
}
}
private class MainActivityShowCameraPermissionRequest(
target: MainActivity
) : PermissionRequest {
private val weakTarget: WeakReference<MainActivity> = WeakReference(target)
override fun proceed() {
val target = weakTarget.get() ?: return
ActivityCompat.requestPermissions(target, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA)
}
override fun cancel() {
val target = weakTarget.get() ?: return
target.onCameraDenied()
}
}
private class MainActivityShowContactsPermissionRequest(
target: MainActivity
) : PermissionRequest {
private val weakTarget: WeakReference<MainActivity> = WeakReference(target)
override fun proceed() {
val target = weakTarget.get() ?: return
ActivityCompat.requestPermissions(target, PERMISSION_SHOWCONTACTS, REQUEST_SHOWCONTACTS)
}
override fun cancel() {
val target = weakTarget.get() ?: return
target.onContactsDenied()
}
}
MainActivity.kt in sample
package permissions.dispatcher.sample
import android.Manifest
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import permissions.dispatcher.*
import permissions.dispatcher.sample.camera.CameraPreviewFragment
import permissions.dispatcher.sample.contacts.ContactsFragment
@RuntimePermissions
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonCamera: Button = findViewById(R.id.button_camera)
buttonCamera.setOnClickListener {
showCameraWithPermissionCheck()
}
val buttonContacts: Button = findViewById(R.id.button_contacts)
buttonContacts.setOnClickListener {
showContactsWithPermissionCheck()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.CAMERA)
fun showCamera() {
// NOTE: Perform action that requires the permission. If this is run by PermissionsDispatcher, the permission will have been granted
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss()
}
@OnPermissionDenied(Manifest.permission.CAMERA)
fun onCameraDenied() {
// NOTE: Deal with a denied permission, e.g. by showing specific UI
// or disabling certain functionality
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show()
}
@OnShowRationale(Manifest.permission.CAMERA)
fun showRationaleForCamera(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_camera_rationale, request)
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
fun onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_ask_again, Toast.LENGTH_SHORT).show()
}
@NeedsPermission(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun showContacts() {
// NOTE: Perform action that requires the permission.
// If this is run by PermissionsDispatcher, the permission will have been granted
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, ContactsFragment.newInstance())
.addToBackStack("contacts")
.commitAllowingStateLoss()
}
@OnPermissionDenied(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun onContactsDenied() {
// NOTE: Deal with a denied permission, e.g. by showing specific UI
// or disabling certain functionality
Toast.makeText(this, R.string.permission_contacts_denied, Toast.LENGTH_SHORT).show()
}
@OnShowRationale(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun showRationaleForContacts(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_contacts_rationale, request)
}
@OnNeverAskAgain(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun onContactsNeverAskAgain() {
Toast.makeText(this, R.string.permission_contacts_never_ask_again, Toast.LENGTH_SHORT).show()
}
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
.setCancelable(false)
.setMessage(messageResId)
.show()
}
}
kt辅助文件
// This file was generated by PermissionsDispatcher. Do not modify!
@file:JvmName("MainActivityPermissionsDispatcher")
package permissions.dispatcher.sample
import androidx.core.app.ActivityCompat
import java.lang.ref.WeakReference
import kotlin.Array
import kotlin.Int
import kotlin.IntArray
import kotlin.String
import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.PermissionUtils
private const val REQUEST_SHOWCAMERA: Int = 0
private val PERMISSION_SHOWCAMERA: Array<String> = arrayOf("android.permission.CAMERA")
private const val REQUEST_SHOWCONTACTS: Int = 1
private val PERMISSION_SHOWCONTACTS: Array<String> = arrayOf("android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS")
fun MainActivity.showCameraWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_SHOWCAMERA)) {
showCamera()
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCAMERA)) {
showRationaleForCamera(MainActivityShowCameraPermissionRequest(this))
} else {
ActivityCompat.requestPermissions(this, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA)
}
}
}
fun MainActivity.showContactsWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this, *PERMISSION_SHOWCONTACTS)) {
showContacts()
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCONTACTS)) {
showRationaleForContacts(MainActivityShowContactsPermissionRequest(this))
} else {
ActivityCompat.requestPermissions(this, PERMISSION_SHOWCONTACTS, REQUEST_SHOWCONTACTS)
}
}
}
fun MainActivity.onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {
when (requestCode) {
REQUEST_SHOWCAMERA ->
{
if (PermissionUtils.verifyPermissions(*grantResults)) {
showCamera()
} else {
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCAMERA)) {
onCameraNeverAskAgain()
} else {
onCameraDenied()
}
}
}
REQUEST_SHOWCONTACTS ->
{
if (PermissionUtils.verifyPermissions(*grantResults)) {
showContacts()
} else {
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *PERMISSION_SHOWCONTACTS)) {
onContactsNeverAskAgain()
} else {
onContactsDenied()
}
}
}
}
}
private class MainActivityShowCameraPermissionRequest(
target: MainActivity
) : PermissionRequest {
private val weakTarget: WeakReference<MainActivity> = WeakReference(target)
override fun proceed() {
val target = weakTarget.get() ?: return
ActivityCompat.requestPermissions(target, PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA)
}
override fun cancel() {
val target = weakTarget.get() ?: return
target.onCameraDenied()
}
}
private class MainActivityShowContactsPermissionRequest(
target: MainActivity
) : PermissionRequest {
private val weakTarget: WeakReference<MainActivity> = WeakReference(target)
override fun proceed() {
val target = weakTarget.get() ?: return
ActivityCompat.requestPermissions(target, PERMISSION_SHOWCONTACTS, REQUEST_SHOWCONTACTS)
}
override fun cancel() {
val target = weakTarget.get() ?: return
target.onContactsDenied()
}
}
2. library模块
library模块仅有一个PermissionUtils工具类,在生成的辅助文件中使用。该类有如下方法:
-
boolean verifyPermissions(int... grantResults)
检查这些权限是否都是PERMISSION_GRANTED状态;所有权限都是该状态,才会返回true
-
boolean hasSelfPermissions(Context context, String... permissions)
检查是否已经拥有了要申请的这些权限;所有权限都拥有了,才会返回true
-
boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions)
boolean shouldShowRequestPermissionRationale(Fragment fragment, String... permissions)
检查是否应该弹出权限说明框;只要有一个权限需要弹出说明框,就会返回true
3. annotation模块
annotation模块里面有5个注解
除了上面的几个注解外,还有一个与@OnShowRationale注解搭配使用的PermissionRequest接口,该接口有两个方法,分别用来进行权限的继续申请或者取消:
PermissionRequest.java
/**
* Interface used by {@link OnShowRationale} methods to allow for continuation
* or cancellation of a permission request.
*/
public interface PermissionRequest {
void proceed();
void cancel();
}
用法在示例中有展示,根据权限提示框上对应的按钮,需要触发对应的方法。示例代码粘贴如下:
@OnShowRationale(Manifest.permission.CAMERA)
fun showRationaleForCamera(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_camera_rationale, request)
}
@OnShowRationale(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
fun showRationaleForContacts(request: PermissionRequest) {
// NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog.
// Call proceed() or cancel() on the provided PermissionRequest to continue or abort
showRationaleDialog(R.string.permission_contacts_rationale, request)
}
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
.setCancelable(false)
.setMessage(messageResId)
.show()
}
4. processor模块
接着就是最重要的processor模块了,这是一个注解处理器模块,用途就是在编辑时生成辅助文件,辅助文件参与后续的编译过程。
要了解一个注解处理器的作用是什么,我们先要通过注册文件找到它,注册文件的目录是固定的:
src/main/resources/META-INF/services/javax.annotation.processing.Processor
permissions.dispatcher.processor.PermissionsProcessor
显然,processor模块的注解处理器代码就是PermissionsProcessor.kt,这就是本节的要点了。
同时,我们注意到resources/META-INF/目录下还有一个gradle目录,下面也有一个文件:
src/main/resources/META-INF/gradle/incremental.annotation.processors
permissions.dispatcher.processor.PermissionsProcessor,isolating
这是配置给Gradle的,目的是开启Gradle增量编译.
首先看看PermissionsProcessor除了process外的其他方法:
/** Element Utilities, obtained from the processing environment */
var ELEMENT_UTILS: Elements by Delegates.notNull()
/** Type Utilities, obtained from the processing environment */
var TYPE_UTILS: Types by Delegates.notNull()
class PermissionsProcessor : AbstractProcessor() {
/* 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 {
...
return true
}
}
这三个方法都是非常标准的写法,在getSupportedAnnotationTypes方法中表明了该注解处理器只处理@RuntimePermissions注解。这是因为,@RuntimePermissions注解修饰的是一个类,其他四种类型的注解都分布在该类里面,这些注解可以通过Elements来获取,就和DOM树一样。
接下来就是process方法了。在该方法中的步骤如下:
-
首先创建了一个用于自增请求码的RequestCodeProvider对象;
-
然后获取所有注解了@RuntimePermissions的Element,并将这些Element包装成为一个RuntimePermissionsElement对象;
-
最后根据Element是否有@Metadata注解来判断源文件是Java编写还是Kotlin编写,进而调用相应的方法生成相应格式的辅助文件。
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
// Create a RequestCodeProvider which guarantees unique request codes for each permission request
val requestCodeProvider = RequestCodeProvider()
// The Set of annotated elements needs to be ordered
// in order to achieve Deterministic, Reproducible Builds
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) {
processKotlin(it, rpe, requestCodeProvider)
} else {
processJava(it, rpe, requestCodeProvider)
}
}
return true
}
下面依次说说process方法三个步骤中的要点。 RequestCodeProvider实现就是靠AtomicInteger.andIncrement方法,非常简单:
class RequestCodeProvider {
private val currentCode = AtomicInteger(0)
fun nextRequestCode(): Int = currentCode.andIncrement
}
至于RuntimePermissionsElement,则是TypeElement对象的包装类,而TypeElement对象就是我们在注解的Activity、Fragment类了。RuntimePermissionsElement的作用就是解析Element的各种值、保存Element里面四种注解的子Element,另外还有针对四种注解注解的子Element的检查。代码如下:
class RuntimePermissionsElement(val element: TypeElement) {
val typeName: TypeName = TypeName.get(element.asType())
val ktTypeName = element.asType().asTypeName()
val typeVariables = element.typeParameters.map { TypeVariableName.get(it) }
val ktTypeVariables = element.typeParameters.map { it.asTypeVariableName() }
val packageName = element.packageName()
val inputClassName = element.simpleString()
val generatedClassName = inputClassName + GEN_CLASS_SUFFIX
val needsElements = element.childElementsAnnotatedWith(NeedsPermission::class.java)
private val onRationaleElements = element.childElementsAnnotatedWith(OnShowRationale::class.java)
private val onDeniedElements = element.childElementsAnnotatedWith(OnPermissionDenied::class.java)
private val onNeverAskElements = element.childElementsAnnotatedWith(OnNeverAskAgain::class.java)
init {
validateNeedsMethods()
validateRationaleMethods()
validateDeniedMethods()
validateNeverAskMethods()
}
...
}
注意最前面4个属性,以“kt”开头的是给Kotlin使用的,否则是给Java使用的;而且两者也是使用对应的的poet库文件获取的。但实际上,两者的值都是一致的,没有什么区别。且有一些字段调用的方法是kotlin扩展方法,需要注意一下。 下表是以示例中的MainActivity为例子,给出的RuntimePermissionsElement中各个字段的解释以及值。
最后就是对每一个需要产生辅助文件的Element进行处理了。这里首先会通过it.getAnnotation(Metadata::class.java)获取Metadata,该注解是Kotlin文件特有的注解,所有的kotlin文件在经过kotlin编译器之后都会带上该注解。所以我们可以通过Element是否有该注解来确定是否是Kotlin文件。 如果是kotlin文件,则调用processKotlin方法,否则调用processJava方法。两个方法以及相关的代码如下所示:
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)
}
val javaProcessorUnits = listOf(JavaActivityProcessorUnit(), JavaFragmentProcessorUnit(), JavaConductorProcessorUnit())
val kotlinProcessorUnits = listOf(KotlinActivityProcessorUnit(), KotlinFragmentProcessorUnit(), KotlinConductorProcessorUnit())
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)
}
}
很显然,在processKotlin/processJava方法中,首先从kotlinProcessorUnits/javaProcessorUnits中找到能处理Element的处理器,然后调用处理器生成辅助文件,最后通过Filer写入到磁盘中。这就是上面代码干的事情。
如何找到能够处理Element的处理器,这里涉及到了TypeMirror,通过TypeMirror就可以判断两个Element之间的关系。KotlinActivityProcessorUnit、KotlinBaseProcessorUnit可以处理的TypeMirror类型如下:
fun typeMirrorOf(className: String): TypeMirror = ELEMENT_UTILS.getTypeElement(className).asType()
class KotlinActivityProcessorUnit : KotlinBaseProcessorUnit() {
override fun getTargetType(): TypeMirror = typeMirrorOf("android.app.Activity")
...
}
class KotlinFragmentProcessorUnit : KotlinBaseProcessorUnit() {
override fun getTargetType(): TypeMirror = typeMirrorOf("androidx.fragment.app.Fragment")
...
}
因此,我们实例中的MainActivity就会被KotlinActivityProcessorUnit和JavaActivityProcessorUnit所处理。而两者createFile方法的实现在各自的基类KotlinBaseProcessorUnit、JavaBaseProcessorUnit中。
下面简单说一下KotlinBaseProcessorUnit、JavaBaseProcessorUnit在创建辅助文件时的代码,过多的细节不做进一步说明。
KotlinBaseProcessorUnit.kt
override fun createFile(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): FileSpec {
return FileSpec.builder(rpe.packageName, rpe.generatedClassName)
.addComment(FILE_COMMENT)
.addAnnotation(createJvmNameAnnotation(rpe.generatedClassName))
.addProperties(createProperties(rpe, requestCodeProvider))
.addFunctions(createWithPermissionCheckFuns(rpe))
.addFunctions(createOnShowRationaleCallbackFuns(rpe))
.addFunctions(createPermissionHandlingFuns(rpe))
.addTypes(createPermissionRequestClasses(rpe))
.build()
}
第2行builder方法指定了文件将要生成在哪个包下,文件名是什么。
第3行则在生成的文件的第一行添加了一行注释。
第4行将会在辅助文件头中添加@file:JvmName("MainActivityPermissionsDispatcher") 注解,若没有该注解,则文件在编译后会加上“Kt”的后缀,Java代码调用时该类时需要注意。
第5行会为每一个@NeedsPermission请求方法生成一个对应的请求码以及权限数组,且如果请 求方法有参数,会生成额外的对象(GrantableRequest)或者字段来保存传入的值。
第6行为每个@NeedsPermission请求方法生成对应的处理方法。
第7行为每个无参数的@NeedsPermission请求方法生成对应的处理方法。
第8行生成权限请求回调方法。
第9行为每个请求生成一个PermissionRequest的实现类。
JavaBaseProcessorUnit.kt
final override fun createFile(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): JavaFile {
return JavaFile.builder(rpe.packageName, createTypeSpec(rpe, requestCodeProvider))
.addFileComment(FILE_COMMENT)
.build()
}
private fun createTypeSpec(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): TypeSpec {
return TypeSpec.classBuilder(rpe.generatedClassName)
.addOriginatingElement(rpe.element) // for incremental annotation processing
.addModifiers(Modifier.FINAL)
.addFields(createFields(rpe, requestCodeProvider))
.addMethod(createConstructor())
.addMethods(createWithPermissionCheckMethods(rpe))
.addMethods(createOnShowRationaleCallbackMethods(rpe))
.addMethods(createPermissionHandlingMethods(rpe))
.addTypes(createPermissionRequestClasses(rpe))
.build()
}