แอปพลิเคชัน Firebase จะใช้งานได้แม้ว่าแอปของคุณจะสูญเสียการเชื่อมต่อเครือข่ายชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับการยืนยันข้อมูลภายในเครื่อง การจัดการการแสดงข้อมูล และการจัดการเวลาในการตอบสนองด้วย
ความต่อเนื่องของดิสก์
แอป Firebase จะจัดการการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลแคชจะพร้อมใช้งานขณะออฟไลน์และ Firebase จะส่งการเขียนซ้ำเมื่อการเชื่อมต่อเครือข่ายกลับมาทำงานอีกครั้ง
เมื่อเปิดใช้การคงอยู่ของดิสก์ แอปจะเขียนข้อมูลลงในอุปกรณ์ภายใน เพื่อให้แอปรักษาสถานะไว้ขณะออฟไลน์ได้ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม
คุณเปิดใช้การคงอยู่ของดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว
Kotlin+KTX
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
พฤติกรรมอย่างต่อเนื่อง
เมื่อเปิดใช้การถาวร ข้อมูลทั้งหมดที่ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะซิงค์ขณะที่ออนไลน์ยังอยู่บนดิสก์และใช้งานแบบออฟไลน์ได้ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอป ซึ่งหมายความว่าแอปจะทำงานเหมือนกับที่ออนไลน์อยู่โดยใช้ข้อมูลในเครื่องที่เก็บไว้ในแคช Callback ของผู้ฟังจะยังคงเริ่มทำงานต่อไปเพื่ออัปเดตในพื้นที่
ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะเก็บคิวการดำเนินการเขียนทั้งหมดที่ดำเนินการขณะที่แอปออฟไลน์โดยอัตโนมัติ เมื่อเปิดใช้การถาวรแล้ว คิวนี้จะยังคงอยู่ในดิสก์ด้วยเพื่อให้การเขียนทั้งหมดพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปกลับมาเชื่อมต่ออีกครั้ง ระบบจะส่งการดำเนินการทั้งหมดไปยังเซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์ของ Firebase
หากแอปใช้การตรวจสอบสิทธิ์ของ Firebase ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะเก็บโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ไว้เมื่อรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุขณะที่แอปออฟไลน์อยู่ ไคลเอ็นต์จะหยุดการเขียนชั่วคราวจนกว่าแอปจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง มิฉะนั้นการดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย
อัปเดตข้อมูลให้ใหม่อยู่เสมอ
ฐานข้อมูลเรียลไทม์ของ Firebase จะซิงค์ข้อมูลและจัดเก็บสำเนาข้อมูลในเครื่องสำหรับผู้ฟังที่ใช้งานอยู่ นอกจากนี้ ยังซิงค์สถานที่ตั้งบางแห่งได้ด้วย
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะดาวน์โหลดข้อมูลในตำแหน่งเหล่านี้โดยอัตโนมัติและซิงค์อยู่เสมอแม้ว่าข้อมูลอ้างอิงจะไม่มี Listener ที่ใช้งานอยู่ก็ตาม คุณปิดการซิงค์อีกครั้งได้โดยใช้โค้ดต่อไปนี้
Kotlin+KTX
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
โดยค่าเริ่มต้น ระบบจะแคชข้อมูลที่ซิงค์ไว้ก่อนหน้านี้ 10 MB ซึ่งน่าจะเพียงพอสำหรับแอปพลิเคชันส่วนใหญ่ หากแคชมีขนาดเกินกว่าที่กำหนดค่าไว้ ฐานข้อมูลเรียลไทม์ของ Firebase จะล้างข้อมูลที่มีการใช้งานล่าสุดเมื่อเร็วๆ นี้ออกอย่างถาวร โดยข้อมูลที่ซิงค์ไว้จะไม่ถูกลบออกจากแคชถาวร
การค้นหาข้อมูลแบบออฟไลน์
ฐานข้อมูลเรียลไทม์ของ Firebase จะจัดเก็บข้อมูลที่แสดงผลจากการค้นหาไว้สำหรับการใช้งานเมื่อออฟไลน์ สำหรับการค้นหาที่สร้างขึ้นในขณะออฟไลน์ ฐานข้อมูลเรียลไทม์ของ Firebase จะยังคงทำงานต่อไปสำหรับข้อมูลที่โหลดก่อนหน้านี้ หากข้อมูลที่ขอไม่โหลด ฐานข้อมูลเรียลไทม์ของ Firebase จะโหลดข้อมูลจากแคชในเครื่อง เมื่อเชื่อมต่อเครือข่ายได้อีกครั้ง ข้อมูลจะโหลดและจะแสดงการค้นหา
ตัวอย่างเช่น โค้ดนี้จะค้นหา 4 รายการสุดท้ายในฐานข้อมูลคะแนนแบบเรียลไทม์ของ 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()); } // ... });
สมมติว่าผู้ใช้ขาดการเชื่อมต่อ ออฟไลน์ และรีสตาร์ทแอป ขณะที่ยังออฟไลน์ แอปจะค้นหาสำหรับ 2 รายการสุดท้ายจาก ตำแหน่งเดียวกัน การค้นหานี้จะแสดง 2 รายการสุดท้ายได้สำเร็จเนื่องจากแอปโหลดทั้ง 4 รายการในการค้นหาด้านบนแล้ว
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 เพิ่มเหตุการณ์ "เพิ่มย่อย" สำหรับไดโนเสาร์ 2 ตัวที่มีคะแนนสูงสุดโดยใช้แคชที่เก็บไว้ แต่จะไม่ทำให้เกิดเหตุการณ์ "value" เนื่องจากแอปไม่เคยเรียกใช้การค้นหานั้นขณะออนไลน์
หากแอปมีการขอรายการ 6 รายการล่าสุดขณะออฟไลน์ ก็จะได้รับเหตุการณ์ "เพิ่มรายการย่อย" สำหรับรายการที่แคชไว้ 4 รายการทันที เมื่ออุปกรณ์กลับมาออนไลน์อีกครั้ง ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะซิงค์กับเซิร์ฟเวอร์และรับ 2 เหตุการณ์สุดท้ายที่เป็น "รายการย่อย" และเหตุการณ์ "value" สำหรับแอป
การจัดการธุรกรรมแบบออฟไลน์
ธุรกรรมที่เกิดขึ้นขณะที่แอปออฟไลน์จะอยู่ในคิว เมื่อแอปเชื่อมต่อเครือข่ายได้อีกครั้ง ระบบจะส่งธุรกรรมไปยังเซิร์ฟเวอร์ Realtime Database
การจัดการการตรวจหาบุคคล
ในแอปพลิเคชันแบบเรียลไทม์ บ่อยครั้งที่การตรวจพบเวลาที่ไคลเอ็นต์ เชื่อมต่อและยกเลิกการเชื่อมต่อ เช่น คุณอาจต้องการทำเครื่องหมายผู้ใช้ว่า "ออฟไลน์" เมื่อลูกค้ายกเลิกการเชื่อมต่อ
ไคลเอ็นต์ฐานข้อมูลของ 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()
การดำเนินการนั้นจะอยู่ในเซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์ของ Firebase เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้จะดำเนินการตามกิจกรรมการเขียนที่ขอได้ และจะแจ้งแอปของคุณหากแอปไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หากเมื่อใดก็ตามที่การเชื่อมต่อหมดเวลา หรือไคลเอ็นต์ Realtime Database ปิดอยู่ เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่ 2 (เพื่อให้แน่ใจว่าการดำเนินการยังคงใช้งานได้) จากนั้นเรียกใช้เหตุการณ์
แอปของคุณสามารถใช้ Callback ในการดำเนินการเขียนเพื่อให้แน่ใจว่ามีการแนบ 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 Realtime Database มีการเปลี่ยนแปลง มีตัวอย่างดังต่อไปนี้
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
เป็นค่าบูลีนที่ไม่ซิงค์ระหว่างไคลเอ็นต์ Realtime Database เนื่องจากค่าจะขึ้นอยู่กับสถานะของไคลเอ็นต์ กล่าวคือ หากไคลเอ็นต์หนึ่งอ่านค่า /.info/connected
เป็น "เท็จ" ก็ไม่ได้รับประกันว่าไคลเอ็นต์อีกรายการจะอ่านค่า "เท็จ" ด้วย
Firebase ใน Android จะจัดการสถานะการเชื่อมต่อโดยอัตโนมัติเพื่อลดการใช้แบนด์วิดท์และแบตเตอรี่ เมื่อไคลเอ็นต์ไม่มี Listener ที่ใช้งานอยู่ ไม่มีการดำเนินการเขียนหรือ onDisconnect
ที่รอดำเนินการ และไม่ได้ยกเลิกการเชื่อมต่ออย่างชัดเจนโดยเมธอด goOffline
Firebase จะปิดการเชื่อมต่อหลังจากไม่มีการใช้งานเป็นเวลา 60 วินาที
การจัดการเวลาในการตอบสนอง
การประทับเวลาของเซิร์ฟเวอร์
เซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์ของ Firebase มีกลไกในการแทรกการประทับเวลาที่สร้างขึ้นในเซิร์ฟเวอร์เป็นข้อมูล ฟีเจอร์นี้เมื่อรวมกับ onDisconnect
จะเป็นวิธีง่ายๆ ในการจดบันทึกเวลาที่ไคลเอ็นต์ Realtime Database ยกเลิกการเชื่อมต่ออย่างน่าเชื่อถือ
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 คุณสามารถแนบ Callback กับตําแหน่ง /.info/serverTimeOffset
เพื่อรับค่าเป็นมิลลิวินาทีที่ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase เพิ่มไปยังเวลาที่รายงานในเครื่อง (เวลา Epoch ในหน่วยมิลลิวินาที) เพื่อประมาณเวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของออฟเซ็ตนี้อาจได้รับผลกระทบจากเวลาในการตอบสนองของเครือข่าย ดังนั้นจึงมีประโยชน์สำหรับการค้นหาความคลาดเคลื่อนขนาดใหญ่ (มากกว่า 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"); } });
แอปตัวอย่างการตรวจหาบุคคล
คุณสร้างระบบการตรวจหาบุคคลในบ้านของผู้ใช้ได้โดยการรวมการดำเนินการยกเลิกการเชื่อมต่อเข้ากับการตรวจสอบสถานะการเชื่อมต่อและการประทับเวลาของเซิร์ฟเวอร์ ในระบบนี้ ผู้ใช้แต่ละรายจะจัดเก็บข้อมูลที่ตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ Realtime Database ออนไลน์อยู่หรือไม่ ไคลเอ็นต์จะตั้งค่าตำแหน่งนี้เป็น "จริง" เมื่อออนไลน์และการประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้แสดงถึงเวลาล่าสุดที่ผู้ใช้ที่ระบุออนไลน์
โปรดทราบว่าแอปของคุณควรจัดคิวการดำเนินการยกเลิกการเชื่อมต่อก่อนที่จะมีการทำเครื่องหมายผู้ใช้ออนไลน์ เพื่อหลีกเลี่ยงเงื่อนไขการแข่งขันในกรณีที่การเชื่อมต่อเครือข่ายของไคลเอ็นต์ขาดหายก่อนที่จะส่งทั้ง 2 คำสั่งไปยังเซิร์ฟเวอร์
ต่อไปนี้เป็นระบบการตรวจหาผู้ใช้แบบง่าย:
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"); } });