- 说起Android架构很多人甚至相当一部分多年经验的安卓开发都很难说的清楚。有趣的是,当你和别人谈论这个话题的什么,总有人说你在装什么逼呢。
事实上,大部分安卓从业人员的大部分时间都在做些重复的业务逻辑,而对于底层还是停留在ambiguous,遇到的瓶颈很难提升的阶段。那么今天,就和大家聊聊安卓架构的那些事。
既然是是架构,就不是听上去高大上而已。先从最简单的说起吧,比如依赖注入。废话这么多终于说到正题了。那就容我慢慢道来吧。
先上代码:
- 自定义注解
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
- 注入目标类
@ContentView(R.layout.activity_main)
class MainActivity : BaseActivity() {}
- 声明在对象添加方法
object InjectUtils {
fun inject(context: Any) {
//布局的注入
injectLayout(context)
}
private fun injectLayout(context: Any) {
var layoutId = 0
val javaReflectMillis = measureTimeMillis {
val clazz: Class<*> = context.javaClass
val contentView: ContentView? =
clazz.getAnnotation<ContentView>(ContentView::class.java)
contentView?.let {
layoutId = contentView.value
try {
val method =
context.javaClass.getMethod("setContentView", Int::class.javaPrimitiveType)
method.invoke(context, layoutId)
} catch (e: Exception) {
// e.printStackTrace()
}
}
}
}
}
聪明的你立马会想到ButterKnife对吧。对的,你想的没错,原理是相通的。等等,你这不对啊,原来我一行代码的事情,现在多写了2个类,还用了反射增加了性能开销,这不是傻吗?是的,这样确实挺傻的。
那么到底什么是依赖注入?上面的简单案列是抛砖引玉。事实上,ButterKnife也确实强大对吧。依赖注入简单的说就是实现控制反转–Inversion of> Control(IoC)。你会问是Sping玩的那个IoC吗?是的,就是那个IoC,实现AOP的那个。而很多写安卓多年的开发甚至听都没听过,至于AOP是什么我在这里就不多赘述了,有兴趣的小伙伴查一下相关资料。
IoC简单的说就是吧对象创建的过程交给容器…什么乱七八糟的…写到这突然发现我是要说Kotlin反射来着。
直接上代码
private fun injectLayout0(context: Any) {
var layoutId = 0
val kClass = context::class
val contentView = kClass.annotations.find { it is ContentView } as ContentView?
contentView?.let { //取到注解括号后面的内容it
layoutId = contentView.value
kClass.functions.forEach {
if (it.name == "setContentView") {
try {
it.call(context, layoutId)
return@forEach
} catch (e: Exception) {
// e.printStackTrace()
}
}
}
}
}
Kotlin的反射类型是KClass,当然需要首先在build.gradle中implementation
"org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"添加依赖
那么有些小伙伴就要问了,既然java和Kotlin互通那么Kotlin的反射不是多次一举吗?事情往往不是那么简单。
首先java和Kotlin不是百分百互通的,在相互调用的时候有生产一个平台类型Platform
type。虽然有些libray中的类直接使用的是同名java类,比如HashMap。但是绝大多数的情况下,甚至是基本数据类型,在相互调用的时候有生产一个平台类型Platform
type。Kotlin的KClass和java的Class对象完全不是一回事。
其次,在底层实现上也有很大的去别。有的同学说,你说的啥玩意,Kotlin反射我用过,没那么神神叨叨的。四个字,难用,太慢。
先说结论,同学你真的误解Kotlin的反射了直接放代码
private fun injectLayout(context: Any) {
var layoutId = 0
val javaReflectMillis = measureTimeMillis {
val clazz: Class<*> = context.javaClass
val contentView: ContentView? =
clazz.getAnnotation<ContentView>(ContentView::class.java)
contentView?.let {
layoutId = contentView.value
try {
val method =
context.javaClass.getMethod("setContentView", Int::class.javaPrimitiveType)
method.invoke(context, layoutId)
} catch (e: Exception) {
// e.printStackTrace()
}
}
}
val kotlinReflectMillis = measureTimeMillis {
val kClass = context::class
val contentView = kClass.annotations.find { it is ContentView } as ContentView?
contentView?.let {
layoutId = contentView.value
kClass.functions.forEach {
if (it.name == "setContentView") {
try {
it.call(context, layoutId)
return@forEach
} catch (e: Exception) {
// e.printStackTrace()
}
}
}
}
}
println("JAVA反射用时${javaReflectMillis}")
println("Kotlin反射用时${kotlinReflectMillis}")
}
运行看看
System.out: JAVA反射用时60
System.out: Kotlin反射用时1473
哇塞,翻车了翻车!OMG!~~~!@!#~~@~!#¥~~#~
!~~@~@~@~@~34
啥,别演了?哈哈,看出来了?不演了,不演了。
Kotlin 反射耗时确实比 Java 反射耗时长,毕竟一方面没有 Java 虚拟机加成,Kotlin 的反射主要依赖于 @MetaData
注解,另一方面 Kotlin 反射提供的能力也比 Java 反射多很多(这主要与 Kotlin
本身的语法特性多是相对应的),所以付出多少得到多少,只要它的慢在合理范围内,我们其实也是可以接受的(这里直接搬运了,码字太累了,稍后放出原文链接)
Kotlin在首次编译时确实没有优势,但是KClass反射只会加载一次,类似java类加载的双亲委托机制,加载完成后会放在内存中。下次直接读取缓存。所在在后面操作中简直神速。
看下结果
System.out: JAVA反射用时16
System.out: Kotlin反射用时4
什么是双亲委托机制?双亲委托是…什么乱七八糟的… 讲完了这个,我们加下来继续讲依赖注入
依赖注入技术可以在架构设计上基础之一,几乎运用在所有的主流的第三方框架在,通常结合几种设计模式,完成封装。最典型如retrofit,dagger。你会很容易迷恋上这种优雅的代码风格。
在吗?我要实现安卓23种事件的动态加载 好吧,满足你
/**
* 一次性处理安卓中23种事件
*/
private fun injectClick(context: Any) {
val clazz: Class<*> = context.javaClass
//1.获取类中所有声明方法
clazz.declaredMethods.forEach { method ->
//2.获取方法中声明注解
method.annotations.forEach { it ->
//3.获取自定义元注解
val annotationClass: Class<*> = it.annotationClass.java
val eventBase =
annotationClass.getAnnotation(EventBase::class.java)
eventBase?.let {
//4.获取元注解属性
val listenerSetter: String = eventBase.listenerSetter;
val listenerType: Class<*> = eventBase.listenerType.java
try {
//5.反射得到注解生参数拿到View的id
val valueMethod = annotationClass.getDeclaredMethod("value")
val viewId = valueMethod.invoke(it) as IntArray
for (id in viewId) {
//6.根据id通过反射得到View对象
val findViewById =
clazz.getMethod("findViewById", Int::class.javaPrimitiveType)
val view =
findViewById.invoke(context, id) as View?
view?.let {
//7.这里是java所以使用动态代理设置自定义回调
// 如果是kotlin直接设置block成员属性即可
val proxy = Proxy.newProxyInstance(
listenerType.classLoader,
arrayOf(listenerType)
) { _, _, args ->
method.invoke(context, *(args ?: arrayOfNulls<Any>(0)))
}
val onClickMethod =
view.javaClass.getMethod(listenerSetter, listenerType)
//8.设置View的事件监听
onClickMethod.invoke(view, proxy)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
这里提供了实现部分,如果你对注解的内容不太了解,建议补一补空白。Kotlin提供提供了更加灵活的语法,更加直观有戏的代码编写方式,当然仍然驻守java的同学会说java8也有lambda,Stream为啥要转Kotlin。
其实,差异还是挺大的java8 lambda说白了仅仅是一种简化写法。 Kotlin lambda确是实实在在的对象,对,你没听错。而且不仅lambda。。嗨,这又,扯哪去了。
今天就到这吧,感谢各位小伙伴。这是我逼逼Android架不架构的文章(一),留下的坑,后面慢慢补吧。
最后放下引用部分的连接
参考作者:Kotlin中文社区
标题:重新审视 Kotlin 反射,我觉得可以合理使用