ذخیره کردن وضعیت‌های میانای کاربر

این راهنما درباره انتظارات کاربر از وضعیت «واسط کاربر» و گزینه‌های دردسترس برای حفظ وضعیت بحث می‌کند.

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

برای پر کردن شکاف بین انتظارات کاربر و رفتار سیستم، از ترکیبی از روش‌های زیر استفاده کنید:

  • ‫ViewModel شیء.
  • وضعیت ذخیره‌شده در زمینه‌های زیر:
  • فضای ذخیره‌سازی محلی برای حفظ وضعیت میانای کاربر درطول انتقال برنامه و صفحه.

راه‌حل بهینه به پیچیدگی داده‌های میانای کاربر، موارد استفاده برنامه، و یافتن تعادل بین سرعت دسترسی به داده‌ها و استفاده از حافظه بستگی دارد.

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

انتظارات کاربر و رفتار سیستم

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

بستن وضعیت واسط کاربر به‌درخواست کاربر

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

  • کشیدن برنامه به خارج از صفحه «نمای کلی» (برنامه‌های اخیر).
  • بستن یا خروج اجباری از برنامه در صفحه «تنظیمات».
  • دستگاه درحال بازراه‌اندازی است.
  • تکمیل نوعی کنش «پایان‌دهنده» (که توسط Activity.finish() پشتیبانی می‌شود).

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

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

بستن وضعیت واسط کاربر آغازشده توسط سیستم

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

توجه داشته باشید که می‌توانید (اگرچه توصیه نمی‌شود) عملکرد پیش‌فرض را برای تغییرات پیکربندی ملغی کنید. برای جزئیات بیشتر، مدیریت تغییر پیکربندی را ببینید.

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

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

گزینه‌های حفظ وضعیت واسط کاربر

وقتی انتظارات کاربر درباره وضعیت واسط کاربر با رفتار پیش‌فرض سیستم مطابقت ندارد، باید وضعیت واسط کاربر را ذخیره و بازیابی کنید تا مطمئن شوید که تخریب آغازشده ازسوی سیستم برای کاربر شفاف است.

هریک از گزینه‌های حفظ وضعیت میانای کاربر در ابعاد زیر که بر تجربه کاربر تأثیر می‌گذارند متفاوت است:

ViewModel وضعیت ذخیره‌شده فضای ذخیره‌سازی دائمی
مکان ذخیره در حافظه در حافظه در دیسک یا شبکه
از تغییر پیکربندی جان سالم به‌در می‌برد بله بله بله
دربرابر مرگ پردازش آغازشده توسط سیستم مقاومت می‌کند نه بله بله
از بستن کامل صفحه توسط کاربر جان سالم به‌در می‌برد/finish() نه نه بله
محدودیت‌های داده اشیاء پیچیده مشکلی ندارد، اما فضا براساس حافظه دردسترس محدود می‌شود فقط برای انواع ابتدایی و اشیای ساده و کوچک مثل String فقط با فضای دیسک یا هزینه / زمان بازیابی از منبع شبکه محدود می‌شود
زمان خواندن/نوشتن سریع (فقط دسترسی به حافظه) آهسته (به سریال‌سازی/سریال‌سازی معکوس نیاز دارد) کُند (به دسترسی به دیسک یا تراکنش شبکه نیاز دارد)

از ViewModel برای مدیریت تغییرات پیکربندی استفاده کنید

‫ViewModel برای ذخیره و مدیریت داده‌های مربوط به میانای کاربری درحالی‌که کاربر به‌طور فعال از برنامه استفاده می‌کند ایده‌آل است. این ویژگی امکان دسترسی سریع به داده‌های واسط کاربر را فراهم می‌کند و به شما کمک می‌کند از واکشی مجدد داده‌ها از شبکه یا دیسک در چرخش، تغییر اندازه پنجره، و دیگر تغییرات پیکربندی رایج جلوگیری کنید. برای آشنایی با نحوه پیاده‌سازی ViewModel، راهنمای ViewModel را ببینید.

‫ViewModel داده‌ها را در حافظه نگه می‌دارد، که یعنی بازیابی آن ارزان‌تر از بازیابی داده‌ها از دیسک یا شبکه است. ‫ViewModel با مالک چرخه حیات، مثل مقصد «پیمایش» یا فعالیت، مرتبط است. درطول تغییر پیکربندی در حافظه می‌ماند و سیستم به‌طور خودکار ViewModel را با نمونه مالک چرخه حیات جدیدی که از تغییر پیکربندی حاصل می‌شود مرتبط می‌کند.

برخلاف وضعیت ذخیره‌شده، ViewModels درطول فرایند آغازشده توسط سیستم ازبین می‌روند. برای بار کردن مجدد داده‌ها پس‌از بسته شدن فرایند آغازشده توسط سیستم در ViewModel، از SavedStateHandle API استفاده کنید. یا اگر داده‌ها مربوط به رابط کاربری است و نیازی نیست در ViewModel نگهداری شود، از rememberSerializable استفاده کنید. برای انواع داده‌های ابتدایی یا سناریوهایی که نمی‌خواهید از @Serializable استفاده کنید، از rememberSaveable استفاده کنید. اگر داده‌ها داده‌های برنامه باشد، بهتر است آن را در دیسک ماندگار کنید.

اگر ازقبل راه‌حلی در حافظه برای ذخیره وضعیت واسط کاربر در تغییرات پیکربندی دارید، ممکن است نیازی به استفاده از ViewModel نداشته باشید.

استفاده از وضعیت ذخیره‌شده به‌عنوان پشتیبان برای مدیریت فرایند پایان‌یافته سیستم

میاناهای برنامه‌سازی کاربردی مثل rememberSerializable و rememberSaveable در Compose و SavedStateHandle در ViewModels داده‌های موردنیاز برای بارگیری مجدد وضعیت رابط کاربری را درصورتی‌که سیستم مؤلفه‌ای را ازبین ببرد و بعداً دوباره ایجاد کند ذخیره می‌کنند. برای مدیریت کارآمدتر ساختارهای داده پیچیده، SavedStateHandle از Kotlinx Serialization ازطریق افزونه saved {} پشتیبانی می‌کند و به شما امکان می‌دهد اشیای نوع‌امن را درکنار انواع اولیه استاندارد به‌طور یکپارچه ماندگار و بازیابی کنید. برای آشنایی با نحوه پیاده‌سازی حالت ذخیره‌شده بااستفاده از rememberSaveable، به حالت و Jetpack Compose مراجعه کنید.

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

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

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

علاوه‌براین، وقتی فعالیتی را از یک هدف باز می‌کنید، بسته موارد اضافی هم هنگام تغییر پیکربندی و هم هنگام بازیابی فعالیت توسط سیستم به فعالیت تحویل داده می‌شود. اگر بخشی از داده‌های وضعیت واسط کاربر، مثل پُرسمان جستجو، به‌عنوان هدف اضافی هنگام راه‌اندازی فعالیت ارسال شده باشد، می‌توانید به‌جای بسته وضعیت ذخیره‌شده از بسته اضافی استفاده کنید. برای کسب اطلاعات بیشتر درباره افزونه‌های هدف، هدف و فیلترهای هدف را ببینید.

در هریک از این سناریوها، برای جلوگیری از هدر رفتن چرخه‌ها درحین بارگیری مجدد داده‌ها از پایگاه داده درطول تغییر پیکربندی، باید از ViewModel استفاده کنید.

در مواردی که داده‌های واسط کاربر برای حفظ کردن ساده و سبک باشد، می‌توانید از APIهای وضعیت ذخیره‌شده به‌تنهایی برای حفظ داده‌های وضعیت استفاده کنید.

بااستفاده از SavedStateRegistry به وضعیت ذخیره‌شده متصل شوید

از Fragment 1.1.0 یا وابستگی انتقالی آن Activity 1.0.0 شروع می‌شود، عناصر میانای کاربری، مثل ComponentActivity، SavedStateRegistryOwner را پیاده‌سازی می‌کنند و SavedStateRegistry را ارائه می‌دهند که به آن عنصر پیوند داده شده است. SavedStateRegistry به عناصر اجازه می‌دهد به وضعیت ذخیره‌شده شما متصل شوند تا از آن استفاده کنند یا در آن مشارکت کنند. برای مثال، واحد «وضعیت ذخیره‌شده برای ViewModel» از SavedStateRegistry برای ایجاد SavedStateHandle و ارائه آن به اشیای ViewModel استفاده می‌کند. با فراخوانی savedStateRegistry می‌توانید SavedStateRegistry را از مالک چرخه حیاتتان بازیابی کنید.

عناصری که در وضعیت ذخیره‌شده نقش دارند باید SavedStateRegistry.SavedStateProvider را پیاده‌سازی کنند، که یک روش واحد به‌نام saveState() را تعریف می‌کند. روش saveState() به عنصر شما اجازه می‌دهد Bundle حاوی هر وضعیتی را که باید از آن عنصر ذخیره شود برگرداند. SavedStateRegistry این روش را در مرحله وضعیت ذخیره کردن چرخه حیات مالک چرخه حیات فرا می‌خواند.

  class SearchManager : SavedStateRegistry.SavedStateProvider {
      companion object {
          private const val QUERY = "query"
      }

      private val query: String? = null

      ...

      override fun saveState(): Bundle {
          return bundleOf(QUERY to query)
      }
  }

برای ثبت SavedStateProvider، با registerSavedStateProvider() در SavedStateRegistry تماس بگیرید و کلیدی را برای مرتبط کردن با داده‌های ارائه‌دهنده و همچنین ارائه‌دهنده ارسال کنید. داده‌های قبلاً ذخیره‌شده برای ارائه‌دهنده را می‌توان با فراخوانی consumeRestoredStateForKey() در SavedStateRegistry و ارسال کلید مرتبط با داده‌های ارائه‌دهنده از وضعیت ذخیره‌شده بازیابی کرد.

ظرف ComponentActivity، می‌توانید SavedStateProvider را در onCreate() پس‌از تماس با super.onCreate() ثبت کنید. یا می‌توانید LifecycleObserver را در SavedStateRegistryOwner تنظیم کنید که LifecycleOwner را پیاده‌سازی می‌کند و SavedStateProvider را پس‌از وقوع رویداد ON_CREATE ثبت کنید. بااستفاده از LifecycleObserver، می‌توانید ثبت و بازیابی وضعیت قبلاً ذخیره‌شده را از خود SavedStateRegistryOwner جدا کنید.

  class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
      companion object {
          private const val PROVIDER = "search_manager"
          private const val QUERY = "query"
      }

      private val query: String? = null

      init {
          // Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
          registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
              if (event == Lifecycle.Event.ON_CREATE) {
                  val registry = registryOwner.savedStateRegistry

                  // Register this object for future calls to saveState()
                  registry.registerSavedStateProvider(PROVIDER, this)

                  // Get the previously saved state and restore it
                  val state = registry.consumeRestoredStateForKey(PROVIDER)

                  // Apply the previously saved state
                  query = state?.getString(QUERY)
              }
          }
      }

      override fun saveState(): Bundle {
          return bundleOf(QUERY to query)
      }

      ...
  }

  class SearchActivity : ComponentActivity() {
    private var searchManager = SearchManager(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Set up your Compose UI here
        setContent {
            // ...
        }
    }
  }

از ماندگاری محلی برای مدیریت مرگ فرایند برای داده‌های پیچیده یا بزرگ استفاده کنید

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

‫ViewModel و وضعیت ذخیره‌شده بااستفاده از rememberSerializable، rememberSaveable، یا SavedStateHandle راه‌حل‌های ذخیره‌سازی بلندمدت نیستند و بنابراین جایگزین فضای ذخیره‌سازی محلی، مثل پایگاه داده، نمی‌شوند. درعوض، باید از این سازوکارها فقط برای ذخیره موقت وضعیت رابط کاربری گذرا استفاده کنید و از فضای ذخیره‌سازی ماندگار برای سایر داده‌های برنامه استفاده کنید. برای جزئیات بیشتر درباره نحوه استفاده از فضای ذخیره‌سازی محلی برای حفظ داده‌های مدل برنامه در بلندمدت (مثلاً درصورت راه‌اندازی مجدد دستگاه)، راهنمای معماری برنامه را ببینید.

مدیریت وضعیت واسط کاربر: تقسیم و غلبه

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

  • ماندگاری محلی: همه داده‌های برنامه‌ای را که نمی‌خواهید درصورت باز و بسته کردن برنامه ازدست بدهید ذخیره می‌کند.
    • مثال: مجموعه‌ای از اشیای آهنگ که می‌تواند شامل فایل‌های صوتی و فراداده باشد.
  • ViewModel: همه داده‌های لازم برای نمایش میانای کاربری مرتبط، وضعیت میانای کاربری صفحه، را در حافظه ذخیره می‌کند.
    • مثال: اشیای آهنگ مربوط به جدیدترین جستجو و جدیدترین پرسمان جستجو.
  • وضعیت ذخیره‌شده (rememberSerializable،‏ rememberSaveable، و SavedStateHandle): مقدار کمی از داده‌های لازم برای بارگیری مجدد وضعیت میانای کاربر را ذخیره می‌کند اگر سیستم متوقف شود و سپس میانای کاربر را بازسازی کند. به‌جای ذخیره کردن اشیاء پیچیده در اینجا، اشیاء پیچیده را در فضای ذخیره‌سازی محلی ماندگار کنید و شناسه یکتایی برای این اشیاء در میاناهای برنامه‌سازی کاربردی وضعیت ذخیره‌شده ذخیره کنید.
    • مثال: ذخیره کردن جدیدترین پرسمان جستجو.

به‌عنوان مثال، برنامه‌ای را درنظر بگیرید که به شما امکان می‌دهد در کتابخانه آهنگ‌هایتان جستجو کنید. در اینجا نحوه مدیریت رویدادهای مختلف آمده است:

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

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

وقتی برنامه به پس‌زمینه می‌رود و سیستم وضعیت را ذخیره می‌کند، پُرسمان جستجو باید بااستفاده از APIهای وضعیت ذخیره‌شده ذخیره شود، درصورتی‌که فرایند بازسازی شود. ازآنجایی‌که این اطلاعات برای بار کردن داده‌های برنامه که در این مکان ذخیره شده‌اند ضروری است، پُرسمان جستجو را در ViewModel ذخیره کنید SavedStateHandle، یا از rememberSerializable یا rememberSaveable در ترکیب‌پذیرهایتان استفاده کنید. این تمام اطلاعاتی است که برای بار کردن داده‌ها و بازگرداندن واسط کاربر به وضعیت فعلی‌اش نیاز دارید.

بازیابی وضعیت‌های پیچیده: درحال سرهم کردن قطعات

وقتی کاربر باید به برنامه برگردد، دو سناریو ممکن برای بازسازی واسط کاربر وجود دارد:

  • پس‌از اینکه سیستم فرایند برنامه را پایان داد، رابط کاربری مجدداً ایجاد می‌شود. سیستم پُرسمان را بااستفاده از میاناهای برنامه‌سازی کاربردی وضعیت ذخیره‌شده ذخیره کرده است. ‫ViewModel (بااستفاده از SavedStateHandle) یا عنصر ترکیبی (بااستفاده از rememberSerializable یا rememberSaveable) پُرسمان را به‌طور خودکار بازیابی می‌کند. اگر عنصر ترکیبی پُرسمان را بازیابی کند، پُرسمان را به ViewModel ارسال می‌کند. ViewModel می‌بیند که نتایج جستجویی در حافظه نهان ندارد و بار کردن نتایج جستجو را بااستفاده از پُرسمان جستجوی داده‌شده واگذار می‌کند.
  • پس‌از تغییر پیکرببندی، میانای کاربر بازآفرینی می‌شود. ازآنجایی‌که نمونه ViewModel ازبین نرفته است، ViewModel همه اطلاعات ذخیره‌شده در حافظه را دارد و نیازی نیست دوباره از پایگاه داده پُرسمان کند.

منابع بیشتر

برای کسب اطلاعات بیشتر درباره ذخیره کردن وضعیت‌های واسط کاربر، منابع زیر را ببینید.

Codelabs

محتوا را می‌بیند