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

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

پایداری دیسک

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

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

شما می‌توانید تنها با یک خط کد، قابلیت ماندگاری دیسک را فعال کنید.

Kotlin

Firebase.database.setPersistenceEnabled(true)

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

رفتار مداوم

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

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

اگر برنامه شما از احراز هویت فایربیس استفاده می‌کند، کلاینت 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 مگابایت از داده‌های همگام‌سازی‌شده قبلی در حافظه پنهان (cache) ذخیره می‌شود. این مقدار برای اکثر برنامه‌ها کافی است. اگر حافظه پنهان از اندازه پیکربندی‌شده خود بزرگتر شود، 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 با استفاده از حافظه پنهان دائمی، رویدادهای «child added» را برای دو دایناسور با بالاترین امتیاز ایجاد می‌کند. اما رویداد «value» را ایجاد نمی‌کند، زیرا برنامه هرگز آن پرس‌وجو را در حالت آنلاین اجرا نکرده است.

اگر برنامه در حالت آفلاین شش مورد آخر را درخواست کند، رویدادهای «child added» را برای چهار مورد ذخیره شده فوراً دریافت می‌کند. وقتی دستگاه دوباره آنلاین می‌شود، کلاینت Firebase Realtime Database با سرور همگام‌سازی می‌شود و دو رویداد آخر «child added» و «value» را برای برنامه دریافت می‌کند.

مدیریت تراکنش‌ها به صورت آفلاین

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

مدیریت حضور

در برنامه‌های بلادرنگ، اغلب تشخیص زمان اتصال و قطع اتصال کلاینت‌ها مفید است. برای مثال، ممکن است بخواهید وقتی کلاینت یک کاربر قطع می‌شود، او را به عنوان «آفلاین» علامت‌گذاری کنید.

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

در اینجا یک مثال ساده از نوشتن داده‌ها پس از قطع اتصال با استفاده از تابع اولیه 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 را false بخواند، هیچ تضمینی وجود ندارد که کلاینت جداگانه نیز false را بخواند.

در اندروید، فایربیس به طور خودکار وضعیت اتصال را مدیریت می‌کند تا پهنای باند و مصرف باتری را کاهش دهد. وقتی یک کلاینت هیچ شنونده فعالی ندارد، هیچ عملیات نوشتن در انتظار یا onDisconnect ندارد و به طور صریح توسط متد goOffline قطع نشده است، فایربیس پس از 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 به زمان گزارش شده محلی (زمان epoch به میلی ثانیه) اضافه می‌کنند تا زمان سرور را تخمین بزنند، بر حسب میلی ثانیه، به دست آورید. توجه داشته باشید که دقت این انحراف می‌تواند تحت تأثیر تأخیر شبکه قرار گیرد و بنابراین در درجه اول برای کشف اختلافات بزرگ (> 1 ثانیه) در زمان ساعت مفید است.

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 آنلاین است یا خیر. کلاینت‌ها هنگام آنلاین شدن، این مکان را روی true تنظیم می‌کنند و هنگام قطع ارتباط، یک مهر زمانی. این مهر زمانی آخرین باری را که کاربر مشخص آنلاین بوده است، نشان می‌دهد.

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

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

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