使用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扶正了。
- 使用Kotlin编写6.0权限检查框架学习总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- Kotlin学习总结文档
- Kotlin学习总结文档
- Kotlin入门学习总结
- Kotlin使用简单总结
- 使用 Kotlin 读取本地视频并使用Vitamio框架编写万能播放器进行播放(一)
- 使用 Kotlin 读取本地视频并使用Vitamio框架编写万能播放器进行播放(二)
- 【FirstKotlinApp】使用Kotlin封装6.0的权限请求流程
- kotlin学习day9: 为什么要使用kotlin
- android 权限(一)自定义,检查,使用权限
- kotlin学习小点总结1
- 使用kotlin编写Android第一个Activity
- 使用Kotlin语言编写Android程序
- 使用Kotlin编写Android项目示例
- 微信小程序数据处理
- shell编程中的一些坑
- 通过邮箱找回密码
- 分针网——每日分享:你不知道的CSS3圆角
- 继承ViewGroup实现Scroll滑动效果
- 使用Kotlin编写6.0权限检查框架学习总结
- 初学者入门学习java的简介笔记(2)
- Git : Distributed version control system
- javac错误:javac不是内部或外部命令 也不是可运行的程序 解决方法
- 串口1中断服务函数的解析
- C# 之泛型详解
- 人脸识别之light_cnn
- spring session和Redis数据库实现单点登录功能
- Git使用(2)创建版本库