تعمل تطبيقات 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 ميغابايت من البيانات التي تمت مزامنتها سابقًا في ذاكرة التخزين المؤقت. من المفترض أن يكون ذلك كافيًا لمعظم التطبيقات. إذا تجاوزت ذاكرة التخزين المؤقت حجمها الذي تم ضبطه، ستحذف Firebase Realtime Database البيانات التي تم استخدامها مؤخرًا. لا تتم إزالة البيانات التي تتم مزامنتها نهائيًا من ذاكرة التخزين المؤقت.
الاستعلام عن البيانات بلا إنترنت
يخزّن Firebase Realtime Database البيانات التي يتم عرضها من طلب بحث لاستخدامها بلا اتصال بالإنترنت. بالنسبة إلى طلبات البحث التي تم إنشاؤها بلا إنترنت، يستمر عمل Firebase Realtime Database للبيانات التي تم تحميلها سابقًا. إذا لم يتم تحميل البيانات المطلوبة، يحمّل Firebase Realtime Database البيانات من ذاكرة التخزين المؤقت المحلية. عندما يتوفّر الاتصال بالشبكة مرة أخرى، يتم تحميل البيانات وستعكس طلب البحث.
على سبيل المثال، يطلب هذا الرمز آخر أربعة عناصر في 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()); } // ... });
لنفترض أنّ المستخدم فقد الاتصال بالإنترنت وأعاد تشغيل التطبيق، وطلب التطبيق آخر عنصرَين من الموقع نفسه. سيعرض هذا الطلب آخر عنصرَين بنجاح لأنّ التطبيق حمّل العناصر الأربعة كلّها في الطلب أعلاه.
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 أحداث "تمت إضافة طفل" للديناصورَين اللذين حصلا على أعلى النقاط، وذلك باستخدام ذاكرة التخزين المؤقت الثابتة. ولكن لن يتم إنشاء حدث "قيمة"، لأنّ التطبيق لم ينفّذ هذا الطلب مطلقًا أثناء الاتصال بالإنترنت.
إذا طلب التطبيق آخر ستة عناصر أثناء عدم الاتصال بالإنترنت، سيحصل على أحداث "تمت إضافة عنصر فرعي" للعناصر الأربعة المخزّنة مؤقتًا على الفور. عندما يتصل الجهاز بالإنترنت مرة أخرى، تتم مزامنة عميل Firebase Realtime Database مع الخادم ويحصل على آخر حدثَين من نوع "تمت إضافة عنصر فرعي" و"القيمة" للتطبيق.
التعامل مع المعاملات بلا إنترنت
يتم وضع أي معاملات يتم إجراؤها أثناء عدم اتصال التطبيق بالإنترنت في قائمة الانتظار. وعندما يستعيد التطبيق الاتصال بالشبكة، يتم إرسال المعاملات إلى خادم Realtime Database.
إدارة ميزة "التواجد في المنزل"
في التطبيقات التي تعمل في الوقت الفعلي، من المفيد غالبًا رصد وقت اتصال العملاء وانقطاع اتصالهم. على سبيل المثال، قد تحتاج إلى وضع علامة على مستخدم على أنّه "غير متصل بالإنترنت" عندما ينقطع اتصال العميل.
توفّر برامج "قاعدة بيانات Firebase" عناصر أساسية بسيطة يمكنك استخدامها للكتابة في قاعدة البيانات عندما ينقطع اتصال أحد البرامج بخوادم "قاعدة بيانات 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، يتحقّق الخادم من الأمان مرة ثانية (للتأكّد من أنّ العملية لا تزال صالحة) ثم يستدعي الحدث.
يمكن لتطبيقك استخدام دالة الاستدعاء في عملية الكتابة
للتأكّد من إرفاق 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 تلقائيًا حالة الاتصال لتقليل معدل نقل البيانات واستخدام البطارية. عندما لا يكون لدى العميل أي مستمعين نشطين،
أو عمليات كتابة أو 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);
انحراف الساعة
مع أنّ firebase.database.ServerValue.TIMESTAMP أكثر دقة ويُفضَّل استخدامه في معظم عمليات القراءة والكتابة، قد يكون من المفيد أحيانًا تقدير انحراف ساعة العميل بالنسبة إلى خوادم Firebase Realtime Database. يمكنك
إرفاق دالة رد اتصال بالموقع الجغرافي /.info/serverTimeOffset
للحصول على القيمة، بالمللي ثانية، التي يضيفها عملاء Firebase Realtime Database
إلى الوقت المحلي الذي تم الإبلاغ عنه (الوقت المنقضي بالمللي ثانية) لتقدير
وقت الخادم. يُرجى العِلم أنّ دقة هذا الإزاحة يمكن أن تتأثر بوقت استجابة الشبكة، وبالتالي فهي مفيدة في المقام الأول لاكتشاف الاختلافات الكبيرة (أكثر من ثانية واحدة) في وقت الساعة.
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
By combining disconnect operations with connection state monitoring and server timestamps, you can build a user presence system. في هذا النظام، يخزّن كل مستخدم البيانات في موقع قاعدة بيانات للإشارة إلى ما إذا كان عميل Realtime Database متصلاً بالإنترنت أم لا. يضبط العملاء هذا الموقع على "صحيح" عندما يتصلون بالإنترنت ويضبطون طابعًا زمنيًا عند قطع الاتصال. يشير هذا الطابع الزمني إلى آخر مرة كان فيها المستخدم المحدّد متصلاً بالإنترنت.
يُرجى العِلم أنّ تطبيقك يجب أن يضع عمليات قطع الاتصال في قائمة انتظار قبل أن يتم وضع علامة على المستخدم بأنّه متصل بالإنترنت، وذلك لتجنُّب أي حالات سباق في حال فقدان الاتصال بالشبكة الخاص بالعميل قبل إرسال كلا الأمرَين إلى الخادم.
في ما يلي نظام بسيط لتحديد حالة المستخدم:
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"); } });