Android中注意使用Kotlin forEach
最近开始使用kotlin,因为一直使用的是Android 7.0测试手机,所有没有发现问题。
然后今天准备在低版本测试一下后发现打开某个页面崩溃了,
java.lang.NoClassDefFoundError: xxx.xxx$lambda$1
很奇怪,开始以为的MultiDex 导致的,但是这个崩溃并不是在启动就闪退,而是过了几个页面才出现的。
根据代码行数发现是在一个map.forEach处,
代码如下,很简单的循环
data.forEach { k,v ->
//
}
先说解决方案吧
将代码改为如下这样
data.forEach { (k, v) ->
//
}
原因:
我们先复现场景
fun test1(){
hashMapOf("a" to "b").forEach { t, u ->
}
}
fun test2(){
hashMapOf("a" to "b").forEach { (t, u) ->
}
}
根据上面的情况,test1()方法在API24之前会崩溃,而test2()正常,我们看下编译之后的smali代码.
先看test1()的
.method public final test1()V
new-array v0, v0, [Lkotlin/Pair;
invoke-static {v0}, Lkotlin/collections/MapsKt;->hashMapOf([Lkotlin/Pair;)Ljava/util/HashMap;
move-result-object v1
check-cast v0, Ljava/util/function/BiConsumer;
invoke-virtual {v1, v0}, Ljava/util/HashMap;->forEach(Ljava/util/function/BiConsumer;)V
return-void
.end method
很容易发现kotlin使用了java8的forEach,java8的forEach是通过java.util.function.BiConsumer
实现的,这个类在API24之前没有,所以会崩溃。
我们再看test2()方法生成的smail代码
.method public final test2()V
invoke-static {v4}, Lkotlin/collections/MapsKt;->hashMapOf([Lkotlin/Pair;)Ljava/util/HashMap;
move-result-object v0
check-cast v0, Ljava/util/Map;
.line 19
.local v0, "$receiver$iv":Ljava/util/Map;
invoke-interface {v0}, Ljava/util/Map;->entrySet()Ljava/util/Set;
move-result-object v4
invoke-interface {v4}, Ljava/util/Set;->iterator()Ljava/util/Iterator;
move-result-object v5
:goto_1c
invoke-interface {v5}, Ljava/util/Iterator;->hasNext()Z
move-result v4
if-eqz v4, :cond_37
invoke-interface {v5}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v2
check-cast v2, Ljava/util/Map$Entry;
.local v2, "element$iv":Ljava/util/Map$Entry;
move-object v1, v2
.local v1, "$t_u":Ljava/util/Map$Entry;
invoke-interface {v1}, Ljava/util/Map$Entry;->getKey()Ljava/lang/Object;
move-result-object v3
check-cast v3, Ljava/lang/String;
.local v3, "t":Ljava/lang/String;
invoke-interface {v1}, Ljava/util/Map$Entry;->getValue()Ljava/lang/Object;
move-result-object v4
check-cast v4, Ljava/lang/String;
.line 16
nop
goto :goto_1c
.line 20
.end local v1 # "$t_u":Ljava/util/Map$Entry;
.end local v2 # "element$iv":Ljava/util/Map$Entry;
.end local v3 # "t":Ljava/lang/String;
:cond_37
nop
.line 17
return-void
.end method
不难看出,这种情况下使用的是Iterator
循环,所以可以正常运行。
参考:
https://developer.android.com/studio/write/java8-support.html
https://stackoverflow.com/questions/42869086/java-lang-noclassdeffounderror-inlinedforeachlambda1-in-kotlin
http://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/