内容简介:Kotlin 从首次推出到现在,可谓发展的十分迅速,独特的空安全特性吸引了很多 Android 开发者去使用,Google 也正式将 Kotlin 这门语言作为 Android 开发的首选语言。Kotlin 官方也为各位开发者提供了一系列的插件,开发文档以及 IDE 支持,本文介绍的 Kotlin-Android-Extensions 就是一款 Kotlin 的安卓开发扩展插件。直接在 build.gradle 中引入该插件:模拟的业务场景如下:
Kotlin 从首次推出到现在,可谓发展的十分迅速,独特的空安全特性吸引了很多 Android 开发者去使用,Google 也正式将 Kotlin 这门语言作为 Android 开发的首选语言。Kotlin 官方也为各位开发者提供了一系列的插件,开发文档以及 IDE 支持,本文介绍的 Kotlin-Android-Extensions 就是一款 Kotlin 的安卓开发扩展插件。
二、Kotlin-Android-Extensions 使用
引入
直接在 build.gradle 中引入该插件:
apply plugin: 'kotlin-android-extensions'
使用
模拟的业务场景如下:
- 在 activity_main.xml 中创建一个 id 为 button_test 的 button
- 在 MainActivity.kt 中为这个 button 设置点击事件
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* /** * Created by Xu on 2020/02/05. * * @author Xu */ class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button_test.setOnClickListener { // todo } } }
这里可以观察到,并没有熟悉的 findViewById() 方法,而是直接使用了 button_test 这个对象,该对象其实是由插件根据布局 xml 中所设置的控件 id 而自动生成的。
三、Kotlin-Android-Extensions 源码分析
为什么不需要使用到 findViewById() 方法呢?之前我在分析 ButterKnife 源码的时候也问过类似的问题( 传送门 ),最后其实是通过 APT(编译时注解)的方式自动生成了 findViewById 方法,猜测这里也是通过类似的自动生成代码方式帮我们补充了。
我们首先试着去反编译 Kotlin ByteCode,具体是通过打开 Android Studio -> Tools -> Kotlin -> Show Kotlin Bytecode,然后选择 build 文件夹下的 MainActivity.class,点击 Decompile 即可。反编译完代码如下:
public final class MainActivity extends AppCompatActivity { private HashMap _$_findViewCache; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(layout.activity_main); ((Button)this._$_findCachedViewById(id.button_test)).setOnClickListener((OnClickListener)null.INSTANCE); } public View _$_findCachedViewById(int var1) { if (this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(var1); if (var2 == null) { var2 = this.findViewById(var1); this._$_findViewCache.put(var1, var2); } return var2; } public void _$_clearFindViewByIdCache() { if (this._$_findViewCache != null) { this._$_findViewCache.clear(); } } }
这里会发现多了一个 _\$_findViewCache 的成员变量以及 _\$_findCachedViewById 的方法,而这个方法内部其实也是使用到了 findViewById,并且对 view 进行了缓存,避免了该方法的重复调用。
那么这些代码是怎么生成的呢?通过谷歌的搜索,笔者找到了该插件的源代码地址( 传送门 ),然后观察到这个变量和方法命名是固定的,跟具体的类命名无关,猜测是一个固定的常量值,在代码中进行全局搜索,找到以下这个相关类:
AbstractAndroidExtensionsExpressionCodegenExtension.kt:
abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionCodegenExtension { companion object { val PROPERTY_NAME = "_\$_findViewCache" val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById" val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" val ON_DESTROY_METHOD_NAME = "onDestroyView" fun shouldCacheResource(resource: PropertyDescriptor) = (resource as? AndroidSyntheticProperty)?.shouldBeCached == true } // 省略部分代码 }
再进一步的去搜索,找到类中对应的 generateCacheField() 和 generateCachedFindViewByIdFunction() 方法。
先看 generateCacheField():
private fun SyntheticPartsGenerateContext.generateCacheField() { val cacheImpl = CacheMechanism.getType(containerOptions.getCacheOrDefault(classOrObject)) classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, cacheImpl.descriptor, null, null) }
这里用到 CacheMechanism 的 getType 方法,然后通过 classBuilder#newField() 生成。
CacheMechanism#getType():
fun getType(cacheImpl: CacheImplementation): Type { return Type.getObjectType(when (cacheImpl) { CacheImplementation.SPARSE_ARRAY -> "android.util.SparseArray" CacheImplementation.HASH_MAP -> HashMap::class.java.canonicalName CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache") }.replace('.', '/')) }
这里返回的是 _\$_findViewCache 这个成员变量的类型,默认是 HashMap,也可以在 build.gradle 中指定类型:
androidExtensions { defaultCacheImplementation = "HASH_MAP" // or SPARSE_ARRAY、NONE }
再看 generateCachedFindViewByIdFunction():
private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() { val containerAsmType = state.typeMapper.mapClass(container) val viewType = Type.getObjectType("android/view/View") val methodVisitor = classBuilder.newMethod( JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", null, null) methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) val cacheImpl = CacheMechanism.get(containerOptions.getCacheOrDefault(classOrObject), iv, containerAsmType) fun loadId() = iv.load(1, Type.INT_TYPE) // Get cache property cacheImpl.loadCache() val lCacheNonNull = Label() iv.ifnonnull(lCacheNonNull) // Init cache if null cacheImpl.initCache() // Get View from cache iv.visitLabel(lCacheNonNull) cacheImpl.loadCache() loadId() cacheImpl.getViewFromCache() iv.checkcast(viewType) iv.store(2, viewType) val lViewNonNull = Label() iv.load(2, viewType) iv.ifnonnull(lViewNonNull) // Resolve View via findViewById if not in cache iv.load(0, containerAsmType) val containerType = containerOptions.containerType when (containerType) { AndroidContainerType.ACTIVITY, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG -> { loadId() iv.invokevirtual(containerType.internalClassName, "findViewById", "(I)Landroid/view/View;", false) } AndroidContainerType.FRAGMENT, AndroidContainerType.ANDROIDX_SUPPORT_FRAGMENT, AndroidContainerType.SUPPORT_FRAGMENT, LAYOUT_CONTAINER -> { if (containerType == LAYOUT_CONTAINER) { iv.invokeinterface(containerType.internalClassName, "getContainerView", "()Landroid/view/View;") } else { iv.invokevirtual(containerType.internalClassName, "getView", "()Landroid/view/View;", false) } iv.dup() val lgetViewNotNull = Label() iv.ifnonnull(lgetViewNotNull) // Return if getView() is null iv.pop() iv.aconst(null) iv.areturn(viewType) // Else return getView().findViewById(id) iv.visitLabel(lgetViewNotNull) loadId() iv.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false) } else -> throw IllegalStateException("Can't generate code for $containerType") } iv.store(2, viewType) // Store resolved View in cache cacheImpl.loadCache() loadId() cacheImpl.putViewToCache { iv.load(2, viewType) } iv.visitLabel(lViewNonNull) iv.load(2, viewType) iv.areturn(viewType) FunctionCodegen.endVisit(methodVisitor, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, classOrObject) }
这里的代码比较复杂,但可以观察到一个重要的地方,它会去判断当前的类是 Activity 还是 Fragment,再去执行对应的寻找控件方法。例如是 Activity 的话,则执行的是 findViewById 方法;而如果是 Fragment,则先执行 getView 方法获取到对应的 rootView,再执行 findViewById。
还有一个点,最后的实现都会调用到 iv#invokevirtual() 方法,iv 是 InstructionAdapter 类的一个实例。InstructionAdapter 继承于 MethodVisiter,用途是生成方法实现的字节码,这里不再深究实现细节,有兴趣的读者可以再去了解一下。
四、Kotlin-Android-Extensions 总结
Kotlin-Android-Extensions 这个插件,通过 自动生成寻找控件代码的字节码,对查找完的控件进行缓存以及 IDE 跳转支持 等方式,使得 Android 的业务开发更加地便捷高效,有效提高研发效率,提升研发体验。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
大学程序设计课程与竞赛训练教材
吴永辉、王建德 / 机械工业出版社 / 2013-6 / 69.00
本书每章为一个主题,实验内容安排紧扣大学算法和数学的教学,用程序设计竞赛中的算法和数学试题作为实验试题,将算法和数学的教学与程序设计竞赛的解题训练结合在一起;在思维方式和解题策略的训练方面,以问题驱动和启发式引导为主要方式,培养读者通过编程解决问题的能力。 本书特点: 书中给出的234道试题全部精选自ACM国际大学生程序设计竞赛的世界总决赛以及各大洲赛区现场赛和网络预赛、大学程序设计竞......一起来看看 《大学程序设计课程与竞赛训练教材》 这本书的介绍吧!
JSON 在线解析
在线 JSON 格式化工具
Base64 编码/解码
Base64 编码/解码