如何让 Android “掷骰子”?解析 Kotlin 中的随机数生成
\n\n你是否曾好奇过,你的手机是如何“选择”一个随机数的?看似简单的任务——获取一个 5 到 10 之间的随机整数。但在 Android 和 Kotlin 的底层,隐藏着一整套机制,它不仅关系到便利性,还关系到你应用的安全性。难怪 “How can I generate random number in specific range in Android?” 这个问题在 Stack Overflow 上获得了数千次浏览。我们不仅会给出现成的答案——我们还会深入源码,弄清楚实际发生了什么。
\n\n在本文中,你将了解到:
\n- \n
- 如何在 Kotlin 中正确生成指定范围内的随机数。 \n
kotlin.random.Random、java.util.Random和SecureRandom之间的区别。 \n - 为什么博彩游戏需要一种类,而密码生成需要另一种。 \n
- 如何避免那些将“随机性”变成可预测性的典型错误。 \n
开始吧!
\n\n经典方式:Kotlin 中的 Random(以及为什么它并不总是正确的)
\n\n最显而易见的方法是使用 kotlin.random.Random。它内置于语言中,开箱即用。以下是生成 0 到 100(含)之间随机数的方法:
import kotlin.random.Random\n\nfun main() {\n val randomNumber = Random.nextInt(0, 101) // 从 0 到 100(含)\n println("随机数: $randomNumber")\n}\n\n一切都很简单。但让我们看看底层。在 JVM 平台上,Random.nextInt(from, until) 内部使用了 java.util.Random。而后者又通过 线性同余生成器 (LCG) 生成数字。公式很简单:
new_seed = (old_seed * A + C) mod M
其中 A、C 和 M 是常量。这很快,但 不具备密码学安全性。如果攻击者知道了几个数字,他们就可以恢复种子并预测所有后续数字。
\n\n何时使用: 用于游戏(非赌博)、从列表中随机选择元素、在普通应用中洗牌。
\n\n进阶用法:用于多线程的 ThreadLocalRandom
\n\n如果你的应用大量使用协程或多线程,普通的 Random 可能会成为瓶颈。每次调用 nextInt() 都会进行同步,以避免竞态条件。解决方案是使用 Java 中的 ThreadLocalRandom:
import java.util.concurrent.ThreadLocalRandom\n\nfun getRandomInRange(min: Int, max: Int): Int {\n return ThreadLocalRandom.current().nextInt(min, max + 1)\n}\n\n这个类为每个线程分配一个单独的 Random 实例。不需要同步——每个线程都使用自己的生成器。在实践中,这可以在多线程场景中提升性能。
安全第一:SecureRandom
\n\n现在想象一下,你正在生成一次性密码 (OTP) 或用于恢复访问的令牌。使用普通的 Random 是一种错误。这时需要用到 SecureRandom:
import java.security.SecureRandom\n\nfun generateSecureCode(length: Int): String {\n val secureRandom = SecureRandom()\n val digits = "0123456789"\n return (1..length)\n .map { digits[secureRandom.nextInt(digits.length)] }\n .joinToString("")\n}\n\nSecureRandom 从操作系统的熵源收集熵:麦克风噪音、按键时间(在模拟器中,来自 /dev/random 或 /dev/urandom 文件)。这使其不可预测。但代价是速度——它可能比普通的 Random 慢几十倍。
重要提示: 在 Android 上,S