Android Extensions: Add SparseArray implementation for the View cache (KT-18250)

This commit is contained in:
Yan Zhulanow
2017-06-05 23:32:56 +03:00
parent fbfd51e97e
commit 7b238e0b21
9 changed files with 221 additions and 41 deletions
@@ -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)
@@ -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)
}
}
@@ -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)
}
@@ -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() {}
}
@@ -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
@@ -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>
@@ -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
@@ -17,6 +17,7 @@
package kotlinx.android.extensions
public enum class CacheImplementation {
SPARSE_ARRAY,
HASH_MAP,
NO_CACHE;
@@ -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/");