data class User(

var login: String,

var location: String,

var bio: String)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.CLASS)

annotation class Api(val url: String)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.CLASS)

annotation class Path(val url: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.FUNCTION)

annotation class Get(val url: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class PathVariable(val name: String = “”)

@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class Query(val name: String = “”)

@Api(“https://api.github.com”)

interface GitHubApi {

@Api(“users”)

interface Users {

@Get(“{name}”)

fun get(name: String): User

@Get(“{name}/followers”)

fun followers(name: String): List

}

@Api(“repos”)

interface Repos {

@Get(“{owner}/{repo}/forks”)

fun forks(owner: String, repo: String)

}

}

object RetroApi {

const val PATH_PATTERN = “”“({(\w+)})”“”

val okHttp = OkHttpClient()

val gson = Gson()

val enclosing = {

cls: Class<*> ->

var currentCls: Class<*>? = cls

sequence {

while(currentCls != null){

// enclosingClass获取下一个class

// yield将对象添加到正在构建的sequence序列当中

currentCls = currentCls?.also { yield(it) }?.enclosingClass

}

}

}

//内联特化

inline fun create(): T {

val functionMap = T::class.functions.map{ it.name to it }.toMap() //【函数名,函数本身】的Pair转成map

val interfaces = enclosing(T::class.java).takeWhile { it.isInterface }.toList() //拿到所有接口列表

println("interfaces=

i

n

t

e

r

f

a

c

e

s

"

)

/

/

输出

[

G

i

t

H

u

b

A

p

i

interfaces")// 输出 [GitHubApi

interfaces")//输出[GitHubApiUsers, GitHubApi]

//foldRight从interfaces序列的右边开始拼

val apiPath = interfaces.foldRight(StringBuilder()) {

clazz, acc ->

// 拿到每个接口类的Api注解的url参数值,如果url参数为空,则使用类名作为url值

acc.append(clazz.getAnnotation(Api::class.java)?.url?.takeIf { it.isNotEmpty() } ?: clazz.name)

.append(“/”)

}.toString()

println(“apiPath= $apiPath”) // https://api.github.com/users/

//动态代理

return Proxy.newProxyInstance(RetroApi.javaClass.classLoader, arrayOf(T::class.java)) {

proxy, method, args ->

//所有函数中的抽象函数 即接口的方法

functionMap[method.name]?.takeIf { it.isAbstract }?.let {

function ->

//方法的参数

val parameterMap = function.valueParameters.map {

//参数名和参数的值放在一起

it.name to args[it.index - 1] //valueParameters包含receiver 因此需要index-1来对应args

}.toMap()

println(“parameterMap= $parameterMap”) //{name=bennyhuo}

//{name} 拿到Get注解的参数 如果注解参数不为空就使用注解参数,如果为空使用方法名称

val endPoint = function.findAnnotation()!!.url.takeIf { it.isNotEmpty() } ?: function.name

println(“endPoint= $endPoint”) //{name}/followers

//正则找到endPoint中的所有符合"{owner}/{repo}/forks"其中{xxx}的结果

val compiledEndPoint = Regex(PATH_PATTERN).findAll(endPoint).map {

matchResult ->

println(“matchResult.groups= ${matchResult.groups}”) // [MatchGroup(value={name}, range=0…5), MatchGroup(value={name}, range=0…5), MatchGroup(value=name, range=1…4)]

println(“matchResult.groups1.range= ${matchResult.groups[1]?.range}”) // 0…5

println(“matchResult.groups2.value= ${matchResult.groups[2]?.value}”) // name

matchResult.groups[1]!!.range to parameterMap[matchResult.groups[2]!!.value]

}.fold(endPoint) {

acc, pair ->

//acc的初始值就是endPoint即{name}/followers

println(“acc= ${acc}”) // {name}/followers

println(“pair= ${pair}”) // (0…5, bennyhuo) pair是一个 range to name

acc.replaceRange(pair.first, pair.second.toString()) // 把{name}/followers中的0到5的位置的字符串{name}替换成bennyhuo

}

println(“compiledEndPoint= ${compiledEndPoint}”) //bennyhuo/followers

//拼接api和参数

val url = apiPath + compiledEndPoint

println(“url ==== $url”)

println(“*****************”)

okHttp.newCall(Request.Builder().url(url).get().build()).execute().body()?.charStream()?.use {

gson.fromJson(JsonReader(it), method.genericReturnType)//返回json的解析结果

}

}

} as T

}

}

fun main() {

//interface com.bennyhuo.kotlin.annotations.eg.GitHubApi

//println(“enclosingClass=${GitHubApi.Users::class.java.enclosingClass}”)

val usersApi = RetroApi.create()

val user = usersApi.get(“bennyhuo”)

val followers = usersApi.followers(“bennyhuo”).map { it.login }

println(“user ====== $user”)

println(“followers ======== $followers”)

}

这个例子还是有点复杂,不太好理解,有些方法没接触过不知道啥意思,这里加了很多打印方法,把结果打印输出一下,这样能知道具体是代表的啥,就好理解一点了。

实例:注解加持反射版 Model 映射

这个例子是在前面反射一节实现的model映射例子的基础上,通过添加注解方式处理那些字段名称不是相同风格的情况,比如两个对象中的avatar_url 和 avatarUrl的相互映射。

//不写默认是RUNTIME

//@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class FieldName(val name: String)

@Target(AnnotationTarget.CLASS)

annotation class MappingStrategy(val klass: KClass)

interface NameStrategy {

fun mapTo(name: String): String

}

//下划线转驼峰

object UnderScoreToCamel : NameStrategy {

// html_url -> htmlUrl

override fun mapTo(name: String): String {

//先转成字符数组,然后fold操作

return name.toCharArray().fold(StringBuilder()) { acc, c ->

when (acc.lastOrNull()) { //上一次的acc不是空

‘_’ -> acc[acc.lastIndex] = c.toUpperCase() //上一次结果的最后一个字符是下划线就把下划线位置替换成当前字符的大写字母

else -> acc.append© // 否则直接拼接

}

//返回acc

acc

}.toString()

}

}

//驼峰转下划线

object CamelToUnderScore : NameStrategy {

override fun mapTo(name: String): String {

//先转成字符数组,然后fold操作

return name.toCharArray().fold(StringBuilder()) { acc, c ->

when {

c.isUpperCase() -> acc.append(‘_’).append(c.toLowerCase()) //如果是大写字母直接拼一个下划线再拼上小写

else -> acc.append©

}

//返回acc

acc

}.toString()

}

}

//使用定义的策略注解,驼峰转下划线

@MappingStrategy(CamelToUnderScore::class)

data class UserVO(

val login: String,

//@FieldName(“avatar_url”) //这种是单个字段上面添加注解,只能一个一个添加

val avatarUrl: String,

var htmlUrl: String

)

data class UserDTO(

var id: Int,

var login: String,

var avatar_url: String,

var url: String,

var html_url: String

)

fun main() {

val userDTO = UserDTO(

0,

“Bennyhuo”,

“https://avatars2.githubusercontent.com/u/30511713?v=4”,

“https://api.github.com/users/bennyhuo”,

“https://github.com/bennyhuo”

)

val userVO: UserVO = userDTO.mapAs()

println(userVO)

val userMap = mapOf(

“id” to 0,

“login” to “Bennyhuo”,

“avatar_url” to “https://api.github.com/users/bennyhuo”,

“html_url” to “https://github.com/bennyhuo”,

“url” to “https://api.github.com/users/bennyhuo”

)

val userVOFromMap: UserVO = userMap.mapAs()

println(userVOFromMap)

}

inline fun From.mapAs(): To {

return From::class.memberProperties.map { it.name to it.get(this) }

.toMap().mapAs()

}

inline fun Map.mapAs(): To {

return To::class.primaryConstructor!!.let {

it.parameters.map { parameter ->

parameter to (this[parameter.name]

// let(this::get)等价于let{this[it]} userDTO[“avatar_url”]

?: (parameter.annotations.filterIsInstance().firstOrNull()?.name?.let(this::get))

// 拿到UserVO类的注解MappingStrategy的kclass即CamelToUnderScore,它是一个object calss, objectInstance获取实例,然后调用mapTo把avatarUrl转成avatar_url,最后调用userDTO[“avatar_url”]

?: To::class.findAnnotation()?.klass?.objectInstance?.mapTo(parameter.name!!)?.let(this::get)

?: if (parameter.type.isMarkedNullable) null

else throw IllegalArgumentException(“${parameter.name} is required but missing.”))

}.toMap().let(it::callBy)

}

}

这里如果注解上不写@Retention(AnnotationRetention.RUNTIME)默认就是运行时类型。

下面两种写法是等价的:

parameter.annotations.filterIsInstance()

parameter.findAnnotation()

下面两种写法是等价的:

let(this::get)

let{

this[it]

}

mapAs()方法中做了几件事:

尝试直接从当前Map中获取To对象的同名参数值, 尝试从To对象的字段上面的注解来获取需要转换的参数名,再根据名字获取Map中的值 尝试获取To对象的类注解得到处理类,调用处理类方法驼峰转下划线,再根据名字获取Map中的值 以上大招都没有获取到,如果To对象的字段可接受空值,就赋值null, 否则就抛异常

驼峰和下划线转换那里稍微有点绕。。

实例:注解处理器版 Model 映射

这个例子会用到一些著名的代码生成库:

生成Java代码:JavaPoet 生成Kotlin代码:KotlinPoet

上面两个都是square公司出品的开源库,JakeWharton大神的杰作,这个例子中主要用到了KotlinPoet,还有一个这个学习课程资料主讲大神自己写的一个库。

dependencies {

implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk8”

implementation “com.squareup:kotlinpoet:1.4.3”

implementation “com.bennyhuo.aptutils:aptutils:1.7.1”

implementation project(“:apt:annotations”)

}

注解声明:

@Retention(AnnotationRetention.BINARY)

@Target(AnnotationTarget.CLASS)

annotation class ModelMap

这里不需要在运行时保留注解,编译就会生成代码了,因此使用的是AnnotationRetention.BINARY

注解生成代码:

package com.bennyhuo.kotlin.annotations.apt.compiler

import com.bennyhuo.aptutils.AptContext

import com.bennyhuo.aptutils.logger.Logger

import com.bennyhuo.aptutils.types.ClassType

import com.bennyhuo.aptutils.types.asKotlinTypeName

import com.bennyhuo.aptutils.types.packageName

import com.bennyhuo.aptutils.types.simpleName

import com.bennyhuo.aptutils.utils.writeToFile

import com.bennyhuo.kotlin.annotations.apt.ModelMap

import com.squareup.kotlinpoet.*

import javax.annotation.processing.*

import javax.lang.model.SourceVersion

import javax.lang.model.element.ExecutableElement

import javax.lang.model.element.TypeElement

import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

//必须指定注解的类型

@SupportedAnnotationTypes(“com.bennyhuo.kotlin.annotations.apt.ModelMap”)

@SupportedSourceVersion(SourceVersion.RELEASE_8)

class ModelMapProcessor: AbstractProcessor() {

override fun init(processingEnv: ProcessingEnvironment) {

super.init(processingEnv)

AptContext.init(processingEnv)

}

//fun Sample.toMap() = mapOf(“a” to a, “b” to b)

//fun Map.toSample() = Sample(this[“a”] as Int, this[“b”] as String)

override fun process(annotations: MutableSet, roundEnv: RoundEnvironment): Boolean {

roundEnv.getElementsAnnotatedWith(ModelMap::class.java)

.forEach {

element ->

element.enclosedElements.filterIsInstance()

.firstOrNull { it.simpleName() == “” }

?.let {

val typeElement = element as TypeElement

FileSpec.builder(typeElement.packageName(), “${typeElement.simpleName()}$$ModelMap”) //$$转义

.addFunction(

FunSpec.builder(“toMap”)

.receiver(typeElement.asType().asKotlinTypeName())

.addStatement(“return mapOf(KaTeX parse error: Expected '}', got 'EOF' at end of input: …nToString {""""{it.simpleName()}” to ${it.simpleName()}“”" }})")//mapOf(“a” to a, “b” to b)

.build()

)

.addFunction(

FunSpec.builder(“to${typeElement.simpleName()}”)

.addTypeVariable(TypeVariableName(“V”))

.receiver(MAP.parameterizedBy(STRING, TypeVariableName(“V”)))

.addStatement(

“return

t

y

p

e

E

l

e

m

e

n

t

.

s

i

m

p

l

e

N

a

m

e

(

)

(

{typeElement.simpleName()}(

typeElement.simpleName()({it.parameters.joinToString{ “”“this[”${it.simpleName()}”] as %T “”" } })", //Sample(this[“a”] as Int, this[“b”] as String) %T是模板字符串 用后面的参数替换

*it.parameters.map { it.asType().asKotlinTypeName() }.toTypedArray()

)

.build() 自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?

面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。

学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。

我们搜集整理过这几年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

我们在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

5%以上Android开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?

面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。

学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。

我们搜集整理过这几年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

[外链图片转存中…(img-EAXRVtW6-1711865269864)]

我们在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

相关链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: