9

在我们最近发布的应用程序中,我们看到kotlin.NoWhenBranchMatchedException向 Fabric/Crashlytics 报告了少数 s。

这是有问题的代码片段:

private lateinit var welcome: Welcome

// ...

welcome.welcomeStateLoginStatus.let {
    val handled = when (it) {
        UnknownUser -> {
            btn_login.visibility = View.VISIBLE
            btn_logout.visibility = View.GONE

            secondButtonFocusedInfoText = getString(R.string.welcome_login_button_info)
            tv_user_description.text = null
        }
        is InternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as internal user"
        }
        ExternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as external user"
        }
    }
}

和类定义:

data class Welcome(val welcomeStateLoginStatus: WelcomeStateLoginStatus, val userCanBuySubscription: UserCanBuySubscription? = null) : WelcomeState()

sealed class WelcomeStateLoginStatus() : Serializable
object UnknownUser : WelcomeStateLoginStatus()
data class InternalUser(var user: User) : WelcomeStateLoginStatus()
object ExternalUser : WelcomeStateLoginStatus()

我对这段代码在理论上如何抛出异常感到困惑——正如你所看到的,我们甚至引入了这个handled值,只是为了强制编译器确保所有情况都得到处理......

4

1 回答 1

10

Serialization was indeed the problem:

package com.drei.tv.ui.welcome

import junit.framework.Assert.assertEquals
import org.junit.Test
import java.io.*


class WelcomeStateLoginStatusTest {

    @Test
    fun testSerialization() {
        val original: UnknownUser = UnknownUser

        val copy: UnknownUser = unpickle(pickle(original), UnknownUser::class.java)

        println("singleton: $UnknownUser")
        println("original: $original")
        println("copy: $copy")

        val handled1 = when (copy) {
            original -> println("copy matches original")
            else -> println("copy does not match original")
        }

        val handled2 = when (copy) {
            is UnknownUser -> println("copy is an instance of UnknownUser")
            else -> println("copy is no instance of UnknownUser")
        }

        assertEquals(original, copy)
    }

    private fun <T : Serializable> pickle(obj: T): ByteArray {
        val baos = ByteArrayOutputStream()
        val oos = ObjectOutputStream(baos)
        oos.writeObject(obj)
        oos.close()
        return baos.toByteArray()
    }

    private fun <T : Serializable> unpickle(b: ByteArray, cl: Class<T>): T {
        val bais = ByteArrayInputStream(b)
        val ois = ObjectInputStream(bais)
        val o = ois.readObject()
        return cl.cast(o)
    }
}

Produces the following output:

singleton: com.drei.tv.ui.welcome.UnknownUser@75828a0f
original: com.drei.tv.ui.welcome.UnknownUser@75828a0f
copy: com.drei.tv.ui.welcome.UnknownUser@5f150435
copy does not match original
copy is an instance of UnknownUser

junit.framework.AssertionFailedError: 
Expected :com.drei.tv.ui.welcome.UnknownUser@75828a0f
Actual   :com.drei.tv.ui.welcome.UnknownUser@5f150435

As for solutions: Either implement Serializable properly or just use an is check instead of an equality check.

Thanks to Lionel Briand and Hong Duan for pointing us in the right direction and to Jason S for the code of pickle and unpickle posted in this answer

于 2018-06-21T08:40:20.480 回答