Почему Котлин бросает исключение в очень простой кусок кода? - PullRequest
0 голосов
/ 10 июля 2019
  • В конце вопроса обновление .Теперь я должен включить и отключить ошибку с одним изменением строки в крошечном проекте со всем кодом, транскрибированным ниже.
  • Ответ из Алексей Романов блестящийПроверьте в конце вопроса.Это удивительная и неизвестная особенность среды Android Studio .

Я обнаружил очень странную ошибку в Kotlin при использовании обновленной Android 3.4.2 .

Сначала я запустил свойпроверить код на моем компьютере (не Android ), используя основные модули в любом файле kotlin в моем единственном модуле.Он всегда работает для меня, но начал выдавать ошибку, которую я комментирую ниже, а затем еще одну ошибку, которая больше не позволяет запускать основной модуль.

Поиск в Stack Overflow, один пользователь, как утверждалосьчто последняя описанная ошибка заканчивается, когда кто-то использует test файлы в папке java (не 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")
}   

Я просто указываю на fun main() строку в test файле и нажимаюзеленый значок.

Сообщение об ошибке

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")
}

Полныйсообщение об ошибке при запуске кода test.kt на моем компьютере (не в Android):

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

Дляте, кто хочет видеть, чтобы верить:

crazy bug

Заключение : Теперь я знаю, как избежать этой сумасшедшей ошибки, но я не понимаюне понимаю, почему это работает!Теоретически, исходный файл, который я использую для своих объявлений, должен быть взаимозаменяемым, потому что все файлы находятся в одном модуле!


                              Solution of puzzle          

Алексей Романов ударил ногтем по голове!

Я исследовал немного больше и обнаружил, что 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

и поместить инициализацию в другое место (например, внутри onCreate в MainActivity классе. В моем случае, непосредственно перед1-й вызов addSplit, один из методов TimingLogger class

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

Теперь мой тестовый код плавно печатает

start
fool
what a big fool
ok

Ответы [ 2 ]

3 голосов
/ 10 июля 2019

Проблема не имеет ничего общего с Kotlin;Вы не можете просто использовать классы Android в коде, запущенном непосредственно на вашем компьютере, а не в эмуляторе, потому что версии в стандартном jar-коде будут зависать при первом использовании.Они просто предоставляют файлы классов с теми же именами и сигнатурами методов, которые будут существовать на устройстве, чтобы ваш код мог компилироваться (не запускаться!).

Использовать Robolectric для полученияверсия библиотеки Android, пригодная для JVM-тестов.

, если я помещу приведенную ниже декларацию в MainActivity.kt и снова запустите test.kt на моем компьютере

Тогда ваш тест не использует классы Android (подробности см. Ниже).

Стоит помнить, что в одном модуле все объявления, независимо от того, в каком файле они находятся,выполняются.

Это неправильно.Что происходит с объявлениями верхнего уровня val / var / fun в Kotlin, так это то, что они заключены в один класс для каждого файла, поэтому ваш код работает так:

// 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() делает не load MainActivity (что можно подтвердить, добавив некоторую печатьв этот файл), поэтому, если вы переместите туда var timings, ничего в библиотеке заглушки не будет вызвано.

0 голосов
/ 10 июля 2019

Вы пропустили цитату в первой строке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...