Kotlin random() 总是生成相同的“随机”数字

rm *_*-rf 6 random android kotlin

我创建了一个应用程序,它应该从图像数组中随机选择一个图像。在我的模拟器 Nexus 5X And​​roid 5.1 上,一切都按预期运行。一旦我在真实设备 Galaxy Note 10 Lite 上尝试相同的操作,我总是会以相同的顺序得到相同的“随机”数字。我首先需要重新启动手机以生成一个新的“随机”数字列表,然后该列表始终相同。示例:我的数组包含 200 个元素,我在 Galaxy 上打开应用程序,它为图像 ID 选择以下随机数:43、12、176、33、2、78。然后我关闭应用程序并再次打开应用程序,现在它又具有完全相同的“随机”数字:43、12、176、33、2、78。我需要重新启动手机才能获取新的随机数字,这些数字将保持不变,直到我再次重新启动手机。在我的模拟器上,一切正常,当我按预期重新启动应用程序时,我总是会得到新的随机数。

\n

这是我的应用程序的完整代码,没有图像数组列表:

\n

MainActivity.kt

\n
class MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n val imageList = arrayOf(Image(R.drawable.image1, false),\n            Image(R.drawable.image2, false),\n            Image(R.drawable.image3, false))\n\nval imageViewMain = findViewById<ImageView>(R.id.imageViewMain)\n    loadNextImage(imageViewMain, imageList)\n\n    imageViewMain.setOnClickListener {\n        val dialogClickListener =\n            DialogInterface.OnClickListener { _, which ->\n                when (which) {\n                    DialogInterface.BUTTON_POSITIVE -> {\n                        loadNextImage(imageViewMain, imageList)\n                    }\n                    DialogInterface.BUTTON_NEGATIVE -> { }\n                }\n            }\n        val builder: AlertDialog.Builder = AlertDialog.Builder(this)\n        builder.setMessage("N\xc3\xa4chstes Bild?").setPositiveButton("Ja", dialogClickListener)\n            .setNegativeButton("Nein", dialogClickListener).show()\n    }\n}\n\nprivate fun getNextChoice(): Int {\n    return (0..1).random()\n}\n\nprivate fun getNextImage(imageList: Array<Image>): Int {\n    val listSize = imageList.size\n    var imageId: Int\n    do {\n        imageId = (0 until listSize).random()\n    } while (imageList[imageId].played)\n\n    imageList[imageId].played = true\n    return imageList[imageId].image\n}\n\nprivate fun loadNextImage(imageViewMain: ImageView, imageList: Array<Image>) {\n    val imageQuestionmark = R.drawable.questionmark\n    val nextChoice = getNextChoice()\n    if (nextChoice == 0) {\n        imageViewMain.load(imageQuestionmark)\n    } else if (nextChoice == 1) {\n        imageViewMain.load(getNextImage(imageList))\n    }\n    Toast.makeText(this, "Bild hat geladen", Toast.LENGTH_SHORT).show()\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n

图像:

\n
data class Image(\n    val image: Int,\n    var played: Boolean\n)\n
Run Code Online (Sandbox Code Playgroud)\n

编辑: \n我尝试了 cactustictacs 在评论中建议的内容,并创建了一个简单的应用程序,一次使用 kotlin 随机函数,一次使用 java 随机函数。这是我使用的代码:

\n

科特林:

\n
import androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport android.widget.Button\nimport android.widget.Toast\n\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        val buttonTest = findViewById<Button>(R.id.buttonTest)\n\n        buttonTest.setOnClickListener {\n            val getRandomNumber = (0..999).random()\n            Toast.makeText(this, getRandomNumber.toString(), Toast.LENGTH_SHORT).show()\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

爪哇:

\n
import androidx.appcompat.app.AppCompatActivity;\n\nimport android.os.Bundle;\nimport android.widget.Button;\nimport android.widget.Toast;\n\nimport java.util.Random;\n\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        Button buttonTest = (Button) findViewById(R.id.buttonTest);\n\n        buttonTest.setOnClickListener(v -> {\n            int randomNumber = new Random().nextInt(999);\n            Toast.makeText(this, "" + randomNumber, Toast.LENGTH_SHORT).show();\n        });\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在 Kotlin 上,我得到的行为与最初的问题相同,无论我对应用程序做什么(我什至可以卸载并再次安装),我总是得到相同的数字集。在 Java 上,它按预期工作,一旦我关闭应用程序,我就会得到一组新的数字。所以错误肯定出在 kotlin 上。

\n

也许有帮助,我的 Android 版本是 12,我的手机是 Galaxy Note 10 Lite。

\n

Mer*_*çen 3

这是 Kotlin 默认 Random 类的一个非常糟糕的实现。Java Random 类尝试在每个新实例上始终使用不同的种子,而 Kotlin 在整个设备中硬编码相同的种子。这怎么可能成为 Random 实现的默认行为。我花了很多时间才理解它。

参见Java实现:

 public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

private static final AtomicLong seedUniquifier
    = new AtomicLong(8682522807148012L);
Run Code Online (Sandbox Code Playgroud)

啊啊啊,Kotlin 来了:

companion object Default : Random(), Serializable {
    private val defaultRandom: Random = defaultPlatformRandom()

    private object Serialized : Serializable {
        private const val serialVersionUID = 0L

        private fun readResolve(): Any = Random
    }

    private fun writeReplace(): Any = Serialized

    override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount)
    override fun nextInt(): Int = defaultRandom.nextInt()
    override fun nextInt(until: Int): Int = defaultRandom.nextInt(until)
    override fun nextInt(from: Int, until: Int): Int = defaultRandom.nextInt(from, until)
Run Code Online (Sandbox Code Playgroud)

defaultRandom 是一个单例,总是以相同的种子启动......

(我通过 Android Studio 源获取了此代码...)

注意:所以这是 kotlin 版本 1.7.10 和 Android api 小于 33-34 的错误。已于 20 年 7 月 1 日修复...