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

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

ความต่อเนื่องของดิสก์

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

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

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

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

พฤติกรรมอย่างต่อเนื่อง

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

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

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

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

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

ต่อไปนี้เป็นระบบการตรวจหาผู้ใช้แบบง่าย:

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