Kotlin反射真的不好用?带你从Andriod初代目架构深层次聊聊到底什么是依赖注入(一)

  • 说起Android架构很多人甚至相当一部分多年经验的安卓开发都很难说的清楚。有趣的是,当你和别人谈论这个话题的什么,总有人说你在装什么逼呢。
    事实上,大部分安卓从业人员的大部分时间都在做些重复的业务逻辑,而对于底层还是停留在ambiguous,遇到的瓶颈很难提升的阶段。那么今天,就和大家聊聊安卓架构的那些事。
    既然是是架构,就不是听上去高大上而已。先从最简单的说起吧,比如依赖注入。废话这么多终于说到正题了。那就容我慢慢道来吧。
    先上代码:
  1. 自定义注解
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) 
  1. 注入目标类
@ContentView(R.layout.activity_main)
class MainActivity : BaseActivity() {}
  1. 声明在对象添加方法
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 反射,我觉得可以合理使用
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值