欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

kotlin android extensions 插件實(shí)現(xiàn)示例詳解

 更新時(shí)間:2022年10月18日 09:11:57   作者:程序員江同學(xué)  
這篇文章主要為大家介紹了kotlin android extensions 插件實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

kotlin-android-extensions 插件是 Kotlin 官方提供的一個(gè)編譯器插件,用于替換 findViewById 模板代碼,降低開(kāi)發(fā)成本

雖然 kotlin-android-extensions 現(xiàn)在已經(jīng)過(guò)時(shí)了,但比起其他替換 findViewById 的方案,比如第三方的 ButterKnife 與官方現(xiàn)在推薦的 ViewBinding

kotlin-android-extensions 還是有著一個(gè)明顯的優(yōu)點(diǎn)的:極其簡(jiǎn)潔的 APIKAE 方案比起其他方案寫(xiě)起來(lái)更加簡(jiǎn)便,這是怎么實(shí)現(xiàn)的呢?我們一起來(lái)看下

原理淺析

當(dāng)我們接入KAE后就可以通過(guò)以下方式直接獲取 View

import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewToShowText.text = "Hello"
    }
}

而它的原理也很簡(jiǎn)單,KAE插件將上面這段代碼轉(zhuǎn)換成了如下代碼

public final class MainActivity extends AppCompatActivity {
   private HashMap _$_findViewCache;
   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300023);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.textView);
      var10000.setText((CharSequence)"Hello");
   }
   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();
      }
   }   
}

可以看到,實(shí)際上 KAE 插件會(huì)幫我們生成一個(gè) _$_findCachedViewById()函數(shù),在這個(gè)函數(shù)中首先會(huì)嘗試從一個(gè) HashMap 中獲取傳入的資源 id 參數(shù)所對(duì)應(yīng)的控件實(shí)例緩存,如果還沒(méi)有緩存的話(huà),就調(diào)用findViewById()函數(shù)來(lái)查找控件實(shí)例,并寫(xiě)入 HashMap 緩存當(dāng)中。這樣當(dāng)下次再獲取相同控件實(shí)例的話(huà),就可以直接從 HashMap 緩存中獲取了。

當(dāng)然KAE也幫我們生成了_$_clearFindViewByIdCache()函數(shù),不過(guò)在 Activity 中沒(méi)有調(diào)用,在 Fragment 的 onDestroyView 方法中會(huì)被調(diào)用到

總體結(jié)構(gòu)

在了解了KAE插件的簡(jiǎn)單原理后,我們一步一步來(lái)看一下它是怎么實(shí)現(xiàn)的,首先來(lái)看一下總體結(jié)構(gòu)

KAE插件可以分為 Gradle 插件,編譯器插件,IDE 插件三部分,如下圖所示

我們今天只分析 Gradle 插件與編譯器插件的源碼,它們的具體結(jié)構(gòu)如下:

  • AndroidExtensionsSubpluginIndicatorKAE插件的入口
  • AndroidSubplugin用于配置傳遞給編譯器插件的參數(shù)
  • AndroidCommandLineProcessor用于接收編譯器插件的參數(shù)
  • AndroidComponentRegistrar用于注冊(cè)如圖的各種Extension

源碼分析

插件入口

當(dāng)我們查看 kotlin-gradle-plugin 的源碼,可以看到 kotlin-android-extensions.properties 文件,這就是插件的入口

implementation-class=org.jetbrains.kotlin.gradle.internal.AndroidExtensionsSubpluginIndicator

接下來(lái)我們看一下入口類(lèi)做了什么工作

class AndroidExtensionsSubpluginIndicator @Inject internal constructor(private val registry: ToolingModelBuilderRegistry) :
    Plugin<Project> {
    override fun apply(project: Project) {
        project.extensions.create("androidExtensions", AndroidExtensionsExtension::class.java)
        addAndroidExtensionsRuntime(project)
        project.plugins.apply(AndroidSubplugin::class.java)
    }
    private fun addAndroidExtensionsRuntime(project: Project) {
        project.configurations.all { configuration ->
            val name = configuration.name
            if (name != "implementation") return@all
            configuration.dependencies.add(
                project.dependencies.create(
                    "org.jetbrains.kotlin:kotlin-android-extensions-runtime:$kotlinPluginVersion"
                )
            )
        }
    }
}
open class AndroidExtensionsExtension {
    open var isExperimental: Boolean = false
    open var features: Set<String> = AndroidExtensionsFeature.values().mapTo(mutableSetOf()) { it.featureName }
    open var defaultCacheImplementation: CacheImplementation = CacheImplementation.HASH_MAP
}

AndroidExtensionsSubpluginIndicator中主要做了這么幾件事

  • 創(chuàng)建androidExtensions配置,可以看出其中可以配置是否開(kāi)啟實(shí)驗(yàn)特性,啟用的feature(因?yàn)椴寮邪?code>views與parcelize兩個(gè)功能),viewId緩存的具體實(shí)現(xiàn)(是hashMap還是sparseArray)
  • 自動(dòng)添加kotlin-android-extensions-runtime依賴(lài),這樣就不必在接入了插件之后,再手動(dòng)添加依賴(lài)了,這種寫(xiě)法可以學(xué)習(xí)一下
  • 配置AndroidSubplugin插件,開(kāi)始配置給編譯器插件的傳參

配置編譯器插件傳參

class AndroidSubplugin : KotlinCompilerPluginSupportPlugin {
    // 1. 是否開(kāi)啟編譯器插件
    override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
        if (kotlinCompilation !is KotlinJvmAndroidCompilation)
            return false
        // ...    
        return true
    }
    // 2. 傳遞給編譯器插件的參數(shù)
    override fun applyToCompilation(
        kotlinCompilation: KotlinCompilation<*>
    ): Provider<List<SubpluginOption>> {
        //...
        val pluginOptions = arrayListOf<SubpluginOption>()
        pluginOptions += SubpluginOption("features",
                                         AndroidExtensionsFeature.parseFeatures(androidExtensionsExtension.features).joinToString(",") { it.featureName })
        fun addVariant(sourceSet: AndroidSourceSet) {
            val optionValue = lazy {
                sourceSet.name + ';' + sourceSet.res.srcDirs.joinToString(";") { it.absolutePath }
            }
            pluginOptions += CompositeSubpluginOption(
                "variant", optionValue, listOf(
                    SubpluginOption("sourceSetName", sourceSet.name),
                    //use the INTERNAL option kind since the resources are tracked as sources (see below)
                    FilesSubpluginOption("resDirs", project.files(Callable { sourceSet.res.srcDirs }))
                )
            )
            kotlinCompilation.compileKotlinTaskProvider.configure {
                it.androidLayoutResourceFiles.from(
                    sourceSet.res.sourceDirectoryTrees.layoutDirectories
                )
            }
        }
        addVariant(mainSourceSet)
        androidExtension.productFlavors.configureEach { flavor ->
            androidExtension.sourceSets.findByName(flavor.name)?.let {
                addVariant(it)
            }
        }
        return project.provider { wrapPluginOptions(pluginOptions, "configuration") }
    }
    // 3. 定義編譯器插件的唯一 id,需要與后面編譯器插件中定義的 pluginId 保持一致
    override fun getCompilerPluginId() = "org.jetbrains.kotlin.android"
    // 4. 定義編譯器插件的 `Maven` 坐標(biāo)信息,便于編譯器下載它
    override fun getPluginArtifact(): SubpluginArtifact =
        JetBrainsSubpluginArtifact(artifactId = "kotlin-android-extensions")
}

主要也是重寫(xiě)以上4個(gè)函數(shù),各自的功能在文中都有注釋?zhuān)渲兄饕枰⒁?code>applyToCompilation方法,我們傳遞了features,variant等參數(shù)給編譯器插件

variant的主要作用是為不同 buildType,productFlavor目錄的 layout 文件生成不同的包名

import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.debug.activity_debug.*
import kotlinx.android.synthetic.demo.activity_demo.*

比如如上代碼,activity_debug文件放在debug目錄下,而activiyt_demo文件則放在demo這個(gè)flavor目錄下,這種情況下它們的包名是不同的

編譯器插件接收參數(shù)

class AndroidCommandLineProcessor : CommandLineProcessor {
    override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
    override val pluginOptions: Collection<AbstractCliOption>
            = listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION, CONFIGURATION, FEATURES_OPTION)
    override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
        when (option) {
            VARIANT_OPTION -> configuration.appendList(AndroidConfigurationKeys.VARIANT, value)
            PACKAGE_OPTION -> configuration.put(AndroidConfigurationKeys.PACKAGE, value)
            EXPERIMENTAL_OPTION -> configuration.put(AndroidConfigurationKeys.EXPERIMENTAL, value)
            DEFAULT_CACHE_IMPL_OPTION -> configuration.put(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL, value)           
            else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
        }
    }
}

這段代碼很簡(jiǎn)單,主要是解析variant,包名,是否開(kāi)啟試驗(yàn)特性,緩存實(shí)現(xiàn)方式這幾個(gè)參數(shù)

注冊(cè)各種Extension

接下來(lái)到了編譯器插件的核心部分,通過(guò)注冊(cè)各種Extension的方式修改編譯器的產(chǎn)物

class AndroidComponentRegistrar : ComponentRegistrar {
    companion object {
        fun registerViewExtensions(configuration: CompilerConfiguration, isExperimental: Boolean, project: MockProject) {
            ExpressionCodegenExtension.registerExtension(project,
                    CliAndroidExtensionsExpressionCodegenExtension(isExperimental, globalCacheImpl))
            IrGenerationExtension.registerExtension(project,
                    CliAndroidIrExtension(isExperimental, globalCacheImpl))
            StorageComponentContainerContributor.registerExtension(project,
                    AndroidExtensionPropertiesComponentContainerContributor())
            ClassBuilderInterceptorExtension.registerExtension(project,
                    CliAndroidOnDestroyClassBuilderInterceptorExtension(globalCacheImpl))
            PackageFragmentProviderExtension.registerExtension(project,
                    CliAndroidPackageFragmentProviderExtension(isExperimental))
        }
    }
    override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
        if (AndroidExtensionsFeature.VIEWS in features) {
            registerViewExtensions(configuration, isExperimental, project)
        }
    }
}

可以看出,主要就是在開(kāi)啟了AndroidExtensionsFeature.VIEWS特性時(shí),注冊(cè)了5個(gè)Extension,接下來(lái)我們來(lái)看下這5個(gè)Extension都做了什么

IrGenerationExtension

IrGenerationExtensionKAE插件的核心部分,在生成 IR 時(shí)回調(diào),我們可以在這個(gè)時(shí)候修改與添加 IR,KAE插件生成的_findCachedViewById方法都是在這個(gè)時(shí)候生成的,具體實(shí)現(xiàn)如下:

private class AndroidIrTransformer(val extension: AndroidIrExtension, val pluginContext: IrPluginContext) :
    IrElementTransformerVoidWithContext() {
    override fun visitClassNew(declaration: IrClass): IrStatement {
        if ((containerOptions.cache ?: extension.getGlobalCacheImpl(declaration)).hasCache) {
            val cacheField = declaration.getCacheField() 
            declaration.declarations += cacheField // 添加_$_findViewCache屬性
            declaration.declarations += declaration.getClearCacheFun() // 添加_$_clearFindViewByIdCache方法
            declaration.declarations += declaration.getCachedFindViewByIdFun() // 添加_$_findCachedViewById方法
        }
        return super.visitClassNew(declaration)
    }
    override fun visitCall(expression: IrCall): IrExpression {
        val result = if (expression.type.classifierOrNull?.isFragment == true) {
            // this.get[Support]FragmentManager().findFragmentById(R$id.<name>)
            createMethod(fragmentManager.child("findFragmentById"), createClass(fragment).defaultType.makeNullable()) {
                addValueParameter("id", pluginContext.irBuiltIns.intType)
            }.callWithRanges(expression).apply {
                // ...
            }
        } else if (containerHasCache) {
            // this._$_findCachedViewById(R$id.<name>)
            receiverClass.owner.getCachedFindViewByIdFun().callWithRanges(expression).apply {
                dispatchReceiver = receiver
                putValueArgument(0, resourceId)
            }
        } else {
        	// this.findViewById(R$id.<name>)
            irBuilder(currentScope!!.scope.scopeOwnerSymbol, expression).irFindViewById(receiver, resourceId, containerType)
        }
        return with(expression) { IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.CAST, type, result) }
    }
}

如上所示,主要做了兩件事:

  • visitClassNew方法中給對(duì)應(yīng)的類(lèi)(比如 Activity 或者 Fragment )添加了_$_findViewCache屬性,以及_$_clearFindViewByIdCache_$_findCachedViewById方法
  • visitCall方法中,將viewId替換為相應(yīng)的表達(dá)式,比如this._$_findCachedViewById(R$id.<name>)或者this.findViewById(R$id.<name>)

可以看出,其實(shí)KAE插件的大部分功能都是通過(guò)IrGenerationExtension實(shí)現(xiàn)的

ExpressionCodegenExtension

ExpressionCodegenExtension的作用其實(shí)與IrGenerationExtension基本一致,都是用來(lái)生成_$_clearFindViewByIdCache等代碼的

主要區(qū)別在于,IrGenerationExtension在使用IR后端時(shí)回調(diào),生成的是IR。

ExpressionCodegenExtension在使用 JVM 非IR后端時(shí)回調(diào),生成的是字節(jié)碼

在 Kotlin 1.5 之后,JVM 后端已經(jīng)默認(rèn)開(kāi)啟 IR,可以認(rèn)為這兩個(gè) Extension 就是新老版本的兩種實(shí)現(xiàn)

StorageComponentContainerContributor

StorageComponentContainerContributor的主要作用是檢查調(diào)用是否正確

class AndroidExtensionPropertiesCallChecker : CallChecker {
    override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
        // ...
        with(context.trace) {
            checkUnresolvedWidgetType(reportOn, androidSyntheticProperty)
            checkDeprecated(reportOn, containingPackage)
            checkPartiallyDefinedResource(resolvedCall, androidSyntheticProperty, context)
        }
    }
}    

如上,主要做了是否有無(wú)法解析的返回類(lèi)型等檢查

ClassBuilderInterceptorExtension

ClassBuilderInterceptorExtension的主要作用是在onDestroyView方法中調(diào)用_$_clearFindViewByIdCache方法,清除KAE緩存

private class AndroidOnDestroyCollectorClassBuilder(
    private val delegate: ClassBuilder,
    private val hasCache: Boolean
) : DelegatingClassBuilder() {      
    override fun newMethod(
        origin: JvmDeclarationOrigin,
        access: Int,
        name: String,
        desc: String,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val mv = super.newMethod(origin, access, name, desc, signature, exceptions)
        if (!hasCache || name != ON_DESTROY_METHOD_NAME || desc != "()V") return mv
        hasOnDestroy = true
        return object : MethodVisitor(Opcodes.API_VERSION, mv) {
            override fun visitInsn(opcode: Int) {
                if (opcode == Opcodes.RETURN) {
                    visitVarInsn(Opcodes.ALOAD, 0)
                    visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
                }
                super.visitInsn(opcode)
            }
        }
    }
}

可以看出,只有在 Fragment 的onDestroyView方法中添加了 clear 方法,這是因?yàn)?Fragment 的生命周期與其根 View 生命周期可能并不一致,而 Activity 的 onDestroy 中是沒(méi)有也沒(méi)必要添加的

PackageFragmentProviderExtension

PackageFragmentProviderExtension的主要作用是注冊(cè)各種包名,以及該包名下的各種提示

import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.debug.activity_debug.*
import kotlinx.android.synthetic.demo.activity_demo.*

比如我們?cè)?IDE 中引入上面的代碼,就可以引入 xml 文件中定義的各個(gè) id 了,這就是通過(guò)這個(gè)Extension實(shí)現(xiàn)的

總結(jié)

本文主要從原理淺析,總體架構(gòu),源碼分析等角度分析了 kotlin-android-extensions 插件到底是怎么實(shí)現(xiàn)的

相比其它方案,KAE使用起來(lái)可以說(shuō)是非常簡(jiǎn)潔優(yōu)雅了,可以看出 Kotlin 編譯器插件真的可以打造出極簡(jiǎn)的 API,因此雖然KAE已經(jīng)過(guò)時(shí)了,但還是有必要學(xué)習(xí)一下的

以上就是kotlin android extensions 插件實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于kotlin android extensions 插件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論