3 回答

TA貢獻2051條經(jīng)驗 獲得超10個贊
監(jiān)聽器是異步的,如果你把 println 語句放在username =
行下面,它就會打印。
事實上,繼續(xù)做吧;觀察時間戳;哪個先打???空的還是回調(diào)里面的?
var正在被回調(diào)修改,但println
在 Firebase 發(fā)出其值之前很久(在計算機時代,即)首先執(zhí)行。
此外,我會顛倒行的順序mDatabase
。您本質(zhì)上是在請求一個值,然后聽取結果;結果可能已經(jīng)發(fā)出。您應該先添加偵聽器,然后再請求數(shù)據(jù)。
更新:如果我需要另一個回調(diào)的值怎么辦?
歡迎來到異步編程的世界:-)
你描述的是一組獨立的異步操作。您需要值 A 和值 B,但是在獲得值 A 之前您無法獲得值 B。兩者都是異步的并且需要時間,但是您在主線程上沒有時間,或者更確切地說,您有 ~16ms計算、測量和繪制屏幕,以便操作系統(tǒng)可以保持每秒 60 幀的速度。這不是很多時間,也是異步編程存在的部分原因!
簡而言之,您想要的是一個對象的實例,一旦操作完成就可以調(diào)用它。
在常規(guī)同步函數(shù)中,每個語句都在另一個語句之后執(zhí)行,并且在前一個語句未完成之前不會執(zhí)行任何語句;因此,所有語句都是阻塞語句。
例如:
var times = 2
var message = "Hello"
var world = "World"
println("The message is $message $times")
println(world)
將打印:
The message is Hello 2
World
這是因為執(zhí)行點會從一行走到另一行,等待上一行執(zhí)行。如果一個操作需要時間,線程將被阻塞(無法執(zhí)行任何其他操作)直到該操作完成并且執(zhí)行點可以移動到下一條指令。
可以想象,iOS 和 Android(以及 Windows、macOS、Linux 等)中的主線程無法被阻止,否則操作系統(tǒng)將無法響應您的觸摸和其他發(fā)生的事情(例如,手機,如果用戶界面沒有響應并且您無法點擊“接聽”),則無法處理來電。
這就是為什么我們使用其他“線程”來卸載不是超快的東西。這伴隨著思維方式的改變以及正確的計劃,因為現(xiàn)在事情變得更加復雜了。
讓我們看一個簡單的例子(一些偽代碼,所以請原諒任何明顯的錯誤,這只是為了說明這一點,而不是寫一個解決方案)。
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秒;在決定需要 ANR(應用程序未響應)對話框之前,這超過了 Android 操作系統(tǒng)對主線程無響應的 5 秒限制;這就是臭名昭著的“看起來 XXX 應用程序沒有響應,等待或關閉”。
你能做什么?
最初,如果你有太多的回調(diào)(其中回調(diào) A 在回調(diào) B 完成之前無法執(zhí)行,而回調(diào) B 在回調(diào) C 完成之前無法執(zhí)行),并且你開始像那樣嵌套它們,你最終會陷入臭名昭著的回調(diào)地獄(在 Javascript中) ,但適用于任何語言/平臺)。
基本上跟蹤所有這些異步回調(diào)并確保在響應到來時,您的下一個回調(diào)已準備就緒,等等是一件痛苦的事情,并且它會引入指數(shù)級的復雜性,例如,如果回調(diào) C 在中間失敗,現(xiàn)在您必須讓回調(diào) B 知道 C 失敗了,因此它也必須失敗,這反過來又必須讓回調(diào) A(原來的!)知道 B 失敗了,因此 A 必須對此做些什么,A 是否需要知道 B 因為 C 而失敗了嗎?還是 A 只關心 B 和 B,而 B 失敗背后的原因無關緊要?
好吧,正如您所看到的,即使談論這個也會變得復雜和混亂,我什至沒有涵蓋其他同樣復雜的可能場景。
我在這里想說的并不是說你不應該使用回調(diào);而是說你不應該使用回調(diào)。而是您必須仔細計劃何時何地使用它們。
Kotlin 有一些替代方案可以通過使用協(xié)程來減少/消除回調(diào)地獄,但這些是一個中等高級的主題,它還需要對組件和部件的設計方式進行根本性的改變。
總而言之,對于您的用例,請記住 OOP 的黃金法則:制作做很少事情的小型具體類,并將它們做好。if ()
如果您需要在各處開始添加太多,那么您很可能會在各處混合業(yè)務邏輯、隨機決策和“whatabout”案例。
假設您有一個處理位置數(shù)據(jù)并將其上傳到服務器的類。
您可能會想:
在 Activity/Fragment(或 ViewModel)中編寫所有代碼;很快就變得一團糟。
使用靜態(tài)方法(或單例模式)創(chuàng)建 LocationUtils;已經(jīng)一團糟,但也很難測試和模擬。如果您需要不止一種類型的處理怎么辦?或者如果你想將它們存儲在數(shù)據(jù)庫中,你是否要添加更多的靜態(tài)方法?
創(chuàng)建一個小的 LocationProcessor 類,它接收兩個點(緯度/經(jīng)度)在一個小函數(shù)中進行處理,并返回處理后的數(shù)據(jù),然后創(chuàng)建另一個名為 LocationUploader 的類,它從處理器接收干凈的輸入,并將其上傳到服務器.?這些類都不應該考慮“如果我沒有權限怎么辦,如果用戶關閉位置怎么辦”等等。這些問題超出了一個類的責任范圍,該類的目的是處理位置坐標,別無其他。應該有其他類負責。請記住,小班級、小職責== 在單個文件中無需擔心。
結論?
好吧,此時有更好的答案可以為您提供所需內(nèi)容的復制粘貼版本;我相信你今天必須從這堵文字墻中拿出的概念是,為了編寫現(xiàn)代的、可測試的、簡單的功能代碼,你計劃事情的方式必須發(fā)生變化。
長話短說:當事情不是同步的時候,你需要保持一些東西(一個對象)準備好被回調(diào)(因此名稱回調(diào)),監(jiān)聽(或觀察)(因此我們稱它們?yōu)楸O(jiān)聽器或觀察器),發(fā)射某物(通常稱為 Observable,因為它可以被“觀察”)。
祝你好運!

TA貢獻1806條經(jīng)驗 獲得超8個贊
正如 Martin 所說,這是一個異步操作,您應該在異步過程完成后處理文本輸出:
mDatabase.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
TODO("not implemented")
}
override fun onDataChange(snapshot: DataSnapshot) {
userName = snapshot.child(uid).child("name").getValue().toString()
println(userName) //--> Asynchronous request has ended, show the name
}
})

TA貢獻1799條經(jīng)驗 獲得超6個贊
是的,偵聽器是異步的,只有在 onDataChange 方法中打印變量時它才會起作用。
但是,您可以使用回調(diào)策略來等待 Firebase 返回數(shù)據(jù)。是這樣的:
interface MyCallback {
fun onCallback(value: String )
}
fun readData(myCallback: MyCallback){
mDatabase.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
userName = snapshot.child(uid).child("name").getValue().toString()
myCallback.onCallback(value)
}
})
}
fun test(){
readData(object: MyCallback {
override fun onCallback(value : String) {
println(value)
}
})
}
添加回答
舉報