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