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

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

ความคงอยู่ของดิสก์

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

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

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

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

พฤติกรรมการคงอยู่

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

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

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

การรักษาข้อมูลให้สดใหม่

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

Java

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

Kotlin+KTX

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

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

Java

scoresRef.keepSynced(false);

Kotlin+KTX

scoresRef.keepSynced(false)

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

การสืบค้นข้อมูลออฟไลน์

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

ตัวอย่างเช่น โค้ดนี้จะค้นหาสี่รายการสุดท้ายในฐานข้อมูลคะแนนเรียลไทม์ของ Firebase

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

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

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

    // ...
});

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

    // ...
})

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

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

การจัดการธุรกรรมออฟไลน์

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

การจัดการการแสดงตน

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

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

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

Java

DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");

Kotlin+KTX

val presenceRef = Firebase.database.getReference("disconnectmessage")
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!")

วิธี onDisconnect ทำงาน

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

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

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

Kotlin+KTX

presenceRef.onDisconnect().removeValue { error, reference ->
    error?.let {
        Log.d(TAG, "could not establish onDisconnect event: ${error.message}")
    }
}

เหตุการณ์ onDisconnect สามารถยกเลิกได้โดยการโทร .cancel() :

Java

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.setValue("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

Kotlin+KTX

val onDisconnectRef = presenceRef.onDisconnect()
onDisconnectRef.setValue("I disconnected")
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel()

กำลังตรวจจับสถานะการเชื่อมต่อ

สำหรับฟีเจอร์ที่เกี่ยวข้องกับการแสดงตนจำนวนมาก แอปของคุณจะรู้ว่าแอปออนไลน์หรือออฟไลน์เมื่อใดจะมีประโยชน์ ฐานข้อมูลเรียลไทม์ของ Firebase มีตำแหน่งพิเศษที่ /.info/connected ซึ่งอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ Firebase Realtime Database เปลี่ยนแปลง นี่คือตัวอย่าง:

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

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

/.info/connected เป็นค่าบูลีนที่ไม่ได้ซิงโครไนซ์ระหว่างไคลเอนต์ Realtime Database เนื่องจากค่าจะขึ้นอยู่กับสถานะของไคลเอนต์ กล่าวอีกนัยหนึ่ง ถ้าไคลเอนต์หนึ่งอ่าน /.info/connected เป็นเท็จ ก็ไม่รับประกันว่าไคลเอนต์แยกต่างหากจะอ่านเท็จด้วย

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

เวลาในการตอบสนองการจัดการ

เวลาประทับของเซิร์ฟเวอร์

เซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์ของ Firebase มีกลไกในการแทรกการประทับเวลาที่สร้างบนเซิร์ฟเวอร์เป็นข้อมูล คุณลักษณะนี้เมื่อรวมกับ onDisconnect จะเป็นวิธีที่ง่ายในการจดบันทึกเวลาที่ไคลเอ็นต์ Realtime Database ยกเลิกการเชื่อมต่อ:

Java

DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

Kotlin+KTX

val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline")
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

นาฬิกาเอียง

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

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

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

ตัวอย่างการแสดงตน App

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

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

นี่คือระบบการแสดงตนของผู้ใช้อย่างง่าย:

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(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(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

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