تعمل تطبيقات Firebase حتى إذا فقد تطبيقك الاتصال بالشبكة مؤقتًا. بالإضافة إلى ذلك، يوفر Firebase أدوات للاحتفاظ بالبيانات محليًا وإدارة التواجد والتعامل مع وقت الاستجابة.
ثبات القرص
تتعامل تطبيقات Firebase مع الانقطاعات المؤقتة في الشبكة تلقائيًا. تتوفّر البيانات المخزّنة مؤقتًا عندما تكون غير متصل بالإنترنت، ويعيد Firebase إرسال أي عمليات كتابة عند استعادة الاتصال بالشبكة.
عند تفعيل ميزة التثبيت على القرص، يكتب تطبيقك البيانات محليًا على الجهاز حتى يحافظ التطبيق على حالته بلا اتصال بالإنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق.
يمكنك تفعيل ميزة تثبيت القرص باستخدام سطر واحد فقط من الرمز.
Kotlin+KTX
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
سلوك الاستمرارية
من خلال تفعيل ميزة الاحتفاظ بالبيانات، تظل أي بيانات يزامنها برنامج قاعدة بيانات Firebase في الوقت الفعلي أثناء الاتصال بالإنترنت على القرص وتصبح متاحة بلا اتصال بالإنترنت، حتى عندما يُعيد المستخدم أو نظام التشغيل تشغيل التطبيق. ويعني هذا أنّ تطبيقك يعمل كما لو كان متصلاً بالإنترنت باستخدام البيانات المحلية المخزَّنة في ذاكرة التخزين المؤقت. سيستمر تنشيط استدعاءات المستمعين للحصول على التحديثات المحلية.
يحتفظ برنامج قاعدة بيانات Firebase في الوقت الفعلي تلقائيًا بقائمة انتظار تضم جميع عمليات الكتابة التي يتم إجراؤها عندما يكون تطبيقك غير متصل بالإنترنت. عند تفعيل ميزة الاحتفاظ بالبيانات، يتم الاحتفاظ بقائمة الانتظار هذه أيضًا على القرص كي تكون جميع عمليات الكتابة متاحة عندما يُعيد المستخدم أو نظام التشغيل تشغيل التطبيق. وعندما يستعيد التطبيق الاتصال، يتم إرسال جميع العمليات إلى خادم قاعدة بيانات Firebase في الوقت الفعلي.
إذا كان تطبيقك يستخدم مصادقة Firebase، يحتفظ عميل قاعدة بيانات Firebase في الوقت الفعلي، بالرمز المميز لمصادقة المستخدم في جميع عمليات إعادة تشغيل التطبيق. إذا انتهت صلاحية الرمز المميّز للمصادقة عندما يكون تطبيقك بلا اتصال بالإنترنت، يوقف العميل عمليات الكتابة مؤقتًا إلى أن يعيد تطبيقك مصادقة المستخدم، وإلّا فقد يتعذّر إتمام عمليات الكتابة بسبب قواعد الأمان.
الحفاظ على إعادة تحميل البيانات
تعمل "قاعدة بيانات Firebase في الوقت الفعلي" على مزامنة نسخة محلية من البيانات وتخزينها للمستمعين النشطين. بالإضافة إلى ذلك، يمكنك مزامنة مواقع جغرافية محددة.
Kotlin+KTX
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
يعمل برنامج قاعدة بيانات Firebase في الوقت الفعلي تلقائيًا على تنزيل البيانات في هذه المواقع الجغرافية ويحافظ على مزامنتها حتى إذا لم يكن للمرجع أي مستمعين نشطين. يمكنك إيقاف المزامنة مرة أخرى باستخدام سطر الرمز التالي.
Kotlin+KTX
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
بشكل تلقائي، يتم تخزين 10 ميغابايت من البيانات التي تمت مزامنتها سابقًا مؤقتًا. وهذا سيكون كافيًا لمعظم التطبيقات. وإذا تجاوزت ذاكرة التخزين المؤقت الحجم الذي تم ضبطه، تزيل قاعدة بيانات Firebase في الوقت الفعلي نهائيًا البيانات التي تم استخدامها مؤخرًا. لا تتم الإزالة النهائية للبيانات التي يتم الاحتفاظ بها متزامنة من ذاكرة التخزين المؤقت.
الاستعلام عن البيانات بلا اتصال بالإنترنت
تخزِّن "قاعدة بيانات Firebase في الوقت الفعلي" البيانات التي يتم عرضها من طلب بحث لاستخدامها بلا اتصال بالإنترنت. بالنسبة إلى طلبات البحث التي تم إنشاؤها أثناء عدم الاتصال بالإنترنت، تواصل "قاعدة بيانات Firebase في الوقت الفعلي" العمل مع البيانات التي تم تحميلها سابقًا. إذا لم يتم تحميل البيانات المطلوبة، تحمِّل قاعدة بيانات Firebase في الوقت الفعلي البيانات من ذاكرة التخزين المؤقت على الجهاز. عندما يصبح الاتصال بالشبكة متاحًا مرة أخرى، يتم تحميل البيانات وستظهر طلب البحث.
على سبيل المثال، يبحث هذا الرمز عن آخر أربعة عناصر في قاعدة بيانات Firebase في الوقت الفعلي للنتائج.
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()); } // ... });
لنفترض أنّ المستخدم فقد الاتصال بالإنترنت وانقطع الاتصال بالإنترنت ثم أعاد تشغيل التطبيق. عندما يكون التطبيق غير متصل بالإنترنت، يطلب التطبيق آخر عنصرين من الموقع الجغرافي نفسه. سيعرض هذا الاستعلام آخر عنصرين بنجاح لأن التطبيق حمّل العناصر الأربعة جميعها في الاستعلام أعلاه.
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 في الوقت الفعلي الأحداث "المضافة التابعة" للأحداث التي سجّل نوعين من الديناصورات من حيث النتائج، وذلك باستخدام ذاكرة التخزين المؤقت الثابتة. ولن يرفع هذا الحدث الحدث "value"، لأنّ التطبيق لم ينفّذ طلب البحث هذا على الإنترنت.
إذا طلب التطبيق العناصر الستة الأخيرة بلا اتصال بالإنترنت، سيحصل على الفور على أحداث "إضافة تابعة" للعناصر الأربعة المخزّنة مؤقتًا. عندما يعود الجهاز إلى الاتصال بالإنترنت، يتزامن برنامج قاعدة بيانات Firebase في الوقت الفعلي مع الخادم ويحصل على آخر حدثَي "إضافة ثانوي" و"قيمة" للتطبيق.
التعامل مع المعاملات بلا إنترنت
يتم وضع أي معاملات يتم إجراؤها عندما يكون التطبيق بلا اتصال بالإنترنت في قائمة الانتظار. وبعد أن يستعيد التطبيق الاتصال بالشبكة، يتم إرسال المعاملات إلى خادم قاعدة بيانات الوقت الفعلي.
إدارة التواجد
في تطبيقات الوقت الفعلي، غالبًا ما يكون من المفيد رصد حالات اتصال العملاء وقطع الاتصال بهم. على سبيل المثال، يمكنك وضع علامة "غير متصل بالإنترنت" على المستخدم عند قطع اتصال العميل به.
يوفر عملاء قاعدة بيانات 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!");
طريقة عمل ميزة on Connect
عند إنشاء عملية onDisconnect()
، تظهر العملية على خادم "قاعدة بيانات Firebase في الوقت الفعلي". يفحص الخادم الأمان
للتأكّد من أنّ المستخدم يمكنه تنفيذ حدث الكتابة المطلوب، ويبلغ
تطبيقك إذا كان غير صالح. ويراقب الخادم بعد ذلك الاتصال. وفي حال انتهاء مهلة الاتصال في أي وقت، أو
إغلاق الخادم بشكل نشط من قِبل برنامج قاعدة البيانات في الوقت الفعلي، يتحقّق الخادم من الأمان
مرة ثانية (للتأكّد من أن العملية لا تزال صالحة)، ثم يستدعي
الحدث.
يمكن لتطبيقك استخدام عملية الاستدعاء في عملية الكتابة لضمان إرفاق 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 في الوقت الفعلي"
موقعًا خاصًا في /.info/connected
يتم
تعديله في كل مرة تتغيّر فيها حالة اتصال عميل "قاعدة بيانات Firebase" في الوقت الفعلي. وفي ما يلي مثال لذلك:
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
هي قيمة منطقية لا تتم مزامنتها بين عملاء قاعدة بيانات الوقت الفعلي، لأنّ القيمة تعتمد على حالة البرنامج. بعبارة أخرى، إذا قرأ أحد البرامج /.info/connected
على أنّه خطأ، لا يضمن ذلك أنّ برنامجًا منفصلاً سيقرأ أيضًا القيمة "خطأ".
على نظام التشغيل Android، يدير Firebase تلقائيًا حالة الاتصال
لتقليل معدّل نقل البيانات واستخدام البطارية. إذا لم يكن لدى العميل أدوات معالجة مركزية (غير نشطة) أو عمليات onDisconnect
معلَّقة أو عمليات onDisconnect
ولا يتم إلغاء ربطه بشكل صريح باستخدام طريقة goOffline
، يغلق Firebase الاتصال بعد 60 ثانية من عدم النشاط.
وقت الاستجابة
الطوابع الزمنية للخادم
توفّر خوادم "قاعدة بيانات Firebase في الوقت الفعلي" آلية لإدراج
الطوابع الزمنية التي تم إنشاؤها على الخادم كبيانات. وتوفّر هذه الميزة، بالإضافة إلى onDisconnect
، طريقة سهلة لتدوين
الوقت الذي تم فيه قطع اتصال برنامج قاعدة البيانات في الوقت الفعلي بشكل موثوق:
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 في الوقت الفعلي". يمكنك إرفاق طلب معاودة الاتصال بالموقع /.info/serverTimeOffset
للحصول على القيمة بالمللي ثانية التي يضيفها عملاء قاعدة بيانات Firebase في الوقت الفعلي إلى الوقت المحلي لإعداد التقارير (الوقت الفعلي بالمللي ثانية) لتقدير وقت الخادم. يُرجى العِلم أنّ دقة هذه المعادلة يمكن أن تتأثر بوقت استجابة الشبكة، وبالتالي فهي مفيدة في المقام الأول لرصد
تناقضات كبيرة (أكبر من ثانية واحدة) في وقت الساعة.
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"); } });
نموذج تطبيق التواجد
ومن خلال الجمع بين عمليات قطع الاتصال ومراقبة حالة الاتصال والطوابع الزمنية للخادم، يمكنك إنشاء نظام تواجد المستخدم. في هذا النظام، يخزِّن كل مستخدم البيانات في موقع قاعدة بيانات للإشارة إلى ما إذا كان برنامج قاعدة البيانات في الوقت الفعلي متصلاً بالإنترنت أم لا. يضبط العملاء هذا الموقع الجغرافي على "صحيح" عند الاتصال بالإنترنت وعلى طابع زمني عند قطع الاتصال. ويشير هذا الطابع الزمني إلى آخر مرة كان فيها المستخدم المحدّد متصلاً بالإنترنت.
تجدر الإشارة إلى أنّ تطبيقك يجب أن يضيف عمليات قطع الاتصال إلى قائمة الانتظار قبل أن يتم وضع علامة على المستخدم على الإنترنت، لتجنُّب حدوث أي حالات تعارض في حال فقدان اتصال العميل بالشبكة قبل إرسال الأمرَين إلى الخادم.
إليك نظام بسيط لتواجد المستخدم:
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"); } });