حالت های رابط کاربری را ذخیره کنید

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

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

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

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

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

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

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

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

حذف وضعیت رابط کاربری توسط کاربر

کاربر انتظار دارد وقتی به صفحه‌ای می‌رود، حالت گذرای رابط کاربری آن تا زمانی که کاملاً از آن صرف نظر نکند، ثابت بماند. کاربر می‌تواند با انجام موارد زیر، یک صفحه یا برنامه را کاملاً از کار بیندازد:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

استفاده از وضعیت ذخیره شده به عنوان پشتیبان برای مدیریت مرگ فرآیند آغاز شده توسط سیستم

APIهایی مانند rememberSerializable و rememberSaveable در Compose و SavedStateHandle در ViewModels داده‌های مورد نیاز برای بارگذاری مجدد وضعیت رابط کاربری را در صورت از بین رفتن و ایجاد مجدد یک کامپوننت توسط سیستم، ذخیره می‌کنند. برای مدیریت کارآمدتر ساختارهای داده پیچیده، SavedStateHandle از سریال‌سازی Kotlinx از طریق افزونه saved {} پشتیبانی می‌کند و به شما امکان می‌دهد اشیاء type-safe را در کنار انواع اولیه استاندارد، به طور یکپارچه حفظ و بازیابی کنید. برای یادگیری نحوه پیاده‌سازی وضعیت ذخیره شده با استفاده از rememberSaveable ، به State و Jetpack Compose مراجعه کنید.

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

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

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

علاوه بر این، وقتی یک اکتیویتی را از یک intent باز می‌کنید، بسته‌ی extraها هم هنگام تغییر پیکربندی و هم هنگام بازیابی سیستم activity به activity تحویل داده می‌شود. اگر بخشی از داده‌های وضعیت رابط کاربری، مانند یک کوئری جستجو، هنگام اجرای activity به عنوان یک intent extra ارسال شده باشد، می‌توانید از بسته‌ی extras به جای بسته‌ی saved state استفاده کنید. برای کسب اطلاعات بیشتر در مورد intent extraها، به Intent و Intent Filters مراجعه کنید.

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

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

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

با شروع از Fragment 1.1.0 یا Activity 1.0.0 که وابستگی انتقالی دارد، کامپوننت‌های رابط کاربری، مانند ComponentActivity ، SavedStateRegistryOwner را پیاده‌سازی می‌کنند و یک SavedStateRegistry ارائه می‌دهند که به آن کامپوننت متصل است. SavedStateRegistry به کامپوننت‌ها اجازه می‌دهد تا به وضعیت ذخیره شده شما متصل شوند تا آن را مصرف کنند یا در آن مشارکت داشته باشند. به عنوان مثال، ماژول Saved State برای 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 فراخوانی کنید و یک کلید برای مرتبط کردن با داده‌های provider و همچنین خود provider ارسال کنید. داده‌های ذخیره شده قبلی برای provider را می‌توان با فراخوانی consumeRestoredStateForKey() در SavedStateRegistry بازیابی کرد و کلید مرتبط با داده‌های provider را ارسال کرد.

درون یک ComponentActivity ، می‌توانید پس از فراخوانی super.onCreate() ، یک SavedStateProvider در 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 و نه state ذخیره شده با استفاده از rememberSerializable ، rememberSaveable یا SavedStateHandle ، راه‌حل‌های ذخیره‌سازی بلندمدت نیستند و بنابراین جایگزینی برای ذخیره‌سازی محلی، مانند پایگاه داده، محسوب نمی‌شوند. در عوض، شما باید از این مکانیسم‌ها فقط برای ذخیره‌سازی موقت وضعیت رابط کاربری و از ذخیره‌سازی پایدار برای سایر داده‌های برنامه استفاده کنید. برای جزئیات بیشتر در مورد نحوه استفاده از ذخیره‌سازی محلی برای حفظ داده‌های مدل برنامه خود در درازمدت (مثلاً در هنگام راه‌اندازی مجدد دستگاه)، به راهنمای معماری برنامه مراجعه کنید.

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

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

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

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

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

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

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

بازیابی حالت‌های پیچیده: مونتاژ مجدد قطعات

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

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

منابع اضافی

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

کدلبز

محتوا را مشاهده می‌کند

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}