使用Kotlin编写6.0权限检查框架学习总结

来源:互联网 发布:最终信仰知轩 编辑:程序博客网 时间:2024/04/25 23:31

使用Kotlin编写6.0权限检查框架学习总结

很久之前就学习过编译时注解的原理,不过对于java的Element使用不多,这次又重新复习了一遍,顺便学习了一下使用kotlin来编写。
对于运行时注入和编译时注入,不知道的同学百度一下吧,其实不难理解,这里是主要是使用AbstractProcessor类在编译是自动生成类来避免一些使用反射的场景。要使用这个类首先需要一些预备知识。

Java 的Element

自动生成代码需要对这个接口做一定了解,大家可以参看这边文章,基本使用讲的比较详细了。

[ Android 编译时注解-提升-butterknife ]

编译时注入项目结构

这里写图片描述

这里是app依赖permission-compiler,permission-compiler依赖permission-annotation。后面两个是java library,一定要是java的module,否则写注解处理器的时候有些类会找不到。接着首先修改build.gradle添加kotlin的库以及插件。

root

// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {    ext.kotlin_version = '1.1.2-3'    ext.anko_version = '0.10.0'    repositories {        jcenter()    }    dependencies {        classpath 'com.android.tools.build:gradle:2.3.3'        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }}allprojects {    repositories {        jcenter()    }}task clean(type: Delete) {    delete rootProject.buildDir}

app

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'//扩展插件,使用可以直接引用布局里view的id进行操作apply plugin: 'kotlin-android-extensions'//kotlin的apt 类似于java的aptapply plugin: 'kotlin-kapt'android {    compileSdkVersion 25    buildToolsVersion "25.0.3"    defaultConfig {        applicationId "com.dn.tim.timcompiler"        minSdkVersion 15        targetSdkVersion 25        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:25.3.1'    compile 'com.android.support.constraint:constraint-layout:1.0.2'    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"    compile "org.jetbrains.anko:anko:$anko_version"    testCompile 'junit:junit:4.12'    compile project(':permission-annotation')    kapt project(':permission-compiler')}repositories {    mavenCentral()    mavenLocal()}

permission-annotation

apply plugin: 'kotlin'dependencies {    testCompile 'junit:junit:4.12'    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"}

permission-compiler

apply plugin: 'kotlin'dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    compile 'com.squareup:kotlinpoet:0.3.0'    compile project(':permission-annotation')    testCompile 'junit:junit:4.12'}

主要是使用kotlin,配置gradle会稍微麻烦一点.

注解module

permission-annotation

这个module主要是定义了一些注解,为的是编译时能获取到对应的方法来生成代码。这里主要有三个注解
这里写图片描述
@Need表示需要获取请求的权限,OnDelined表示申请权限被拒绝,OnNeverAsk表示不再询问,三个注解都是一样接受一个参数。三个注解仅仅是名字有区别

package com.exampleimport java.lang.annotation.Retentionimport java.lang.annotation.RetentionPolicy@Target(AnnotationTarget.FUNCTION)@Retention(RetentionPolicy.SOURCE)//主要接收运行时权限定义的字符串annotation class Need(val values: Array<String>)

compiler Module

permission-compiler

首先写注解处理器PermissionCompiler

package com.exampleimport javax.annotation.processing.AbstractProcessorimport javax.annotation.processing.ProcessingEnvironmentimport javax.annotation.processing.RoundEnvironmentimport javax.lang.model.SourceVersionimport javax.lang.model.element.ExecutableElementimport javax.lang.model.element.TypeElementclass PermissionCompiler: AbstractProcessor() {     /**     *设置支持的注解类型,支持获取Need注解的元素     */    override fun getSupportedAnnotationTypes(): Set<String> {        return setOf(Need::class.java.canonicalName)    }     /**     * 工具类的初始化     */     @Synchronized override fun init(processingEnvironment: ProcessingEnvironment) {        super.init(processingEnvironment)         FILER_UTILS = processingEnvironment.filer         ELEMENT_UTILS = processingEnvironment.elementUtils         TYPE_UTILS = processingEnvironment.typeUtils    }     /**     * 设置支持最新的源码版本     */    override fun getSupportedSourceVersion(): SourceVersion {        return SourceVersion.latestSupported()    }     //返回值代表是否还提供给其他注解处理器处理    override fun process(set: MutableSet<out TypeElement>, roundEnvironment: RoundEnvironment): Boolean {        //获得被注解了 @Need 的元素 元素是指: 代码中的元素,比如类或者方法        roundEnvironment.getElementsAnnotatedWith(Need::class.java).groupBy {            it.enclosingElement        }.map {            ClassBuilder(it.key as TypeElement, it.value.map { NeedMethod(it as ExecutableElement) })        }.map {            it.brewKotlin()        }        return false    }}

注解处理器的主体就这几个方法,上面三个方法都是基本都是固定代码。最后一个方法就是核心了,具体处理kotlin文件的生成,但是这个类使用之前还需要添加resource文件夹。具体结构目录:
这里写图片描述
这个javax.annotation.processing.Processor文件里添加com.example.PermissionCompiler,现在注解处理器就可以正常使用了。

PermissionCompiler分析

我们目的是根据注解标注的方法来获取元素,在process方法中来分析,看这段代码

roundEnvironment.getElementsAnnotatedWith(Need::class.java).groupBy {            it.enclosingElement        }

roundEnvironment.getElementsAnnotatedWith(Need::class.java)返回的是由Need注解的方法元素,也就是set<ExecutableElement> 调用groupBy通过it.enclosingElement为key生成Map。这里我们的注解都是方法上的,所以it.enclosingElement是类也就是TypeElement。也就是说我们通过上面的方法获取了以TypeElement为key,List<ExecutableElement> 为value的Map。

接下来调用
.map {
ClassBuilder(it.key as TypeElement, it.value.map { NeedMethod(it as ExecutableElement) })
}

kotlin中Map类的map方法会根据传入的方法返回一个List。这里我们得到一个ClassBuilder的List。
ClassBuilder的就是生成kotlin文件的封装类了,最后在foreach调用生成文件的方法,其实最后的map是没有必要的。

ClassBuilder分析

classBuilder类用来生成kotlin代码需要传入TypeElement,以及List<NeedMethod> NeedMethod类封装了所有需要请求的权限名和方法名。

package com.exampleimport javax.lang.model.element.ExecutableElementclass NeedMethod (element: ExecutableElement){    //获得权限名    val permissionName = element.getAnnotation(Need::class.java).values    //获得定义的方法名    val methodName = element.simpleName()}

这里我直接把完整ClassBuilder粘贴出来,对应注释以及生成的类进行对比,便于理解。

package com.exampleimport com.squareup.kotlinpoet.*import com.squareup.kotlinpoet.TypeName.Companion.asTypeNameimport java.util.concurrent.atomic.AtomicIntegerimport javax.lang.model.element.TypeElementimport javax.tools.StandardLocationclass ClassBuilder (val typeElement: TypeElement, val methodElement: List<NeedMethod>){    companion object {        const val CALLER = "target"        const val REQUEST = "requestCode"        const val RESULT = "grantResults"    }    //包名    val PACKAGE_NAME = ELEMENT_UTILS.getPackageOf(typeElement).qualifiedName.toString()    //类的全类名    val FULL_NAME = typeElement.qualifiedName.toString()    //类名    val CLASS_NAME by lazy {        FULL_NAME.substringAfterLast(".")    }    //获得当前类的Type    val TYPE_TARGET = ELEMENT_UTILS.getTypeElement(FULL_NAME).asType()    //当前类的ClassName    val TARGET = TYPE_TARGET.asTypeName()    //判断当前类是否为Activity等的子类    val isActivity = TYPE_UTILS.isSubtype(TYPE_TARGET, TYPE_ACTIVITY)    val isFragment = TYPE_UTILS.isSubtype(TYPE_TARGET, TYPE_FRAGMENT)    val isSupportFragment = if (null == TYPE_SUPPORT) false else TYPE_UTILS.isSubtype(TYPE_TARGET, TYPE_SUPPORT)    //请求码    val currentCode = AtomicInteger(100000)    //标有onDenieds的元素    val onDenieds = typeElement.getChildWithAnnotation(OnDelined::class.java)    val onNeverAsk = typeElement.getChildWithAnnotation(OnNeverAsk::class.java)    fun brewKotlin() {        //类名加上Permission,类似MainActivityPermission        val className = "${CLASS_NAME}Permission"        //类,接口,枚举生成类的builder        val typeSpec = TypeSpec.objectBuilder(className)        //调用checkSelfPermission的成员名        var checkCaller = CALLER            when {            /*            * 如果是Fragment或者activity添加target就可以了            * 调用checkSelfPermission 需要从Fragment获得Activity            * 调用requestPermissions shouldShowRequestPermissionRationale 可以使用Fragment            * */            isSupportFragment || isFragment || isActivity -> {                if (!isActivity)                    checkCaller = "$CALLER.activity"            }            else -> {                throw Exception("checkSelfPermission should only call by Activity or Fragment")            }        }        //回调请求结果,这个类的builder是用来生成方法的        //方法名,这个方法是权限检查的回调        val requestResult = FunSpec.builder("onRequestPermissionsResult")                //参数1 Activity或者Fragment                .addParameter(CALLER,TARGET)                //参数2 返回的请求码                .addParameter(REQUEST,Int::class)                //参数3 返回到结果,是个数组                .addParameter(RESULT, IntArray::class)                //注释                .addKdoc("Please call this method in $CLASS_NAME#onRequestPermissionsResult")        //方法中的代码块,使用when(){}来进行筛选        val resultBlock = CodeBlock.builder()                .beginControlFlow("when($REQUEST)")        //遍历所有的NeedMethod的内容        methodElement.forEach {            need ->            //target中对应的方法,传入的activity 或者fragment            val invokeOriginal = "$CALLER.${need.methodName}()"            //增加类中的全局变量            //解构赋值来获取字符串,后面引用这两个变量            val (propertyPermission, propertyRequest) = addProperties(need, typeSpec)            //添加权限方法            addPermissionMethod(need, typeSpec, propertyPermission, propertyRequest, checkCaller, invokeOriginal)            //添加回调方法            addRequestResult(need, resultBlock, propertyPermission, propertyRequest, invokeOriginal)        }        resultBlock.endControlFlow()        typeSpec.addFun(requestResult.addCode(resultBlock.build()).build())        //生成文件        FILER_UTILS.createResource( StandardLocation.SOURCE_OUTPUT, PACKAGE_NAME, "$className.kt")                .openWriter()                .use {                    with(KotlinFile.builder(PACKAGE_NAME,className)){                        addType(typeSpec.build())                        addFileComment("generated by Kapt , Do not modify!")                        build()                    }.writeTo(it)                }    }    fun addProperties(need:NeedMethod, typeSpec: TypeSpec.Builder):Array<String> {        //创建属性 值为方法需要权限的数组        val propertyPermission = "PERMISSION_${need.methodName.toUpperCase()}"        //方法请求码        val propertyRequest = "REQUEST_${need.methodName.toUpperCase()}"        //生成类似的android.permission.ACCESS_FINE_LOCATION","android.permission.ACCESS_COARSE_LOCATION        //的字符串        val permissionStr = need.permissionName.joinToString("\",\"")        //在类中增加两个属性        typeSpec.addProperties(                listOf(PropertySpec                        .builder(propertyPermission, STRINGARRAY, KModifier.PRIVATE)                        .initializer("arrayOf(\"$permissionStr\")")                        .addKdoc("from $CLASS_NAME#${need.methodName}")                        .build(),                        PropertySpec                                .builder(propertyRequest, Int::class, KModifier.PRIVATE)                                .addKdoc("request $CLASS_NAME#${need.methodName} needs permission")                                .initializer("${currentCode.getAndIncrement()}")                                .build()                )        )        return arrayOf(propertyPermission,propertyRequest)    }    //调用此方法来替代被NeedPermission注解的方法    fun addPermissionMethod(need: NeedMethod, typeSpec: TypeSpec.Builder, propertyPermission:String,                            propertyRequest:String, checkCaller:String, invokeOriginal:String) {        //方法狗仔        val builder = FunSpec.builder("${need.methodName}").run {            //添加参数            addParameter(CALLER, TARGET)            //加入权限判断代码            addCode(CodeBlock.builder()            //需要使用%T转义,这样才会自动导包                    //判断版本号是否大于23api                    //挖坑 --.beginControlFlow("if (%T.VERSION.SDK_INT) >= %T.VERSION_CODES.M", BUILD, BUILD)                    .beginControlFlow("if (%T.VERSION.SDK_INT >= %T.VERSION_CODES.M)", BUILD, BUILD)                    //未获取权限的列表                    .addStatement(" val list = $propertyPermission.filter {$checkCaller.checkSelfPermission(it) == %T.PERMISSION_DENIED}", PACKAGEMANAGER)                    .beginControlFlow("if(list.isEmpty())")                    .addStatement(invokeOriginal)                    .nextControlFlow("else")                    //请求权限                    .addStatement("$CALLER.requestPermissions(list.toTypedArray(),$propertyRequest)")                    .endControlFlow()                    .nextControlFlow("else")                    .addStatement(invokeOriginal)                    .endControlFlow()                    .build()            )            addKdoc("Please call this method replace $CLASS_NAME#${need.methodName}")        }        typeSpec.addFun(builder.build())    }    fun addRequestResult(need: NeedMethod, resultBlock: CodeBlock.Builder, propertyPermission: String,                         propertyRequest: String, invokeOriginal: String) {        val onDenied = findOnDenied(need)        val onNeverAsk = findOnNeverAsk(need)        resultBlock.beginControlFlow("$propertyRequest -> ")        resultBlock.beginControlFlow("if($RESULT.all { it == PackageManager.PERMISSION_GRANTED})")        resultBlock.addStatement(invokeOriginal)        resultBlock.nextControlFlow("else")        resultBlock.beginControlFlow("if ($propertyPermission.all {$CALLER.shouldShowRequestPermissionRationale(it)})")        // 这里回调请求失败        if (onDenied != null) {            resultBlock.addStatement("$CALLER.${onDenied.simpleName()}()")        }        resultBlock.nextControlFlow("else")        //这里回调不再询问        if (onNeverAsk != null) {            resultBlock.addStatement("$CALLER.${onNeverAsk.simpleName()}()")        }        resultBlock.endControlFlow()        resultBlock.endControlFlow()        resultBlock.endControlFlow()    }}

执行了生成的类

// generated by Kapt , Do not modify!package com.dn.tim.timcompilerimport android.content.pm.PackageManagerimport android.os.Buildimport kotlin.Arrayimport kotlin.Intimport kotlin.IntArrayimport kotlin.Stringobject MainActivityPermission {  /**   * from MainActivity#showCamera */  private val PERMISSION_SHOWCAMERA: Array<String> = arrayOf("android.permission.CAMERA")  /**   * request MainActivity#showCamera needs permission */  private val REQUEST_SHOWCAMERA: Int = 100000  /**   * from MainActivity#showLocation */  private val PERMISSION_SHOWLOCATION: Array<String> = arrayOf("android.permission.ACCESS_FINE_LOCATION","android.permission.ACCESS_COARSE_LOCATION")  /**   * request MainActivity#showLocation needs permission */  private val REQUEST_SHOWLOCATION: Int = 100001  /**   * Please call this method replace MainActivity#showCamera */  fun showCamera(target: MainActivity) {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {       val list = PERMISSION_SHOWCAMERA.filter {target.checkSelfPermission(it) == PackageManager.PERMISSION_DENIED}      if(list.isEmpty()) {        target.showCamera()      } else {        target.requestPermissions(list.toTypedArray(),REQUEST_SHOWCAMERA)      }    } else {      target.showCamera()    }  }  /**   * Please call this method replace MainActivity#showLocation */  fun showLocation(target: MainActivity) {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {       val list = PERMISSION_SHOWLOCATION.filter {target.checkSelfPermission(it) == PackageManager.PERMISSION_DENIED}      if(list.isEmpty()) {        target.showLocation()      } else {        target.requestPermissions(list.toTypedArray(),REQUEST_SHOWLOCATION)      }    } else {      target.showLocation()    }  }  /**   * Please call this method in MainActivity#onRequestPermissionsResult */  fun onRequestPermissionsResult(target: MainActivity, requestCode: Int, grantResults: IntArray) {    when(requestCode) {      REQUEST_SHOWCAMERA ->  {        if(grantResults.all { it == PackageManager.PERMISSION_GRANTED}) {          target.showCamera()        } else {          if (PERMISSION_SHOWCAMERA.all {target.shouldShowRequestPermissionRationale(it)}) {            target.deniedCamera()          } else {            target.neverAskCamera()          }        }      }      REQUEST_SHOWLOCATION ->  {        if(grantResults.all { it == PackageManager.PERMISSION_GRANTED}) {          target.showLocation()        } else {          if (PERMISSION_SHOWLOCATION.all {target.shouldShowRequestPermissionRationale(it)}) {            target.deniedLocation()          } else {            target.neverAskLocation()          }        }      }    }  }}

其实就是一点一点把类写出来,逻辑都比较简单,不太懂的直接看有点不太好理解。最后 贴一下扩展的Util方法,这里定义的东西是最顶层的,可以直接调用

package com.exampleimport com.squareup.kotlinpoet.ClassNameimport com.squareup.kotlinpoet.ParameterizedTypeNameimport javax.annotation.processing.Filerimport javax.lang.model.util.Elementsimport javax.lang.model.util.Typesimport kotlin.properties.Delegatesimport com.squareup.kotlinpoet.TypeName.Companion.asTypeNameimport java.util.*import javax.lang.model.element.Elementimport javax.lang.model.element.ExecutableElementvar FILER_UTILS: Filer by Delegates.notNull()var ELEMENT_UTILS: Elements by Delegates.notNull()var TYPE_UTILS: Types by Delegates.notNull()val TYPE_ACTIVITY by lazy { ELEMENT_UTILS.getTypeElement("android.app.Activity").asType() }val TYPE_FRAGMENT by lazy { ELEMENT_UTILS.getTypeElement("android.app.Fragment").asType() }val TYPE_SUPPORT by lazy {    try {        ELEMENT_UTILS.getTypeElement("android.support.v4.app.Fragment").asType()    } catch (e:Exception) {        null    }}//需手动导入 com.squareup.kotlinpoet.TypeName.Companion.asTypeNameval ACTIVITY by lazy { TYPE_ACTIVITY.asTypeName() }val FRAGMENT by lazy { TYPE_FRAGMENT.asTypeName() }val SUPPORT by lazy { TYPE_SUPPORT?.asTypeName() }val STRING = String::class.asTypeName()val ARRAY = ClassName("kotlin","Array")val STRINGARRAY = ParameterizedTypeName.get(ARRAY, STRING)val BUILD:ClassName = ClassName.Companion("android.os","Build")val PACKAGEMANAGER = ClassName("android.content.pm","PackageManager")//因为Kotlin这里获得的方法名会有$(buildType) eg $app_debugfun ExecutableElement.simpleName():String{    val simpleName = simpleName.toString()    val index = simpleName.lastIndexOf("$")    if (index > 0) {        return simpleName.substring(0, index)    }    return simpleName}//获得被指定注解定义的元素fun <A:Annotation> Element.getChildWithAnnotation(annotationClass: Class<A>):List<ExecutableElement> =        enclosedElements.filter {            it.getAnnotation(annotationClass) != null        }.map {            it as ExecutableElement        }//查找匹配的注解方法fun <A:Annotation> findMatchingMethodForNeeds(needsMethod: NeedMethod,                                              otherElements: List<ExecutableElement>,                                              annotationType:Class<A>): ExecutableElement? {    val values = needsMethod.permissionName    //找到第一个匹配的方法    return otherElements.firstOrNull{        val aType = it.getAnnotation(annotationType)        if (aType is OnNeverAsk) {            Arrays.equals(aType.values, values)        } else if (aType is OnDelined) {            Arrays.equals(aType.values, values)        } else {            false        }    }}fun ClassBuilder.findOnDenied(needsMethod: NeedMethod): ExecutableElement?{    return findMatchingMethodForNeeds(needsMethod, onDenieds, OnDelined::class.java)}fun ClassBuilder.findOnNeverAsk(needsMethod: NeedMethod): ExecutableElement? {    return findMatchingMethodForNeeds(needsMethod, onNeverAsk, OnNeverAsk::class.java)}

其实编译时注入的原理还是比较简单,主要是对java的Element使用较少,很多知识点不太熟悉。其实无非就是根据注解的位置获取相应的元素,然后生成辅助类,这样使用的时候就会轻松很多。Kotlin的很多扩展方法就用起来很方便,比如很多集合的扩展方法,都非常实用,只是目前项目使用较少,感觉kotlin真的比java方便多了,怪不得被google扶正了。