【问题标题】:Why Kotlin throws an exception in a very simple piece of code?为什么 Kotlin 在一段非常简单的代码中抛出异常?
【发布时间】:2019-07-10 00:53:45
【问题描述】:
  • 问题末尾有一个更新。现在我必须在一个小项目中通过一行更改来打开和关闭错误,所有代码都转录在下面。
  • Alexey Romanov答案非常出色。检查问题的最后。这是 Android Studio 环境的一个惊人而未知的功能。

我在使用更新的 Android 3.4.2Kotlin 中发现了一个非常古怪的错误。

首先,我使用我唯一模块中的任何 kotlin 文件中的主模块在我的计算机(不是 Android)中运行我的测试代码。它总是对我有用,但它已经开始给出我在下面评论的错误,然后是另一个不允许运行主模块的错误。

Stack Overflow 中搜索,一位用户声称最后描述的错误在使用java 文件夹下的test 文件时结束(不是Android test 文件)

但是仍然继续给出第一个错误,我在上面评论过,我设法将其简化为以下示意图示例:

class Cl(
  var a:Int=0
)
var vCl = arrayListOf<Cl>()

主要模块是:

fun main(){
    println("start")
    vCl.clear()  // error points to this line
    println("ok")
}   

我只是指向 test 文件中的fun main() 行,然后单击绿色图标。

错误信息

Exception in thread "main" java.lang.ExceptionInInitializerError

当我将一个全局语句从一个文件更改为另一个文件(在顶部,在任何类或函数之外)时,错误突然停止。

var timings = TimingLogger("MyTag", "Your")

这太疯狂了。我吓坏了!

更新:
=======

我制作了newerror,这是一个新的小型项目,其中包含一个模块来重现此问题中描述的错误。以下是完整代码:

Gradle:项目创建后没有变化。

AndroidManifest.xml:项目创建后没有变化。

activity_main.xml:它很简单,因为我所有的视图都是动态创建的:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/myLayout"
        android:textAllCaps="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent" tools:context=
          "br.com.greatsolutions.paulo.myerror.MainActivity">
</RelativeLayout>

MainActivity.kt 代码:

package br.com.greatsolutions.paulo.myerror

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

compiler.kt 代码

package br.com.greatsolutions.paulo.myerror

import android.util.TimingLogger
var timings = TimingLogger("MyTag", "Your")
class Cl(
    var a:Int=0
)
var vCl = arrayListOf<Cl>()

最后是 test.kt 代码:

package br.com.greatsolutions.paulo.myerror

fun main(){
    println("start")
    vCl.clear()
    println("ok")
}

当我在我的电脑(不是 Android)中运行 test.kt 代码时的完整错误消息:

start
Exception in thread "main" java.lang.ExceptionInInitializerError
    at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt:5)
    at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt)
Caused by: java.lang.RuntimeException: Stub!
    at android.util.TimingLogger.<init>(TimingLogger.java:59)
    at br.com.greatsolutions.paulo.myerror
              .CompilerKt.<clinit>(compiler.kt:5)
    ... 2 more

再一次,如果我将以下声明放入 MainActivity.kt 并在我的计算机中再次运行 test.kt ...

var timings = TimingLogger("MyTag", "Your")

...错误消失了!

start
ok

对于那些希望看到相信的人:

结论:现在我知道如何避免这个疯狂的错误,但我不明白它为什么会起作用!理论上,我用来进行声明的源文件应该是可互换的,因为所有文件都在同一个模块中!


                              Solution of puzzle          

Alexey Romanov 一针见血!

我进行了更多研究,发现 Android Studio 只有在计算机测试中使用了范围的任何变量时才会执行文件中的声明。

var timings = TimingLogger("MyTag", "Your")

MainActivity.kt 内,没有显示错误。

我在这个文件中放入以下代码后:

open class Fool(val a:Int=5){
   init { println("fool") }
} 

class SuperFool(a:Int=8):Fool(a) {
  init { println("what a big fool") }
}
var v = SuperFool()

当我把 println(v.a) 我的代码放在 test.kt 中时:

package br.com.greatsolutions.paulo.myerror

fun main(){
    println("start")
    println(v.a)   // new line
    vCl.clear()    
    println("ok")
}

它给出了与以前相同的错误!把这条线去掉,又没有错误了!

解决方案是,如果您的项目中的 Android 类中有任何变量声明,您必须使用 lateinit 并在其他不会在测试执行中运行的代码点初始化。

在这种情况下可以做

lateinit var timings:TimingLogger

并将初始化放在其他地方(例如,在 MainActivity 类中的 onCreate 内。在我的情况下,就在第一次调用 addSplit 之前,这是来自 TimingLogger 类的方法之一

timings = TimingLogger("MyTag", "Your")

现在我的测试代码会打印出来

start
fool
what a big fool
ok

【问题讨论】:

  • 你是如何实例化 vDispFu 的?
  • 抱歉,又一个错字。简化使用vCl
  • 我已经更正了。
  • 我刚刚在我的测试文件中尝试过这个,它通过了。您是否将它们放在同一个文件中?
  • 可以在这里发布完整代码吗?

标签: android exception kotlin


【解决方案1】:

问题与 Kotlin 无关;您不能只在直接在您的计算机上而不是在模拟器中运行的代码中使用 Android 类,因为标准 jar 中的版本在首次使用时会崩溃。它们只是为了提供与设备上存在的名称和方法签名相同的类文件,以便您的代码可以编译(而不是运行!)。

使用 Robolectric 获取可用于 JVM 测试的 Android 库版本。

如果我将以下声明放入 MainActivity.kt 并在我的计算机中再次运行 test.kt

那么您的测试不使用任何 Android 类(详见下文)。

值得记住的是,在同一个模块中,所有声明,无论它们位于哪个文件中,都会被执行。

错了。 Kotlin 中的顶级 val/var/fun 声明会发生什么情况,它们会被包装到每个文件的单个类中,因此您的代码如下所示:

// compiler.kt
package br.com.greatsolutions.paulo.myerror

object CompilerKt { // you can actually see the name in the stack trace
    var timings = TimingLogger("MyTag", "Your")
    val vCl = arrayListOf<Cl>()
}

class Cl(
    var a:Int=0
)

// test.kt
package br.com.greatsolutions.paulo.myerror

object TestKt {
    fun main(){
        println("start")
        CompilerKt.vCl.clear()
        println("ok")
    }
}

所以调用TestKt.main() 会强制加载和初始化CompilerKt,因为您在那里引用了vCl。这包括调用构造函数 TimingLogger("MyTag", "Your") 在使用存根库时抛出异常。

调用TestKt.main() 确实 加载MainActivity(您可以通过向该文件添加一些打印来确认),因此如果您将var timings 移动到那里,存根库中的任何内容都不会被调用.

【讨论】:

  • 我知道,但这只是一个声明和初始化。变量,这不是真正的用法,因为我在测试中没有使用timingLogger。当我简单地将声明更改为另一个源文件时,我的小型演示项目可以完美运行(而且原始项目也大得多)。值得记住的是,在同一个模块中,所有声明,无论它们在哪个文件中,都会被执行。
  • 1.查看错误跟踪:在构造函数at android.util.TimingLogger.&lt;init&gt;(TimingLogger.java:59) 中抛出了异常。这个构造函数正是 TimingLogger("MyTag", "Your") 所调用的。
  • 2. “值得记住的是,在同一个模块中,所有声明,无论它们位于哪个文件中,都会被执行。”不,他们不是。只需将 TimingLogger 替换为您自己的类,该类会在构造函数中打印一些内容,您会看到它在当前失败的情况下打印出来,而在成功的情况下不会打印出来。
  • 我已经扩展了答案。
  • 哇!这很疯狂。你说的对。我做了以下测试。我输入了MainActivity 以下代码open class Fool(val a:Int=5){ init {println("fool") }} class SuperFool(a:Int=8):Fool(a){init {println("what a big fool")}}。当我将println(v.a) 放入compiler.kt 时出现错误。拿出来,没有错误!就好像 Android Studio 仅在使用范围的任何变量时才执行文件中的声明。
【解决方案2】:

您在第一行缺少引号。

【讨论】:

  • 这只是我更改开始消息时的一个错字。这不是我的代码中的真正错误。该程序甚至无法运行并出现此错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多