فعال کردن قابلیت‌های آفلاین در Android

برنامه‌های 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);

به‌طور پیش‌فرض، ۱۰ مگابایت از داده‌های قبلاً همگام‌سازی‌شده در حافظه پنهان ذخیره می‌شود. این مقدار باید برای اکثر برنامه‌ها کافی باشد. اگر حافظه نهان از اندازه پیکربندی‌شده‌اش بزرگ‌تر شود، 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 اتصال را پس‌از ۶۰ ثانیه عدم فعالیت می‌بندد.

تأخیر در رسیدگی

مُهرهای زمان سرور

سرورهای 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");
    }
});

برنامه حضور نمونه

با ترکیب عملیات قطع اتصال با نظارت بر وضعیت اتصال و مُهرهای زمان سرور، می‌توانید سیستم حضور کاربر بسازید. در این سیستم، هر کاربر داده‌ها را در مکانی از پایگاه داده ذخیره می‌کند تا نشان دهد که آیا کارخواه 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");
    }
});