การเปิดใช้ความสามารถออฟไลน์บน Android

แอปพลิเคชัน Firebase จะทำงานได้แม้ว่าแอปจะสูญเสียการเชื่อมต่อเครือข่ายชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับจัดเก็บข้อมูลในเครื่องอย่างถาวร จัดการสถานะออนไลน์ และจัดการเวลาในการตอบสนอง

ความคงทนของดิสก์

แอป Firebase จะจัดการการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลที่แคชไว้จะพร้อมใช้งานขณะออฟไลน์ และ Firebase จะส่งการเขียนซ้ำ เมื่อมีการเชื่อมต่อเครือข่ายอีกครั้ง

เมื่อเปิดใช้การคงอยู่ของดิสก์ แอปจะเขียนข้อมูลในเครื่องไปยัง อุปกรณ์เพื่อให้แอปคงสถานะไว้ได้ขณะออฟไลน์ แม้ว่าผู้ใช้ หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม

คุณเปิดใช้การคงอยู่ของดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว

Kotlin

Firebase.database.setPersistenceEnabled(true)

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

ลักษณะการทำงานของความต่อเนื่อง

การเปิดใช้การคงอยู่จะทำให้ข้อมูลใดก็ตามที่ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ขณะออนไลน์ยังคงอยู่ในดิสก์และพร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่าแอปจะทำงานเหมือนกับตอนออนไลน์โดยใช้ข้อมูลในเครื่องที่จัดเก็บไว้ในแคช การเรียกกลับของผู้ฟังจะยังคงทริกเกอร์สำหรับการอัปเดตในเครื่องต่อไป

Firebase Realtime Databaseไคลเอ็นต์จะเก็บคิวของการดำเนินการเขียนทั้งหมดโดยอัตโนมัติ ซึ่งดำเนินการขณะที่แอปออฟไลน์ เมื่อเปิดใช้การคงอยู่ ระบบจะคงคิวนี้ไว้ในดิสก์ด้วยเพื่อให้การเขียนทั้งหมดพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปกลับมาเชื่อมต่อได้อีกครั้ง ระบบจะส่งการดำเนินการทั้งหมดไปยังเซิร์ฟเวอร์ Firebase Realtime Database

หากแอปใช้การตรวจสอบสิทธิ์ Firebase ไคลเอ็นต์ Firebase Realtime Database จะเก็บโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ ไว้เมื่อรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุขณะที่แอปออฟไลน์ ไคลเอ็นต์จะหยุดการดำเนินการเขียนชั่วคราวจนกว่าแอปจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง ไม่เช่นนั้นการดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย

การอัปเดตข้อมูลอยู่เสมอ

โดย Firebase Realtime Database จะซิงค์และจัดเก็บสำเนาข้อมูลในเครื่อง สำหรับผู้ฟังที่ใช้งานอยู่ นอกจากนี้ คุณยังซิงค์สถานที่ตั้งที่เฉพาะเจาะจงได้ด้วย

Kotlin

val scoresRef = Firebase.database.getReference("scores")
scoresRef.keepSynced(true)

Java

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);

ไคลเอ็นต์ Firebase Realtime Database จะดาวน์โหลดข้อมูลโดยอัตโนมัติที่ ตำแหน่งเหล่านี้และซิงค์ข้อมูลไว้แม้ว่าการอ้างอิงจะไม่มี ผู้ฟังที่ใช้งานอยู่ก็ตาม คุณปิดการซิงค์อีกครั้งได้ด้วยบรรทัดโค้ดต่อไปนี้

Kotlin

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

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

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 มี Primitive อย่างง่ายที่คุณใช้เขียนไปยังฐานข้อมูลได้เมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ฐานข้อมูล Firebase การอัปเดตเหล่านี้จะเกิดขึ้นไม่ว่าไคลเอ็นต์จะยกเลิกการเชื่อมต่ออย่างถูกต้องหรือไม่ ดังนั้นคุณจึงใช้การอัปเดตเหล่านี้เพื่อล้างข้อมูลได้แม้ว่าการเชื่อมต่อจะขาดหาย หรือไคลเอ็นต์จะขัดข้องก็ตาม การดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัปเดต และการนำออก สามารถดำเนินการได้เมื่อมีการยกเลิกการเชื่อมต่อ

ต่อไปนี้คือตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อยกเลิกการเชื่อมต่อโดยใช้ onDisconnect ดั้งเดิม

Kotlin

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 เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนเหตุการณ์ที่ขอได้ และจะแจ้งให้แอปของคุณทราบหากไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์จะ ตรวจสอบการเชื่อมต่อ หากการเชื่อมต่อหมดเวลาหรือRealtime Databaseไคลเอ็นต์ปิดการเชื่อมต่อโดยตรง เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่ 2 (เพื่อให้แน่ใจว่าการดำเนินการยังคงใช้ได้) จากนั้นจะเรียกใช้ เหตุการณ์

แอปของคุณสามารถใช้การเรียกกลับในการดำเนินการเขียน เพื่อให้แน่ใจว่าได้แนบ onDisconnect อย่างถูกต้อง

Kotlin

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

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

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

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);

Clock Skew

แม้ว่า firebase.database.ServerValue.TIMESTAMP จะมีความแม่นยำมากกว่า และเหมาะสำหรับการอ่าน/เขียนส่วนใหญ่ แต่บางครั้งการประมาณค่าความคลาดเคลื่อนของนาฬิกาไคลเอ็นต์ เมื่อเทียบกับเซิร์ฟเวอร์ของ Firebase Realtime Database ก็อาจมีประโยชน์ คุณ สามารถแนบฟังก์ชันเรียกกลับไปยังตำแหน่ง /.info/serverTimeOffset เพื่อรับค่าเป็นมิลลิวินาทีที่ไคลเอ็นต์ Firebase Realtime Database เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลา Epoch เป็นมิลลิวินาที) เพื่อประมาณ เวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของออฟเซ็ตนี้อาจได้รับผลกระทบจาก เวลาในการตอบสนองของเครือข่าย ดังนั้นจึงมีประโยชน์หลักๆ ในการค้นหา ความคลาดเคลื่อนขนาดใหญ่ (> 1 วินาที) ในเวลาของนาฬิกา

Kotlin

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");
    }
});

แอป Presence ตัวอย่าง

การรวมการดำเนินการยกเลิกการเชื่อมต่อกับการตรวจสอบสถานะการเชื่อมต่อและ การประทับเวลาของเซิร์ฟเวอร์จะช่วยให้คุณสร้างระบบสถานะของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละรายจะจัดเก็บข้อมูลไว้ที่ตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ Realtime Database ออนไลน์หรือไม่ ไคลเอ็นต์จะตั้งค่าตำแหน่งนี้เป็น "จริง" เมื่อ ออนไลน์ และตั้งค่าการประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้ ระบุเวลาล่าสุดที่ผู้ใช้ออนไลน์

โปรดทราบว่าแอปควรจัดคิวการดำเนินการยกเลิกการเชื่อมต่อก่อนที่จะ ทำเครื่องหมายผู้ใช้ออนไลน์ เพื่อหลีกเลี่ยงการแข่งขันในกรณีที่ไคลเอ็นต์ สูญเสียการเชื่อมต่อเครือข่ายก่อนที่จะส่งทั้ง 2 คำสั่งไปยังเซิร์ฟเวอร์ได้

ระบบการแสดงข้อมูลของผู้ใช้แบบง่ายมีดังนี้

Kotlin

// 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");
    }
});