Android Extensions: Add SparseArray implementation for the View cache (KT-18250)
This commit is contained in:
+25
-38
@@ -44,7 +44,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
|
||||
class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
companion object {
|
||||
private val PROPERTY_NAME = "_\$_findViewCache"
|
||||
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"
|
||||
@@ -57,7 +57,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
private class SyntheticPartsGenerateContext(
|
||||
val classBuilder: ClassBuilder,
|
||||
val state: GenerationState,
|
||||
val descriptor: ClassDescriptor,
|
||||
val container: ClassDescriptor,
|
||||
val classOrObject: KtClassOrObject,
|
||||
val containerOptions: ContainerOptionsProxy)
|
||||
|
||||
@@ -135,6 +135,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
val context = SyntheticPartsGenerateContext(classBuilder, codegen.state, container, targetClass, containerOptions)
|
||||
context.generateCachedFindViewByIdFunction()
|
||||
context.generateClearCacheFunction()
|
||||
context.generateCacheField()
|
||||
|
||||
if (containerOptions.classType.isFragment) {
|
||||
val classMembers = container.unsubstitutedMemberScope.getContributedDescriptors()
|
||||
@@ -143,8 +144,6 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
context.generateOnDestroyFunctionForFragment()
|
||||
}
|
||||
}
|
||||
|
||||
classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null)
|
||||
}
|
||||
|
||||
private fun FunctionDescriptor.isOnDestroyFunction(): Boolean {
|
||||
@@ -162,10 +161,10 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
methodVisitor.visitCode()
|
||||
val iv = InstructionAdapter(methodVisitor)
|
||||
|
||||
val classType = state.typeMapper.mapClass(descriptor)
|
||||
val classType = state.typeMapper.mapClass(container)
|
||||
|
||||
iv.load(0, classType)
|
||||
iv.invokespecial(state.typeMapper.mapClass(descriptor.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false)
|
||||
iv.invokespecial(state.typeMapper.mapClass(container.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false)
|
||||
iv.areturn(Type.VOID_TYPE)
|
||||
|
||||
FunctionCodegen.endVisit(methodVisitor, ON_DESTROY_METHOD_NAME, classOrObject)
|
||||
@@ -176,29 +175,28 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
methodVisitor.visitCode()
|
||||
val iv = InstructionAdapter(methodVisitor)
|
||||
|
||||
val classType = state.typeMapper.mapClass(descriptor)
|
||||
val className = classType.internalName
|
||||
val containerType = state.typeMapper.mapClass(container)
|
||||
val cacheImpl = CacheMechanism.get(containerOptions, iv, containerType)
|
||||
|
||||
fun loadCache() {
|
||||
iv.load(0, classType)
|
||||
iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
|
||||
}
|
||||
|
||||
loadCache()
|
||||
cacheImpl.loadCache()
|
||||
val lCacheIsNull = Label()
|
||||
iv.ifnull(lCacheIsNull)
|
||||
|
||||
loadCache()
|
||||
iv.invokevirtual("java/util/HashMap", "clear", "()V", false)
|
||||
cacheImpl.loadCache()
|
||||
cacheImpl.clearCache()
|
||||
|
||||
iv.visitLabel(lCacheIsNull)
|
||||
iv.areturn(Type.VOID_TYPE)
|
||||
FunctionCodegen.endVisit(methodVisitor, CLEAR_CACHE_METHOD_NAME, classOrObject)
|
||||
}
|
||||
|
||||
private fun SyntheticPartsGenerateContext.generateCacheField() {
|
||||
val cacheImpl = CacheMechanism.getType(containerOptions)
|
||||
classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, cacheImpl.descriptor, null, null)
|
||||
}
|
||||
|
||||
private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() {
|
||||
val classType = state.typeMapper.mapClass(descriptor)
|
||||
val className = classType.internalName
|
||||
val containerType = state.typeMapper.mapClass(container)
|
||||
|
||||
val viewType = Type.getObjectType("android/view/View")
|
||||
|
||||
@@ -207,32 +205,24 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
methodVisitor.visitCode()
|
||||
val iv = InstructionAdapter(methodVisitor)
|
||||
|
||||
fun loadCache() {
|
||||
iv.load(0, classType)
|
||||
iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
|
||||
}
|
||||
val cacheImpl = CacheMechanism.get(containerOptions, iv, containerType)
|
||||
|
||||
fun loadId() = iv.load(1, Type.INT_TYPE)
|
||||
|
||||
// Get cache property
|
||||
loadCache()
|
||||
cacheImpl.loadCache()
|
||||
|
||||
val lCacheNonNull = Label()
|
||||
iv.ifnonnull(lCacheNonNull)
|
||||
|
||||
// Init cache if null
|
||||
iv.load(0, classType)
|
||||
iv.anew(Type.getType("Ljava/util/HashMap;"))
|
||||
iv.dup()
|
||||
iv.invokespecial("java/util/HashMap", "<init>", "()V", false)
|
||||
iv.putfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
|
||||
cacheImpl.initCache()
|
||||
|
||||
// Get View from cache
|
||||
iv.visitLabel(lCacheNonNull)
|
||||
loadCache()
|
||||
cacheImpl.loadCache()
|
||||
loadId()
|
||||
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
|
||||
iv.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false)
|
||||
cacheImpl.getViewFromCache()
|
||||
iv.checkcast(viewType)
|
||||
iv.store(2, viewType)
|
||||
|
||||
@@ -241,7 +231,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
iv.ifnonnull(lViewNonNull)
|
||||
|
||||
// Resolve View via findViewById if not in cache
|
||||
iv.load(0, classType)
|
||||
iv.load(0, containerType)
|
||||
when (containerOptions.classType) {
|
||||
AndroidContainerType.ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG -> {
|
||||
loadId()
|
||||
@@ -252,7 +242,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
val targetClassName: String
|
||||
if (containerOptions.classType == AndroidContainerType.LAYOUT_CONTAINER) {
|
||||
methodName = "getEntityView"
|
||||
targetClassName = classType.internalName
|
||||
targetClassName = containerType.internalName
|
||||
} else {
|
||||
methodName = "getView"
|
||||
targetClassName = containerOptions.classType.internalClassName
|
||||
@@ -277,12 +267,9 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
iv.store(2, viewType)
|
||||
|
||||
// Store resolved View in cache
|
||||
loadCache()
|
||||
cacheImpl.loadCache()
|
||||
loadId()
|
||||
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
|
||||
iv.load(2, viewType)
|
||||
iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)
|
||||
iv.pop()
|
||||
cacheImpl.putViewToCache { iv.load(2, viewType) }
|
||||
|
||||
iv.visitLabel(lViewNonNull)
|
||||
iv.load(2, viewType)
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.codegen
|
||||
|
||||
import kotlinx.android.extensions.CacheImplementation
|
||||
import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
|
||||
interface CacheMechanism {
|
||||
/** Push the cache object onto the stack. */
|
||||
fun loadCache()
|
||||
|
||||
/** Init cache field. */
|
||||
fun initCache()
|
||||
|
||||
/** Clear cache. The cache storage should be on the stack. */
|
||||
fun clearCache()
|
||||
|
||||
/** Push the cached view onto the stack, or push `null` if the view is not cached. `Int` id should be on the stack. */
|
||||
fun getViewFromCache()
|
||||
|
||||
/** Cache the view. `Int` id should be on the stack. */
|
||||
fun putViewToCache(getView: () -> Unit)
|
||||
|
||||
companion object {
|
||||
fun getType(containerOptions: ContainerOptionsProxy): Type {
|
||||
return Type.getObjectType(when (containerOptions.cache) {
|
||||
CacheImplementation.SPARSE_ARRAY -> "android.util.SparseArray"
|
||||
CacheImplementation.HASH_MAP -> HashMap::class.java.canonicalName
|
||||
CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache")
|
||||
}.replace('.', '/'))
|
||||
}
|
||||
|
||||
fun get(containerOptions: ContainerOptionsProxy, iv: InstructionAdapter, containerType: Type): CacheMechanism {
|
||||
return when (containerOptions.cache) {
|
||||
CacheImplementation.HASH_MAP -> HashMapCacheMechanism(iv, containerType)
|
||||
CacheImplementation.SPARSE_ARRAY -> SparseArrayCacheMechanism(iv, containerType)
|
||||
CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class HashMapCacheMechanism(
|
||||
val iv: InstructionAdapter,
|
||||
val containerType: Type
|
||||
) : CacheMechanism {
|
||||
override fun loadCache() {
|
||||
iv.load(0, containerType)
|
||||
iv.getfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Ljava/util/HashMap;")
|
||||
}
|
||||
|
||||
override fun initCache() {
|
||||
iv.load(0, containerType)
|
||||
iv.anew(Type.getType("Ljava/util/HashMap;"))
|
||||
iv.dup()
|
||||
iv.invokespecial("java/util/HashMap", "<init>", "()V", false)
|
||||
iv.putfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Ljava/util/HashMap;")
|
||||
}
|
||||
|
||||
override fun clearCache() {
|
||||
iv.invokevirtual("java/util/HashMap", "clear", "()V", false)
|
||||
}
|
||||
|
||||
override fun getViewFromCache() {
|
||||
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
|
||||
iv.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false)
|
||||
}
|
||||
|
||||
override fun putViewToCache(getView: () -> Unit) {
|
||||
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
|
||||
getView()
|
||||
iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)
|
||||
iv.pop()
|
||||
}
|
||||
}
|
||||
|
||||
internal class SparseArrayCacheMechanism(
|
||||
val iv: InstructionAdapter,
|
||||
val containerType: Type
|
||||
) : CacheMechanism {
|
||||
override fun loadCache() {
|
||||
iv.load(0, containerType)
|
||||
iv.getfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Landroid/util/SparseArray;")
|
||||
}
|
||||
|
||||
override fun initCache() {
|
||||
iv.load(0, containerType)
|
||||
iv.anew(Type.getType("Landroid/util/SparseArray;"))
|
||||
iv.dup()
|
||||
iv.invokespecial("android/util/SparseArray", "<init>", "()V", false)
|
||||
iv.putfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Landroid/util/SparseArray;")
|
||||
}
|
||||
|
||||
override fun clearCache() {
|
||||
iv.invokevirtual("android/util/SparseArray", "clear", "()V", false)
|
||||
}
|
||||
|
||||
override fun getViewFromCache() {
|
||||
iv.invokevirtual("android/util/SparseArray", "get", "(I)Ljava/lang/Object;", false)
|
||||
}
|
||||
|
||||
override fun putViewToCache(getView: () -> Unit) {
|
||||
getView()
|
||||
iv.invokevirtual("android/util/SparseArray", "put", "(ILjava/lang/Object;)V", false)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -33,7 +33,7 @@ class ContainerOptionsProxy(val classType: AndroidContainerType, val cache: Cach
|
||||
private val CONTAINER_OPTIONS_FQNAME = FqName(ContainerOptions::class.java.canonicalName)
|
||||
private val CACHE_NAME = ContainerOptions::cache.name
|
||||
|
||||
private val DEFAULT_CACHE_IMPL = HASH_MAP
|
||||
private val DEFAULT_CACHE_IMPL = SPARSE_ARRAY
|
||||
|
||||
fun get(container: ClassDescriptor): ContainerOptionsProxy {
|
||||
if (container.kind != ClassKind.CLASS) {
|
||||
@@ -52,7 +52,7 @@ class ContainerOptionsProxy(val classType: AndroidContainerType, val cache: Cach
|
||||
if (supportsCache) DEFAULT_CACHE_IMPL else NO_CACHE)
|
||||
}
|
||||
|
||||
val cache = anno.getEnumValue(CACHE_NAME, HASH_MAP) { valueOf(it) }
|
||||
val cache = anno.getEnumValue(CACHE_NAME, DEFAULT_CACHE_IMPL) { valueOf(it) }
|
||||
|
||||
return ContainerOptionsProxy(classType, cache)
|
||||
}
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
package android.util
|
||||
|
||||
class SparseArray<E : Any> {
|
||||
private val map = HashMap<Int, E>()
|
||||
|
||||
fun get(key: Int): E? {
|
||||
return map.get(key)
|
||||
}
|
||||
|
||||
fun put(key: Int, value: E) {
|
||||
map.put(key, value)
|
||||
}
|
||||
|
||||
fun remove(key: Int): E? {
|
||||
return map.remove(key)
|
||||
}
|
||||
|
||||
fun clear() {}
|
||||
}
|
||||
+6
-1
@@ -13,4 +13,9 @@ public class MyActivity : Activity() {
|
||||
// 1 public _\$_clearFindViewByIdCache
|
||||
// 1 GETSTATIC test/R\$id\.login
|
||||
// 1 INVOKEVIRTUAL test/MyActivity\._\$_findCachedViewById
|
||||
// 1 CHECKCAST android/widget/Button
|
||||
// 1 CHECKCAST android/widget/Button
|
||||
// 12 android/util/SparseArray
|
||||
// 1 INVOKEVIRTUAL android/util/SparseArray\.get
|
||||
// 1 INVOKEVIRTUAL android/util/SparseArray\.put
|
||||
// 1 INVOKEVIRTUAL android/util/SparseArray\.clear
|
||||
// 0 java/util/HashMap
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ItemDetailActivity"
|
||||
tools:ignore="MergeRootFrame" >
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sign in" />
|
||||
|
||||
</FrameLayout>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package test
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import java.io.File
|
||||
import kotlinx.android.synthetic.main.layout.*
|
||||
import kotlinx.android.extensions.*
|
||||
|
||||
@ContainerOptions(cache = CacheImplementation.HASH_MAP)
|
||||
public class MyActivity : Activity() {
|
||||
init {login}
|
||||
}
|
||||
|
||||
// 1 public _\$_findCachedViewById
|
||||
// 1 public _\$_clearFindViewByIdCache
|
||||
// 1 GETSTATIC test/R\$id\.login
|
||||
// 1 INVOKEVIRTUAL test/MyActivity\._\$_findCachedViewById
|
||||
// 1 CHECKCAST android/widget/Button
|
||||
// 12 java/util/HashMap
|
||||
// 1 INVOKEVIRTUAL java/util/HashMap\.get
|
||||
// 1 INVOKEVIRTUAL java/util/HashMap\.put
|
||||
// 1 INVOKEVIRTUAL java/util/HashMap\.clear
|
||||
// 0 android/util/SparseArray
|
||||
+1
@@ -17,6 +17,7 @@
|
||||
package kotlinx.android.extensions
|
||||
|
||||
public enum class CacheImplementation {
|
||||
SPARSE_ARRAY,
|
||||
HASH_MAP,
|
||||
NO_CACHE;
|
||||
|
||||
|
||||
+6
@@ -156,6 +156,12 @@ public class AndroidBytecodeShapeTestGenerated extends AbstractAndroidBytecodeSh
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simpleHashMapCacheImplementation")
|
||||
public void testSimpleHashMapCacheImplementation() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simpleHashMapCacheImplementation/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simpleView")
|
||||
public void testSimpleView() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simpleView/");
|
||||
|
||||
Reference in New Issue
Block a user