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

برنامه های Firebase حتی اگر برنامه شما به طور موقت اتصال شبکه خود را قطع کند، کار می کنند. علاوه بر این، Firebase ابزارهایی را برای تداوم داده ها به صورت محلی، مدیریت حضور و مدیریت تأخیر فراهم می کند.

پایداری دیسک

برنامه های Firebase به طور خودکار وقفه های موقت شبکه را کنترل می کنند. داده‌های ذخیره‌شده در حافظه پنهان در حالت آفلاین در دسترس هستند و Firebase هنگام بازیابی اتصال شبکه، هر نوشته‌ای را دوباره ارسال می‌کند.

وقتی پایداری دیسک را فعال می‌کنید، برنامه شما داده‌ها را به صورت محلی در دستگاه می‌نویسد تا برنامه شما بتواند در حالت آفلاین وضعیت را حفظ کند، حتی اگر کاربر یا سیستم عامل برنامه را مجدداً راه‌اندازی کند.

شما می توانید پایداری دیسک را تنها با یک خط کد فعال کنید.

Kotlin+KTX

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+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 مگابایت از داده‌های همگام‌سازی شده قبلی در حافظه پنهان ذخیره می‌شود. این باید برای اکثر برنامه ها کافی باشد. اگر حافظه پنهان از اندازه پیکربندی شده خود بیشتر شود، Firebase Realtime Database داده هایی را که اخیراً کمتر استفاده شده اند را پاک می کند. داده هایی که همگام نگه داشته می شوند از حافظه پنهان پاک نمی شوند.

پرس و جو داده ها بصورت آفلاین

Firebase Realtime Database داده های بازگشتی از یک درخواست را برای استفاده در حالت آفلاین ذخیره می کند. برای پرس و جوهایی که در حالت آفلاین ساخته می شوند، Firebase Realtime Database برای داده های بارگیری شده قبلی به کار خود ادامه می دهد. اگر داده‌های درخواستی بارگیری نشده باشند، Firebase Realtime Database داده‌ها را از حافظه پنهان محلی بارگیری می‌کند. هنگامی که اتصال شبکه دوباره در دسترس است، داده ها بارگیری می شوند و پرس و جو را منعکس می کنند.

به عنوان مثال، این کد برای چهار مورد آخر در 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());
    }

    // ...
});

فرض کنید که کاربر ارتباط خود را قطع می کند، آفلاین می شود و برنامه را دوباره راه اندازی می کند. در حالی که هنوز آفلاین است، برنامه دو مورد آخر را از یک مکان جستجو می کند. این عبارت با موفقیت دو مورد آخر را برمی گرداند زیرا برنامه هر چهار مورد را در جستار بالا بارگیری کرده است.

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 با استفاده از حافظه پنهان، رویدادهای «کودک اضافه شده» را برای دو دایناسور با بالاترین امتیاز افزایش می‌دهد. اما یک رویداد "ارزش" را افزایش نمی دهد، زیرا برنامه هرگز آن درخواست را در حالت آنلاین اجرا نکرده است.

اگر برنامه شش مورد آخر را در حالت آفلاین درخواست کند، بلافاصله رویدادهای «کودک اضافه شده» را برای چهار مورد ذخیره شده در حافظه پنهان دریافت می‌کند. هنگامی که دستگاه دوباره آنلاین می شود، سرویس گیرنده Firebase Realtime Database با سرور همگام می شود و دو رویداد نهایی «کودک اضافه شده» و «ارزش» را برای برنامه دریافت می کند.

انجام معاملات آفلاین

هر تراکنشی که در زمانی که برنامه آفلاین است انجام شود، در صف قرار می گیرد. هنگامی که برنامه دوباره اتصال شبکه را به دست آورد، تراکنش ها به سرور 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 کار می کند

هنگامی که یک عملیات onDisconnect() ایجاد می کنید، این عملیات در سرور Firebase Realtime Database زندگی می کند. سرور امنیت را بررسی می کند تا مطمئن شود کاربر می تواند رویداد نوشتن درخواستی را انجام دهد و در صورت نامعتبر بودن برنامه شما را به اطلاع می رساند. سپس سرور اتصال را کنترل می کند. اگر در هر نقطه ای زمان اتصال تمام شود، یا به طور فعال توسط سرویس گیرنده Realtime Database بسته شود، سرور برای بار دوم امنیت را بررسی می کند (برای اطمینان از اینکه عملیات همچنان معتبر است) و سپس رویداد را فراخوانی می کند.

برنامه شما می‌تواند از پاسخ به تماس در عملیات نوشتن استفاده کند تا مطمئن شود 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 بخواند، این تضمینی نیست که یک کلاینت جداگانه نیز 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 می تواند مفید باشد. می‌توانید یک تماس برگشتی را به مکان /.info/serverTimeOffset متصل کنید تا مقداری را که مشتریان Firebase Realtime Database بر حسب میلی‌ثانیه به زمان گزارش شده محلی (زمان دوره به میلی‌ثانیه) برای تخمین زمان سرور اضافه می‌کنند، به دست آورید. توجه داشته باشید که دقت این افست می‌تواند تحت‌تاثیر تأخیر شبکه قرار گیرد، و بنابراین اساساً برای کشف اختلافات بزرگ (> 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 آنلاین است یا نه. مشتریان زمانی که آنلاین می‌شوند، این مکان را روی true و هنگام قطع ارتباط یک مهر زمانی تنظیم می‌کنند. این مهر زمان آخرین باری را که کاربر مورد نظر آنلاین بوده است نشان می دهد.

توجه داشته باشید که برنامه شما باید قبل از اینکه کاربر به صورت آنلاین علامت گذاری شود، عملیات قطع اتصال را در صف قرار دهد تا در صورت قطع شدن اتصال شبکه مشتری قبل از ارسال هر دو دستور به سرور، از هرگونه شرایط مسابقه جلوگیری شود.

در اینجا یک سیستم حضور کاربر ساده وجود دارد:

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