แอปพลิเคชัน Firebase ทำงานได้แม้ว่าแอปของคุณจะขาดการเชื่อมต่อเครือข่ายชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับการรักษาข้อมูลในเครื่อง การจัดการการแสดงตน และการจัดการเวลาในการตอบสนอง
ความคงอยู่ของดิสก์
แอป Firebase จะจัดการกับการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลแคชจะพร้อมใช้งานในขณะออฟไลน์ และ Firebase จะส่งการเขียนอีกครั้งเมื่อการเชื่อมต่อเครือข่ายกลับมาดีอีกครั้ง
เมื่อคุณเปิดใช้งานการคงอยู่ของดิสก์ แอพของคุณจะเขียนข้อมูลลงในอุปกรณ์เพื่อให้แอพของคุณสามารถรักษาสถานะขณะออฟไลน์ได้ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอพก็ตาม
คุณสามารถเปิดใช้งานการคงอยู่ของดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว
Kotlin+KTX
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
พฤติกรรมการคงอยู่
เมื่อเปิดใช้งานการคงอยู่ ข้อมูลใดๆ ที่ไคลเอ็นต์ฐานข้อมูล Firebase Realtime จะซิงค์ในขณะที่ออนไลน์ยังคงอยู่ในดิสก์และพร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่าแอปของคุณทำงานเหมือนกับออนไลน์โดยใช้ข้อมูลในเครื่องที่จัดเก็บไว้ในแคช การเรียกกลับของผู้ฟังจะยังคงเริ่มทำงานสำหรับการอัปเดตในเครื่อง
ไคลเอนต์ฐานข้อมูล Firebase Realtime จะเก็บคิวการดำเนินการเขียนทั้งหมดที่ทำในขณะที่แอปของคุณออฟไลน์โดยอัตโนมัติ เมื่อเปิดใช้งานการคงอยู่ คิวนี้จะคงอยู่ในดิสก์ด้วย ดังนั้นการเขียนทั้งหมดของคุณจะพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปกลับมาเชื่อมต่อได้อีกครั้ง การดำเนินการทั้งหมดจะถูกส่งไปยังเซิร์ฟเวอร์ Firebase Realtime Database
หากแอปของคุณใช้ Firebase Authentication ไคลเอ็นต์ฐานข้อมูล Firebase Realtime จะคงโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ไว้ตลอดทั้งที่แอปรีสตาร์ท หากโทเค็นการตรวจสอบสิทธิ์หมดอายุในขณะที่แอปของคุณออฟไลน์ ไคลเอนต์จะหยุดการดำเนินการเขียนชั่วคราวจนกว่าแอปของคุณจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง มิฉะนั้นการดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย
การรักษาข้อมูลให้สดใหม่
ฐานข้อมูลเรียลไทม์ Firebase ซิงโครไนซ์และจัดเก็บสำเนาข้อมูลในเครื่องสำหรับผู้ฟังที่ใช้งานอยู่ นอกจากนี้ คุณยังสามารถซิงค์สถานที่เฉพาะได้
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
ไคลเอนต์ Firebase Realtime Database จะดาวน์โหลดข้อมูลในตำแหน่งเหล่านี้โดยอัตโนมัติ และซิงค์ข้อมูลไว้แม้ว่าข้อมูลอ้างอิงจะไม่มี Listener ที่ใช้งานอยู่ก็ตาม คุณสามารถปิดการซิงโครไนซ์กลับได้ด้วยโค้ดบรรทัดต่อไปนี้
Kotlin+KTX
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
ตามค่าเริ่มต้น ข้อมูลที่ซิงค์ไว้ก่อนหน้านี้ขนาด 10MB จะถูกแคชไว้ นี่ควรจะเพียงพอสำหรับแอปพลิเคชันส่วนใหญ่ หากแคชมีขนาดเกินขนาดที่กำหนดไว้ ฐานข้อมูลเรียลไทม์ของ Firebase จะล้างข้อมูลที่มีการใช้งานล่าสุดน้อยที่สุด ข้อมูลที่ถูกซิงค์จะไม่ถูกลบออกจากแคช
การสืบค้นข้อมูลแบบออฟไลน์
ฐานข้อมูลเรียลไทม์ Firebase จัดเก็บข้อมูลที่ส่งคืนจากการสืบค้นเพื่อใช้เมื่อออฟไลน์ สำหรับการสืบค้นที่สร้างขึ้นขณะออฟไลน์ ฐานข้อมูล Firebase Realtime จะยังคงทำงานกับข้อมูลที่โหลดก่อนหน้านี้ หากยังไม่ได้โหลดข้อมูลที่ร้องขอ ฐานข้อมูลเรียลไทม์ Firebase จะโหลดข้อมูลจากแคชในเครื่อง เมื่อการเชื่อมต่อเครือข่ายพร้อมใช้งานอีกครั้ง ข้อมูลจะโหลดและจะแสดงคำถาม
ตัวอย่างเช่น โค้ดนี้จะค้นหาสี่รายการสุดท้ายในฐานข้อมูลคะแนนเรียลไทม์ของ Firebase
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
สมมติว่าผู้ใช้ขาดการเชื่อมต่อ ออฟไลน์ และรีสตาร์ทแอป ขณะที่ยังออฟไลน์ แอปจะค้นหาสองรายการสุดท้ายจากตำแหน่งเดียวกัน ข้อความค้นหานี้จะส่งคืนสองรายการสุดท้ายได้สำเร็จเนื่องจากแอปได้โหลดทั้งสี่รายการในข้อความค้นหาข้างต้น
Kotlin+KTX
scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
ในตัวอย่างก่อนหน้านี้ ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะเพิ่มเหตุการณ์ 'เพิ่มลูก' สำหรับไดโนเสาร์สองตัวที่ให้คะแนนสูงสุด โดยใช้แคชที่มีอยู่ แต่จะไม่เพิ่มเหตุการณ์ 'มูลค่า' เนื่องจากแอปไม่เคยดำเนินการค้นหานั้นขณะออนไลน์
หากแอปขอรายการหกรายการสุดท้ายขณะออฟไลน์ แอปจะได้รับเหตุการณ์ 'เพิ่มรายการย่อย' สำหรับรายการแคชทั้งสี่รายการทันที เมื่ออุปกรณ์กลับมาออนไลน์อีกครั้ง ไคลเอนต์ฐานข้อมูลเรียลไทม์ Firebase จะซิงโครไนซ์กับเซิร์ฟเวอร์และรับเหตุการณ์ 'เพิ่มลูก' และ 'มูลค่า' สองรายการสุดท้ายสำหรับแอป
การจัดการธุรกรรมแบบออฟไลน์
ธุรกรรมใด ๆ ที่ทำในขณะที่แอปออฟไลน์จะถูกจัดคิว เมื่อแอปกลับมาเชื่อมต่อเครือข่ายได้ ธุรกรรมจะถูกส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์
การจัดการสถานะ
ในแอปพลิเคชันแบบเรียลไทม์ มักจะมีประโยชน์ในการตรวจหาเมื่อไคลเอ็นต์เชื่อมต่อและยกเลิกการเชื่อมต่อ ตัวอย่างเช่น คุณอาจต้องการทำเครื่องหมายผู้ใช้เป็น 'ออฟไลน์' เมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อ
ไคลเอนต์ฐานข้อมูล Firebase มีพื้นฐานง่ายๆ ที่คุณสามารถใช้เพื่อเขียนไปยังฐานข้อมูลเมื่อไคลเอนต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ฐานข้อมูล Firebase การอัปเดตเหล่านี้เกิดขึ้นไม่ว่าไคลเอ็นต์จะยกเลิกการเชื่อมต่ออย่างสมบูรณ์หรือไม่ ดังนั้นคุณจึงสามารถพึ่งพาการอัปเดตเหล่านี้เพื่อล้างข้อมูลได้ แม้ว่าการเชื่อมต่อจะหลุดหรือไคลเอ็นต์ล้มเหลวก็ตาม การดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัพเดต และการลบ สามารถดำเนินการได้เมื่อมีการยกเลิกการเชื่อมต่อ
นี่คือตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อขาดการเชื่อมต่อโดยใช้ onDisconnect
ดั้งเดิม:
Kotlin+KTX
val presenceRef = Firebase.database.getReference("disconnectmessage") // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!")
Java
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!");
onDisconnect ทำงานอย่างไร
เมื่อคุณสร้างการดำเนินการ onDisconnect()
การดำเนินการนั้นจะคงอยู่บนเซิร์ฟเวอร์ Firebase Realtime Database เซิร์ฟเวอร์ตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนกิจกรรมที่ร้องขอได้ และแจ้งให้แอปของคุณทราบหากไม่ถูกต้อง เซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หาก ณ จุดใดการเชื่อมต่อหมดเวลาหรือปิดโดยไคลเอ็นต์ฐานข้อมูลเรียลไทม์ เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่สอง (เพื่อให้แน่ใจว่าการดำเนินการยังคงใช้งานได้) จากนั้นจึงเรียกใช้เหตุการณ์
แอปของคุณสามารถใช้การโทรกลับในการดำเนินการเขียนเพื่อให้แน่ใจว่าได้แนบ onDisconnect
อย่างถูกต้อง:
Kotlin+KTX
presenceRef.onDisconnect().removeValue { error, reference -> error?.let { Log.d(TAG, "could not establish onDisconnect event: ${error.message}") } }
Java
presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) { if (error != null) { Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage()); } } });
เหตุการณ์ onDisconnect
สามารถยกเลิกได้โดยการเรียก .cancel()
:
Kotlin+KTX
val onDisconnectRef = presenceRef.onDisconnect() onDisconnectRef.setValue("I disconnected") // ... // some time later when we change our minds // ... onDisconnectRef.cancel()
Java
OnDisconnect onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.setValue("I disconnected"); // ... // some time later when we change our minds // ... onDisconnectRef.cancel();
การตรวจจับสถานะการเชื่อมต่อ
สำหรับฟีเจอร์ที่เกี่ยวข้องกับการแสดงตนหลายอย่าง จะมีประโยชน์สำหรับแอปของคุณที่จะรู้ว่าแอปออนไลน์หรือออฟไลน์เมื่อใด ฐานข้อมูลเรียลไทม์ Firebase จัดเตรียมตำแหน่งพิเศษที่ /.info/connected
ซึ่งได้รับการอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase เปลี่ยนแปลง นี่คือตัวอย่าง:
Kotlin+KTX
val connectedRef = Firebase.database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue(Boolean::class.java) ?: false if (connected) { Log.d(TAG, "connected") } else { Log.d(TAG, "not connected") } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { Log.d(TAG, "connected"); } else { Log.d(TAG, "not connected"); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
/.info/connected
เป็นค่าบูลีนที่ไม่ได้รับการซิงโครไนซ์ระหว่างไคลเอนต์ฐานข้อมูลเรียลไทม์ เนื่องจากค่าจะขึ้นอยู่กับสถานะของไคลเอนต์ กล่าวอีกนัยหนึ่ง หากไคลเอนต์รายหนึ่งอ่าน /.info/connected
เป็นเท็จ นี่ไม่รับประกันว่าไคลเอนต์ที่แยกต่างหากจะอ่านเท็จเช่นกัน
บน Android นั้น Firebase จะจัดการสถานะการเชื่อมต่อโดยอัตโนมัติเพื่อลดการใช้แบนด์วิดท์และแบตเตอรี่ เมื่อไคลเอ็นต์ไม่มี Listener ที่ใช้งานอยู่ ไม่มีการดำเนินการเขียนหรือ onDisconnect
ที่รอดำเนินการ และไม่ได้ยกเลิกการเชื่อมต่ออย่างชัดเจนโดยวิธี goOffline
Firebase จะปิดการเชื่อมต่อหลังจากไม่มีการใช้งานเป็นเวลา 60 วินาที
การจัดการกับความหน่วงแฝง
การประทับเวลาของเซิร์ฟเวอร์
เซิร์ฟเวอร์ฐานข้อมูล Firebase Realtime มีกลไกในการแทรกการประทับเวลาที่สร้างบนเซิร์ฟเวอร์เป็นข้อมูล คุณลักษณะนี้เมื่อรวมกับ onDisconnect
จะให้วิธีง่ายๆ ในการจดบันทึกเวลาที่ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ตัดการเชื่อมต่ออย่างน่าเชื่อถือ:
Kotlin+KTX
val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline") userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)
Java
DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
นาฬิกาเอียง
แม้ว่า firebase.database.ServerValue.TIMESTAMP
จะมีความแม่นยำมากกว่ามากและเหมาะสำหรับการดำเนินการอ่าน/เขียนส่วนใหญ่ แต่บางครั้งอาจมีประโยชน์ในการประมาณค่าคล็อกนาฬิกาของไคลเอ็นต์โดยสัมพันธ์กับเซิร์ฟเวอร์ของฐานข้อมูล Firebase Realtime Database คุณสามารถแนบการโทรกลับไปยังตำแหน่ง /.info/serverTimeOffset
เพื่อรับค่าเป็นมิลลิวินาที ที่ไคลเอ็นต์ฐานข้อมูล Firebase Realtime เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลายุคในหน่วยมิลลิวินาที) เพื่อประมาณเวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของการชดเชยนี้อาจได้รับผลกระทบจากเวลาแฝงของเครือข่าย และมีประโยชน์ในการค้นหาความคลาดเคลื่อนขนาดใหญ่ (> 1 วินาที) ในเวลาสัญญาณนาฬิกาเป็นหลัก
Kotlin+KTX
val offsetRef = Firebase.database.getReference(".info/serverTimeOffset") offsetRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val offset = snapshot.getValue(Double::class.java) ?: 0.0 val estimatedServerTimeMs = System.currentTimeMillis() + offset } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset"); offsetRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { double offset = snapshot.getValue(Double.class); double estimatedServerTimeMs = System.currentTimeMillis() + offset; } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
แอปการแสดงตนตัวอย่าง
ด้วยการรวมการดำเนินการยกเลิกการเชื่อมต่อเข้ากับการตรวจสอบสถานะการเชื่อมต่อและการประทับเวลาของเซิร์ฟเวอร์ คุณสามารถสร้างระบบการแสดงตนของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละคนจัดเก็บข้อมูลไว้ที่ตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ฐานข้อมูลเรียลไทม์ออนไลน์อยู่หรือไม่ ไคลเอนต์ตั้งค่าตำแหน่งนี้เป็นจริงเมื่อพวกเขาออนไลน์และประทับเวลาเมื่อพวกเขายกเลิกการเชื่อมต่อ การประทับเวลานี้ระบุครั้งสุดท้ายที่ผู้ใช้รายนั้นออนไลน์
โปรดทราบว่าแอปของคุณควรจัดคิวการดำเนินการตัดการเชื่อมต่อก่อนที่ผู้ใช้จะถูกทำเครื่องหมายออนไลน์ เพื่อหลีกเลี่ยงสภาวะการแข่งขันในกรณีที่การเชื่อมต่อเครือข่ายของลูกค้าขาดหายไปก่อนที่ทั้งสองคำสั่งจะถูกส่งไปยังเซิร์ฟเวอร์
นี่คือระบบการแสดงตนของผู้ใช้อย่างง่าย:
Kotlin+KTX
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline val database = Firebase.database val myConnectionsRef = database.getReference("users/joe/connections") // Stores the timestamp of my last disconnect (the last time I was seen online) val lastOnlineRef = database.getReference("/users/joe/lastOnline") val connectedRef = database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue<Boolean>() ?: false if (connected) { val con = myConnectionsRef.push() // When this device disconnects, remove it con.onDisconnect().removeValue() // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP) // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(java.lang.Boolean.TRUE) } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled at .info/connected") } })
Java
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline final FirebaseDatabase database = FirebaseDatabase.getInstance(); final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections"); // Stores the timestamp of my last disconnect (the last time I was seen online) final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline"); final DatabaseReference connectedRef = database.getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { DatabaseReference con = myConnectionsRef.push(); // When this device disconnects, remove it con.onDisconnect().removeValue(); // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP); // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(Boolean.TRUE); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled at .info/connected"); } });