监听器是异步的,如果你把 println 语句放在username = 行下面,它就会打印出来。
事实上,继续做吧;观察时间戳;哪个先打印?空的还是回调里面的?
var 正在被回调修改,但 println 首先执行,远在 Firebase 发出其值之前(在计算机时代)。
另外,我会颠倒mDatabase 行的顺序。
您本质上是在请求一个值,然后监听结果;结果可能已经发出。您应该先添加侦听器,然后再请求数据。
更新:如果我需要另一个回调的值怎么办?
欢迎来到异步编程的世界:-)
你描述的是一组独立的异步操作。您需要值 A 和值 B,但在获得值 A 之前无法获得值 B。两者都是异步的并且需要时间,但是您没有时间在主线程上,或者更确切地说,您有 ~16ms计算、测量和绘制屏幕,使操作系统能够跟上每秒 60 帧的速度。这不是很多时间,也是异步编程存在的部分原因!
This other answer 已经提供了您需要的工作示例。 This other external link 有一个更具体的观察者侦听器模式示例。
简而言之,你想要的是一个对象的实例,一旦操作完成就可以调用它。
在一个常规的同步函数中,每条语句都在另一条语句之后执行,直到前一条语句没有完成,才会执行任何语句;因此,所有语句都是阻塞语句。
例如:
var times = 2
var message = "Hello"
var world = "World"
println("The message is $message $times")
println(world)
将打印:
The message is Hello 2
World
这是因为执行点会从一行到另一行,等待上一个执行。如果一个操作需要时间,则线程将被阻塞(无法执行其他任何操作),直到该操作完成并且执行点可以移动到下一条指令。
您可以想象,iOS 和 Android(以及 Windows、macOS、Linux 等)中的主线程无法被阻止,否则操作系统将无法响应您的触摸和其他发生的事情(例如例如,在手机上,如果 UI 没有响应并且您无法点按“接听”,则无法处理来电。
这就是为什么我们使用其他“线程”来卸载不是超快的东西。这伴随着思维方式的改变和正确的计划,因为现在事情变得更加复杂了。
让我们看一个简单的例子(一些伪代码,所以请承担任何明显的错误,这只是为了说明问题,而不是写解决方案)。
fun main() {
var hello = "Hello"
var message = thisTakesTime()
println("The message is $hello $message")
println(hello)
}
fun thisTakesTime(): String {
// do something that takes 1 second (1000 ms)
Thread.sleep(1000)
return "World"
}
这将打印出来
The message is Hello World
Hello
如您所见,没有任何变化,除了整整一秒钟,主线程没有响应。例如,如果你要在 Android 上运行它,它会工作,但你的应用在 Thread.sleep 期间不会有一秒钟的响应。一秒快,试试10秒;这超过了 Android 操作系统对主线程无响应的 5 秒限制,然后才决定需要 ANR(应用程序无响应)对话框;这就是臭名昭著的“看起来 XXX 应用程序没有响应、等待或关闭”。
你能做什么?
最初,如果你有太多回调(回调 A 直到回调 B 完成才能执行,而回调 B 直到回调 C 完成才能执行),然后你开始像这样嵌套它们,你最终会陷入臭名昭著的 Callback-Hell (在 Javascript 中,但适用于任何语言/平台)。
基本上跟踪所有这些异步回调并确保在响应到来时,您的下一个回调已准备就绪,等等是一件痛苦的事情,并且它会引入指数复杂性,例如回调C 在中间失败了,现在你必须让回调 B 知道 C 失败了,因此它也必须失败,这反过来又必须让回调 A(原来的!)知道 B 失败了,因此 A 有要对此做点什么,A 是否需要知道 B 因为 C 而失败?还是A只关心B和B,而B失败的原因无关紧要?
好吧,正如您所看到的,即使谈论这个也会变得复杂和混乱,我什至没有涵盖同样复杂的其他可能的场景。
我在这里想说的不是你不应该使用回调;而是你不应该使用回调。只是你必须仔细计划在何时何地使用它们。
Kotlin 有替代方法可以通过使用 Coroutines 来减少/删除回调地狱,但这些是一个中等高级的主题,它还需要从根本上改变您设计组件和部件的方式。
总而言之,对于您的用例,请记住 OOP 的黄金法则:创建小的具体类,做很少的事情,然后把它们做好。如果您需要开始在各处添加太多if (),那么您很可能会在各处混合业务逻辑、随机决策和“whatabout”案例。
假设您有一个处理位置数据并将其上传到服务器的类。
你可能会想:
- 在Activity/Fragment(或ViewModel)中编写所有代码;很快就会变得一团糟。
- 使用静态方法(或单例模式)创建 LocationUtils;已经一团糟,而且还很难测试和模拟。如果您需要不止一种类型的处理怎么办?或者如果你想将它们存储在数据库中,你会添加更多的静态方法吗?
- 创建一个小的 LocationProcessor 类,它接收两个点(纬度/经度)在一个小函数中进行处理,并返回处理后的数据,然后创建另一个名为 LocationUploader 的类,它接收来自处理器的干净输入并上传它到服务器。这些类都不应该考虑“如果我没有权限怎么办,如果用户关闭位置怎么办”等。这些问题超出了旨在处理位置坐标的类的职责,仅此而已。应该有其他类负责。请记住,小类,小职责 == 在单个文件中无需担心。
结论?
嗯,现在有更好的答案可以为您提供所需内容的复制粘贴版本;我相信您今天必须从这堵文字墙中汲取的概念是,为了编写现代、可测试和简单的功能代码,您必须改变计划事情的方式。
长话短说:当事情不同步时,您需要保持某些东西(一个对象)准备好被回调(因此名称为回调),侦听(或观察)(因此我们称它们为侦听器或观察者),某些东西的发射(通常称为 Observable,因为它可以被“观察”)。
祝你好运!