ذخیره حالت‌های رابط کاربری (Views)

مفاهیم و پیاده‌سازی Jetpack Compose

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ویو مدل

وضعیت نمونه ذخیره شده

ذخیره‌سازی پایدار

محل نگهداری

در حافظه

در حافظه

روی دیسک یا شبکه

از تغییر پیکربندی جان سالم به در می‌برد

بله

بله

بله

از مرگ فرآیند آغاز شده توسط سیستم جان سالم به در می‌برد

خیر

بله

بله

تابع ()expression/finish فعالیت کامل کاربر را ادامه می‌دهد.

خیر

خیر

بله

محدودیت‌های داده

اشیاء پیچیده خوب هستند، اما فضا با حافظه موجود محدود می‌شود

فقط برای انواع اولیه و اشیاء ساده و کوچک مانند String

فقط محدود به فضای دیسک یا هزینه/زمان بازیابی از منبع شبکه است

زمان خواندن/نوشتن

سریع (فقط دسترسی به حافظه)

کند (نیاز به سریال‌سازی/غیر سریال‌سازی دارد)

کند (نیاز به دسترسی به دیسک یا تراکنش شبکه دارد)

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

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

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

ViewModelها به طور خودکار توسط سیستم از بین می‌روند، زمانی که کاربر از activity یا fragment شما خارج می‌شود یا اگر تابع finish() را فراخوانی کنید، که به این معنی است که وضعیت (state) همانطور که کاربر در این سناریوها انتظار دارد، پاک می‌شود.

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

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

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

تابع فراخوانی onSaveInstanceState() در سیستم View و SavedStateHandle در ViewModels داده‌های مورد نیاز برای بارگذاری مجدد وضعیت یک کنترلر رابط کاربری، مانند یک اکتیویتی یا یک فرگمنت، را در صورتی که سیستم آن کنترلر را از بین ببرد و بعداً دوباره ایجاد کند، ذخیره می‌کنند. برای یادگیری نحوه پیاده‌سازی وضعیت نمونه ذخیره شده با استفاده از onSaveInstanceState ، به بخش «ذخیره و بازیابی وضعیت فعالیت» در راهنمای چرخه حیات اکتیویتی مراجعه کنید.

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

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

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

علاوه بر این، وقتی یک اکتیویتی را از یک intent باز می‌کنید، بسته‌ی افزونه‌ها هم هنگام تغییر پیکربندی و هم هنگام بازیابی سیستم از اکتیویتی، به آن اکتیویتی تحویل داده می‌شود.

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

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

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

با شروع از Fragment 1.1.0 یا وابستگی انتقالی آن Activity 1.0.0 ، کنترلرهای رابط کاربری، مانند یک Activity یا Fragment ، SavedStateRegistryOwner پیاده‌سازی می‌کنند و یک SavedStateRegistry ارائه می‌دهند که به آن کنترلر متصل است. SavedStateRegistry به کامپوننت‌ها اجازه می‌دهد تا به حالت ذخیره شده کنترلر رابط کاربری شما متصل شوند تا آن را مصرف کنند یا در آن مشارکت کنند. به عنوان مثال، ماژول Saved State برای ViewModel SavedStateRegistry برای ایجاد یک SavedStateHandle استفاده می‌کند و آن را در اختیار اشیاء ViewModel شما قرار می‌دهد. می‌توانید SavedStateRegistry را از درون کنترلر رابط کاربری خود با فراخوانی getSavedStateRegistry بازیابی کنید.

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

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String QUERY = "query";
    private String query = null;
    ...

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }
}

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

درون یک Activity یا Fragment ، می‌توانید پس از فراخوانی 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 SearchFragment : Fragment() {
    private var searchManager = SearchManager(this)
    ...
}

جاوا

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String PROVIDER = "search_manager";
    private static String QUERY = "query";
    private String query = null;

    public SearchManager(SavedStateRegistryOwner registryOwner) {
        registryOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
            if (event == Lifecycle.Event.ON_CREATE) {
                SavedStateRegistry registry = registryOwner.getSavedStateRegistry();

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

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

                // Apply the previously saved state
                if (state != null) {
                    query = state.getString(QUERY);
                }
            }
        });
    }

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }

    ...
}

class SearchFragment extends Fragment {
    private SearchManager searchManager = new SearchManager(this);
    ...
}

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

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

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

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

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

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

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

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

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

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

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

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

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

منابع اضافی

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

وبلاگ‌ها

کدلبز