הפעלת יכולות לא מקוונות באנדרואיד

יישומי Firebase עובדים גם אם האפליקציה שלך מאבדת את חיבור הרשת באופן זמני. בנוסף, Firebase מספק כלים להתמשכות נתונים מקומית, לניהול נוכחות וטיפול בחביון.

התמדה בדיסק

אפליקציות Firebase מטפלות באופן אוטומטי בהפרעות רשת זמניות. נתונים במטמון זמינים כשהם לא מקוונים ו- Firebase שולח מחדש כל כתיבה כאשר קישוריות הרשת משוחזרת.

כאשר אתה מפעיל התמדה לדיסק, האפליקציה שלך כותבת את הנתונים באופן מקומי למכשיר כך שהאפליקציה תוכל לשמור על מצב במצב לא מקוון, גם אם המשתמש או מערכת ההפעלה מפעילים את האפליקציה מחדש.

אתה יכול לאפשר התמדה בדיסק רק בשורת קוד אחת.

ג'אווה

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

קוטלין+KTX

Firebase.database.setPersistenceEnabled(true)

התנהגות התמדה

על ידי הפעלת התמדה, כל הנתונים שלקוח מסד הנתונים בזמן אמת של Firebase יסנכרן בזמן שהאינטרנט ממשיך לדיסק וזמין במצב לא מקוון, גם כאשר המשתמש או מערכת ההפעלה מפעילים את האפליקציה מחדש. המשמעות היא שהאפליקציה שלך פועלת כפי שהייתה מקוונת באמצעות הנתונים המקומיים המאוחסנים במטמון. שיחות החזרה למאזינים ימשיכו לירות לעדכונים מקומיים.

לקוח Firebase Realtime Database שומר אוטומטית על תור של כל פעולות הכתיבה המתבצעות כשהאפליקציה שלך במצב לא מקוון. כאשר ההתמדה מופעלת, התור הזה ממשיך גם לדיסק כך שכל הכתיבות שלך יהיו זמינות כאשר המשתמש או מערכת ההפעלה מפעילים את האפליקציה מחדש. כאשר האפליקציה מחזירה את הקישוריות, כל הפעולות נשלחות לשרת מסד הנתונים בזמן אמת של Firebase.

אם האפליקציה משתמשת אימות Firebase , הלקוח מסד Firebase זמן אמת נמשכת אימות של המשתמש האסימון בכל פעם שמתבצע אתחול האפליקציה. אם פג תוקפו של אסימון האימות בזמן שהאפליקציה שלך במצב לא מקוון, הלקוח מושהה פעולות כתיבה עד שהאפליקציה שלך מאמתת את המשתמש מחדש, אחרת פעולות הכתיבה עלולות להיכשל עקב כללי אבטחה.

שמירה על טריות הנתונים

מסד הנתונים בזמן אמת של Firebase מסנכרן ושומר עותק מקומי של הנתונים עבור מאזינים פעילים. בנוסף, תוכל לשמור מיקומים ספציפיים מסונכרנים.

ג'אווה

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

קוטלין+KTX

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

לקוח מסד הנתונים בזמן אמת של Firebase מוריד את הנתונים באופן אוטומטי במיקומים אלה ושומר אותו מסונכרן גם אם אין להפניה פעיל מאזינים פעילים. אתה יכול לבטל את הסנכרון מחדש עם שורת הקוד הבאה.

ג'אווה

scoresRef.keepSynced(false);

קוטלין+KTX

scoresRef.keepSynced(false)

כברירת מחדל, 10MB של נתונים מסונכרנים בעבר נשמרים במטמון. זה אמור להספיק לרוב היישומים. אם המטמון גדל מהגודל המוגדר שלו, מסד הנתונים בזמן אמת של Firebase יטהר נתונים שהיו בשימוש לפחות לאחרונה. נתונים שנשמרים מסונכרנים אינם נמחקים מהמטמון.

שאילתת נתונים לא מקוונת

מסד הנתונים בזמן אמת של Firebase מאחסן נתונים המוחזרים משאילתה לשימוש במצב לא מקוון. לשאילתות שנבנו במצב לא מקוון, מסד הנתונים בזמן אמת של Firebase ממשיך לפעול עבור נתונים שהוטענו בעבר. אם הנתונים המבוקשים לא נטענו, מסד הנתונים בזמן אמת של Firebase טוען נתונים מהמטמון המקומי. כאשר קישוריות הרשת זמינה שוב, הנתונים נטענים וישקפו את השאילתה.

לדוגמה, קוד זה שואל את ארבעת הפריטים האחרונים במאגר ציונים בזמן אמת של Firebase

ג'אווה

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

    // ...
});

קוטלין+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}")
    }

    // ...
})

נניח שהמשתמש מאבד את החיבור, יוצא לא מקוון ומפעיל מחדש את האפליקציה. כשהיא עדיין במצב לא מקוון, האפליקציה שואלת את שני הפריטים האחרונים מאותו מיקום. שאילתה זו תחזיר בהצלחה את שני הפריטים האחרונים מכיוון שהאפליקציה טענה את כל ארבעת הפריטים בשאילתה למעלה.

ג'אווה

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

    // ...
});

קוטלין+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 Realtime Database מעלה אירועי 'הוספת ילדים' עבור שני הדינוזאורים בעלי הציונים הגבוהים ביותר, באמצעות המטמון המתמשך. אבל זה לא יעלה אירוע 'ערך', מכיוון שהאפליקציה מעולם לא ביצעה את השאילתה הזו כשהיא מקוונת.

אם היישום היה מבקש את ששת הפריטים האחרונים במצב לא מקוון, היא הייתה מקבלת אירועי 'הוספת ילד' לארבעת הפריטים השמורים מייד. כאשר המכשיר חוזר לרשת, לקוח מסד הנתונים של Firebase Realtime מסונכרן עם השרת ומקבל את שני אירועי ה 'הילד' והאירועים 'הערך' לאפליקציה.

טיפול בעסקאות לא מקוונות

כל עסקאות המתבצעות כשהאפליקציה במצב לא מקוון, עומדות בתור. ברגע שהאפליקציה תחזיר לעצמה את קישוריות הרשת, העסקאות נשלחות לשרת מסד הנתונים בזמן אמת.

ניהול נוכחות

ביישומים בזמן אמת לעתים קרובות שימושי לזהות מתי לקוחות מתחברים ומתנתקים. לדוגמה, ייתכן שתרצה לסמן משתמש כ'לא מקוון 'כאשר הלקוח שלו מתנתק.

לקוחות מסד הנתונים של Firebase מספקים פרימיטיבים פשוטים שבאמצעותם תוכל לכתוב למסד הנתונים כאשר לקוח מתנתק משרתי מסד הנתונים של Firebase. עדכונים אלה מתרחשים בין אם הלקוח מתנתק בצורה נקייה ובין אם לאו, כך שתוכל לסמוך עליהם כדי לנקות נתונים גם אם החיבור יופסק או שהלקוח קורס. כל פעולות הכתיבה, כולל הגדרה, עדכון והסרה, ניתנות לביצוע לאחר ניתוק.

הנה דוגמה פשוטה של כתיבת נתונים על ניתוק באמצעות onDisconnect פרימיטיבית:

ג'אווה

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 זמן האמת. השרת בודק את האבטחה כדי לוודא שהמשתמש יכול לבצע את אירוע הכתיבה המבוקש, ומודיע לאפליקציה שלך אם היא לא חוקית. השרת עוקב אחר החיבור. אם בשלב מסוים החיבור פסק זמן, או נסגר באופן פעיל על ידי לקוח מסד הנתונים בזמן אמת, השרת בודק את האבטחה בפעם השנייה (כדי לוודא שהפעולה עדיין בתוקף) ולאחר מכן מפעיל את האירוע.

היישום יכול להשתמש התקשרות על פעולת הכתיבה כדי להבטיח את onDisconnect צורף נכון:

ג'אווה

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

קוטלין+KTX

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

onDisconnect האירוע יכול גם יבוטל על ידי התקשרות .cancel() :

ג'אווה

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

קוטלין+KTX

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

זיהוי מצב חיבור

לתכונות רבות הקשורות לנוכחות, כדאי לאפליקציה שלך לדעת מתי היא מקוונת או לא מקוונת. מסד Firebase זמן אמת מספק מיקום מיוחד /.info/connected המתעדכן כל הזמן משתנה מצב החיבור של הלקוח מסד Firebase זמן אמת. הנה דוגמה:

ג'אווה

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

קוטלין+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 הוא ערך בוליאני אשר אינו מסונכרן בין לקוחות מסד זמן אמת בגלל הערך תלוי במצב של הלקוח. במילים אחרות, אם לקוח אחד לא קורא /.info/connected כמו שקר, זו אינה ערובה כי לקוח נפרד גם יקראו שווא.

ב- Android, Firebase מנהל באופן אוטומטי את מצב החיבור כדי להפחית את רוחב הפס ואת צריכת הסוללה. כאשר לקוח אין מאזינים פעילים, שום דבר כתוב ממתין או onDisconnect פעולות, והוא לא מתנתק במפורש על ידי goOffline השיטה, Firebase סוגר את החיבור לאחר 60 שניות של חוסר פעילות.

טיפול באיחור

חותמות זמן של השרת

שרתי מסד הנתונים של Firebase Realtime מספקים מנגנון להוספת חותמות זמן שנוצרות בשרת כנתונים. תכונה זו, בשילוב עם onDisconnect , מספקת דרך קלה לעשות מהימנה לב לשעה שבה לקוח מסד זמן אמת מנותק:

ג'אווה

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

קוטלין+KTX

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

להטות שעון

בעוד firebase.database.ServerValue.TIMESTAMP הוא הרבה יותר מדויק, ועדיפה על פעולות קריאה / כתיבה ביותר, זה יכול להיות שימושי מדי פעם כדי להעריך את השעון הלקוח הטייה ביחס לשרתים של מסד Firebase זמן אמת. ניתן לצרף התקשרות למיקום /.info/serverTimeOffset להשיג את הערך, באלפיות השנייה, כי Firebase זמן אמת לקוחות מסד להוסיף זמן מקומי דיווח (זמן עידן באלפיות השנייה) כדי להעריך את הזמן בשרת. שים לב שדיוק הקיזוז הזה יכול להיות מושפע מאיחור ברשת, ולכן הוא שימושי בעיקר לגילוי פערים גדולים (> שנייה אחת) בזמן השעון.

ג'אווה

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

קוטלין+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")
    }
})

אפליקציית נוכחות לדוגמא

על ידי שילוב פעולות ניתוק עם ניטור מצב חיבור וחותמות זמן של שרת, תוכל לבנות מערכת נוכחות של משתמשים. במערכת זו, כל משתמש מאחסן נתונים במיקום מסד נתונים כדי לציין אם לקוח מסד נתונים בזמן אמת מחובר או לא. לקוחות מגדירים את המיקום הזה כנכון כאשר הם נכנסים לרשת וחותמת זמן כשהם מתנתקים. חותמת זמן זו מציינת את הפעם האחרונה בה המשתמש נתון היה מקוון.

שים לב שהאפליקציה שלך צריכה לתור את פעולות הניתוק לפני שמשתמש מסומן באופן מקוון, כדי למנוע תנאי מירוץ במקרה שחיבור הרשת של הלקוח יאבד לפני שניתן יהיה לשלוח את שתי הפקודות לשרת.

להלן מערכת נוכחות פשוטה של ​​משתמשים:

ג'אווה

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

קוטלין+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")
    }
})