Zamiast korzystać z nakładek zasobów środowiska wykonawczego (RRO), używaj wtyczek biblioteki Car UI, aby tworzyć kompletne implementacje dostosowań komponentów w bibliotece Car UI. RRO umożliwiają zmianę tylko zasobów XML komponentów biblioteki Car UI, co ogranicza zakres dostosowań.
Tworzenie wtyczki
Wtyczka biblioteki Car UI to plik APK, który zawiera klasy implementujące zestaw interfejsów Plugin API. Interfejsy Plugin API można skompilować we wtyczce jako bibliotekę statyczną.
Przykłady w Soong i Gradle:
Soong
Oto przykład Soong:
android_app {
name: "my-plugin",
min_sdk_version: "28",
target_sdk_version: "30",
aaptflags: ["--shared-lib"],
sdk_version: "current",
manifest: "src/main/AndroidManifest.xml",
srcs: ["src/main/java/**/*.java"],
resource_dirs: ["src/main/res"],
static_libs: [
"car-ui-lib-oem-apis",
],
// Disable optimization is mandatory to prevent R.java class from being
// stripped out
optimize: {
enabled: false,
},
certificate: ":my-plugin-certificate",
}
Gradle
Zobacz ten plik build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 28
targetSdkVersion 30
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
debug {
storeFile file('chassis_upload_key.jks')
storePassword 'chassis'
keyAlias 'chassis'
keyPassword 'chassis'
}
}
}
dependencies {
implementation project(':oem-apis')
// Or use the following if you'd like to use the maven artifact
// implementation 'com.android.car.ui:car-ui-lib-plugin-apis:1.0.0'
}
Settings.gradle:
// You can remove the ':oem-apis' if you're using the maven artifact.
include ':oem-apis'
project(':oem-apis').projectDir = new File('./path/to/oem-apis')
include ':my-plugin'
project(':my-plugin').projectDir = new File('./my-plugin')
Wtyczka musi mieć dostawcę treści zadeklarowanego w pliku manifestu, który ma te atrybuty:
android:authorities="com.android.car.ui.plugin"
android:enabled="true"
android:exported="true"
android:authorities="com.android.car.ui.plugin" sprawia, że wtyczka jest wykrywalna
przez bibliotekę Car UI. Dostawca musi być eksportowany, aby można było wysyłać do niego zapytania w czasie działania. Jeśli atrybut enabled jest ustawiony na false, zamiast implementacji wtyczki będzie używana implementacja domyślna. Klasa dostawcy treści nie musi istnieć. W takim przypadku dodaj
tools:ignore="MissingClass" do definicji dostawcy. Poniżej znajdziesz przykładowy wpis w pliku manifestu:
<application>
<provider
android:name="com.android.car.ui.plugin.PluginNameProvider"
android:authorities="com.android.car.ui.plugin"
android:enabled="false"
android:exported="true"
tools:ignore="MissingClass"/>
</application>
Na koniec, ze względów bezpieczeństwa, podpisz aplikację.
Wtyczki jako biblioteka udostępniona
W przeciwieństwie do statycznych bibliotek Androida, które są kompilowane bezpośrednio w aplikacjach, biblioteki udostępnione Androida są kompilowane w samodzielnym pliku APK, do którego odwołują się inne aplikacje w czasie działania.
Wtyczki zaimplementowane jako biblioteka udostępniona Androida mają swoje klasy automatycznie dodawane do współdzielonego modułu ładującego klasy między aplikacjami. Gdy aplikacja korzystająca z biblioteki Car UI określi zależność od biblioteki udostępnionej wtyczki w czasie działania, jej moduł ładujący klasy może uzyskać dostęp do klas biblioteki udostępnionej wtyczki. Wtyczki zaimplementowane jako zwykłe aplikacje na Androida (nie jako biblioteka udostępniona) mogą negatywnie wpływać na czas zimnego startu aplikacji.
Implementowanie i tworzenie bibliotek udostępnionych
Tworzenie bibliotek udostępnionych Androida jest bardzo podobne do tworzenia zwykłych aplikacji na Androida, ale z kilkoma kluczowymi różnicami.
- Użyj tagu
libraryw taguapplicationz nazwą pakietu wtyczki w pliku manifestu aplikacji wtyczki:
<application>
<library android:name="com.chassis.car.ui.plugin" />
...
</application>
- Skonfiguruj regułę tworzenia Soong
android_app(Android.bp) za pomocą flagi AAPTshared-lib, która służy do tworzenia biblioteki udostępnionej:
android_app {
...
aaptflags: ["--shared-lib"],
...
}
Zależności od bibliotek udostępnionych
W przypadku każdej aplikacji w systemie, która korzysta z biblioteki Car UI, dodaj tag
uses-library w pliku manifestu aplikacji w tagu
application z nazwą pakietu wtyczki:
<manifest>
<application
android:name=".MyApp"
...>
<uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
...
</application>
</manifest>
Instalowanie wtyczki
Wtyczki MUSZĄ być preinstalowane na partycji systemowej przez dodanie modułu
do PRODUCT_PACKAGES. Preinstalowany pakiet można zaktualizować podobnie jak każdą inną zainstalowaną aplikację.
Jeśli aktualizujesz istniejącą wtyczkę w systemie, wszystkie aplikacje korzystające z tej wtyczki zostaną automatycznie zamknięte. Po ponownym otwarciu przez użytkownika będą one zawierać zaktualizowane zmiany. Jeśli aplikacja nie była uruchomiona, przy następnym uruchomieniu będzie zawierać zaktualizowaną wtyczkę.
Podczas instalowania wtyczki za pomocą Android Studio należy wziąć pod uwagę kilka dodatkowych kwestii. W momencie pisania tego tekstu w procesie instalacji aplikacji w Android Studio występuje błąd, który powoduje, że aktualizacje wtyczki nie są stosowane. Można to naprawić, wybierając opcję Zawsze instaluj za pomocą menedżera pakietów (wyłącza optymalizacje wdrażania w Androidzie 11 i nowszych wersjach) w konfiguracji kompilacji wtyczki.
Ponadto podczas instalowania wtyczki Android Studio zgłasza błąd, że nie może znaleźć głównej aktywności do uruchomienia. Jest to oczekiwane, ponieważ wtyczka nie ma żadnych aktywności (z wyjątkiem pustego intencji używanego do rozwiązywania intencji). Aby wyeliminować ten błąd, w konfiguracji kompilacji zmień opcję Uruchom na Nic.
Rysunek 1. Konfiguracja wtyczki w Android Studio
Wtyczka proxy
Dostosowywanie aplikacji korzystających z biblioteki Car UI wymaga RRO, które jest kierowane na każdą konkretną aplikację, która ma zostać zmodyfikowana, w tym gdy dostosowania są identyczne w różnych aplikacjach. Oznacza to, że wymagane jest RRO na aplikację. Sprawdź, które aplikacje korzystają z biblioteki Car UI.
Wtyczka proxy biblioteki Car UI to przykładowa biblioteka udostępniona wtyczki, która przekazuje implementacje komponentów do statycznej wersji biblioteki Car UI. Tę wtyczkę można kierować za pomocą RRO, które może służyć jako pojedynczy punkt dostosowywania aplikacji korzystających z biblioteki Car UI bez konieczności implementowania funkcjonalnej wtyczki. Więcej informacji o RRO znajdziesz w artykule Zmienianie wartości zasobów aplikacji w czasie działania.
Wtyczka proxy jest tylko przykładem i punktem początkowym do dostosowywania za pomocą wtyczki. Aby dostosować elementy poza RRO, można zaimplementować podzbiór komponentów wtyczki i użyć wtyczki proxy do pozostałych lub zaimplementować wszystkie komponenty wtyczki od zera.
Chociaż wtyczka proxy zapewnia pojedynczy punkt dostosowywania RRO dla aplikacji, aplikacje, które zrezygnują z używania wtyczki, nadal będą wymagać RRO, które jest kierowane bezpośrednio na samą aplikację.
Implementowanie interfejsów Plugin API
Głównym punktem wejścia do wtyczki jest klasa com.android.car.ui.plugin.PluginVersionProviderImpl. Wszystkie wtyczki muszą zawierać klasę o tej samej nazwie i nazwie pakietu. Ta klasa musi mieć domyślny konstruktor i implementować interfejs PluginVersionProviderOEMV1.
Wtyczki CarUi muszą działać z aplikacjami starszymi lub nowszymi niż wtyczka. Aby to ułatwić, wszystkie interfejsy Plugin API są wersjonowane za pomocą V# na końcu nazwy klasy. Jeśli nowa wersja biblioteki Car UI zostanie wydana z nowymi funkcjami, będą one częścią komponentu w wersji V2. Biblioteka Car UI dokłada wszelkich starań, aby nowe funkcje działały w zakresie starszego komponentu wtyczki.
Na przykład przez przekształcenie nowego typu przycisku na pasku narzędzi w MenuItems.
Aplikacja ze starszą wersją biblioteki Car UI nie może jednak dostosować się do nowej wtyczki napisanej przy użyciu nowszych interfejsów API. Aby rozwiązać ten problem, umożliwiamy wtyczkom zwracanie różnych implementacji na podstawie wersji interfejsu OEM API obsługiwanej przez aplikacje.
PluginVersionProviderOEMV1 ma jedną metodę:
Object getPluginFactory(int maxVersion, Context context, String packageName);
Ta metoda zwraca obiekt, który implementuje najwyższą wersję PluginFactoryOEMV# obsługiwaną przez wtyczkę, ale nie większą niż maxVersion. Jeśli wtyczka nie ma implementacji PluginFactory w tej wersji, może zwrócić wartość null. W takim przypadku używana jest implementacja komponentów CarUi połączona statycznie.
Aby zachować zgodność wsteczną z aplikacjami skompilowanymi ze starszymi wersjami statycznej biblioteki Car Ui, zalecamy obsługę maxVersion w wersji 2, 5 i nowszych w implementacji klasy PluginVersionProvider wtyczki. Wersje 1, 3 i 4 nie są obsługiwane. Więcej
informacji znajdziesz w
PluginVersionProviderImpl.
PluginFactory to interfejs, który tworzy wszystkie inne komponenty CarUi. Określa też, której wersji interfejsów należy używać. Jeśli wtyczka nie ma implementować żadnego z tych komponentów, może zwrócić wartość null w funkcji tworzenia (z wyjątkiem paska narzędzi, który ma osobną funkcję customizesBaseLayout()).
pluginFactory ogranicza, których wersji komponentów CarUi można używać razem. Na przykład nigdy nie będzie pluginFactory, która może utworzyć wersję 100 Toolbar i wersję 1 RecyclerView, ponieważ nie ma gwarancji, że różne wersje komponentów będą ze sobą współpracować. Aby używać paska narzędzi w wersji 100, deweloperzy muszą udostępnić implementację wersji pluginFactory, która tworzy pasek narzędzi w wersji 100. Ogranicza to opcje dotyczące wersji innych komponentów, które można utworzyć. Wersje innych komponentów mogą się różnić. Na przykład pluginFactoryOEMV100 może utworzyć ToolbarControllerOEMV100 i RecyclerViewOEMV70.
Pasek narzędzi
Układ podstawowy
Pasek narzędzi i „układ podstawowy” są ze sobą ściśle powiązane, dlatego funkcja, która tworzy pasek narzędzi, nazywa się installBaseLayoutAround.
Układ podstawowy
to koncepcja, która umożliwia umieszczenie paska narzędzi w dowolnym miejscu wokół
treści aplikacji, aby można było umieścić pasek narzędzi u góry lub u dołu aplikacji, pionowo
po bokach, a nawet okrągły pasek narzędzi otaczający całą aplikację. Można to
zrobić, przekazując widok do installBaseLayoutAround, aby pasek narzędzi lub układ
podstawowy mógł się wokół niego owinąć.
Wtyczka powinna pobrać podany widok, odłączyć go od elementu nadrzędnego, rozwinąć własny układ wtyczki w tym samym indeksie elementu nadrzędnego i z tymi samymi LayoutParams co widok, który został właśnie odłączony, a następnie ponownie dołączyć widok wewnątrz układu, który został właśnie rozwinięty. Rozwinięty układ będzie zawierać pasek narzędzi, jeśli zażąda tego aplikacja.
Aplikacja może zażądać układu podstawowego bez paska narzędzi. W takim przypadku installBaseLayoutAround powinna zwrócić wartość null. W przypadku większości wtyczek to wszystko, co trzeba zrobić, ale jeśli autor wtyczki chce zastosować np. dekorację wokół krawędzi aplikacji, można to zrobić za pomocą układu podstawowego. Te dekoracje są szczególnie przydatne w przypadku urządzeń z ekranami innymi niż prostokątne, ponieważ mogą one umieścić aplikację w prostokątnym obszarze i dodać przejścia do obszaru innego niż prostokątny.
installBaseLayoutAround otrzymuje też Consumer<InsetsOEMV1>. Ten odbiorca może służyć do informowania aplikacji, że wtyczka częściowo zakrywa treść aplikacji (za pomocą paska narzędzi lub w inny sposób). Aplikacja będzie wtedy wiedzieć, że ma nadal rysować w tym obszarze, ale nie będzie w nim umieszczać żadnych krytycznych komponentów, z którymi użytkownik może wchodzić w interakcje. Ten efekt jest używany w naszym projekcie referencyjnym, aby pasek narzędzi był półprzezroczysty, a listy przewijały się pod nim. Gdyby ta funkcja nie została zaimplementowana, pierwszy element na liście byłby ukryty pod paskiem narzędzi i nie można by go kliknąć. Jeśli ten efekt nie jest potrzebny, wtyczka może zignorować odbiorcę.
Rysunek 2. Przewijanie treści pod paskiem narzędzi
Z perspektywy aplikacji, gdy wtyczka wysyła nowe wcięcia, będzie je otrzymywać z dowolnych aktywności lub fragmentów, które implementują InsetsChangedListener. Jeśli
aktywność lub fragment nie implementuje InsetsChangedListener, biblioteka Car Ui
domyślnie będzie obsługiwać wcięcia, stosując je jako dopełnienie do
Activity lub FragmentActivity zawierającego fragment. Biblioteka domyślnie nie stosuje wcięć do fragmentów. Oto przykładowy fragment implementacji, który stosuje wcięcia jako dopełnienie do RecyclerView w aplikacji:
public class MainActivity extends Activity implements InsetsChangedListener {
@Override
public void onCarUiInsetsChanged(Insets insets) {
CarUiRecyclerView rv = requireViewById(R.id.recyclerview);
rv.setPadding(insets.getLeft(), insets.getTop(),
insets.getRight(), insets.getBottom());
}
}
Na koniec wtyczka otrzymuje wskazówkę fullscreen, która służy do określenia, czy widok, który ma zostać opakowany, zajmuje całą aplikację, czy tylko niewielką sekcję.
Można jej użyć, aby uniknąć stosowania niektórych dekoracji wzdłuż krawędzi, które mają sens tylko wtedy, gdy pojawiają się wzdłuż krawędzi całego ekranu. Przykładową aplikacją, która używa układów podstawowych innych niż pełnoekranowe, są Ustawienia. Każdy panel układu dwupanelowego ma własny pasek narzędzi.
Ponieważ oczekuje się, że installBaseLayoutAround zwróci wartość null, gdy toolbarEnabled ma wartość false, wtyczka musi zwrócić wartość false z customizesBaseLayout, aby wskazać, że nie chce dostosowywać układu podstawowego.
Aby w pełni obsługiwać sterowanie obrotowe, układ podstawowy musi zawierać FocusParkingView i FocusArea. Te widoki można pominąć na urządzeniach, które nie obsługują sterowania obrotowego. FocusParkingView/FocusAreas są zaimplementowane w
statycznej bibliotece CarUi, dlatego do udostępniania fabryk do
tworzenia widoków z kontekstów używana jest funkcja setRotaryFactories.
Konteksty używane do tworzenia widoków Focus muszą być kontekstem źródłowym, a nie kontekstem wtyczki. FocusParkingView powinien znajdować się jak najbliżej pierwszego widoku w drzewie, ponieważ jest to widok, który jest zaznaczany, gdy użytkownik nie powinien widzieć żadnego zaznaczenia. FocusArea musi otaczać pasek narzędzi w układzie podstawowym, aby wskazać, że jest to strefa sterowania obrotowego. Jeśli FocusArea nie jest dostępny, użytkownik nie może nawigować do żadnych przycisków na pasku narzędzi za pomocą kontrolera obrotowego.
Kontroler paska narzędzi
Implementacja rzeczywistego ToolbarController powinna być znacznie prostsza niż implementacja układu podstawowego. Jego zadaniem jest pobieranie informacji przekazywanych do jego setterów i wyświetlanie ich w układzie podstawowym. Informacje o większości metod znajdziesz w Javadoc. Niektóre bardziej złożone metody opisujemy poniżej.
getImeSearchInterface służy do wyświetlania wyników wyszukiwania w oknie IME (klawiatury). Może to być przydatne do wyświetlania lub animowania wyników wyszukiwania obok klawiatury, na przykład jeśli klawiatura zajmuje tylko połowę ekranu. Większość funkcji jest zaimplementowana w statycznej bibliotece CarUi. Interfejs wyszukiwania we wtyczce udostępnia tylko metody, za pomocą których biblioteka statyczna może pobierać wywołania zwrotne TextView i onPrivateIMECommand. Aby to obsługiwać, wtyczka powinna używać podklasy TextView, która zastępuje onPrivateIMECommand i przekazuje wywołanie do podanego odbiornika jako TextView paska wyszukiwania.
setMenuItems po prostu wyświetla MenuItems na ekranie, ale będzie wywoływana zaskakująco często. Ponieważ interfejs Plugin API dla MenuItems jest niezmienny, za każdym razem, gdy MenuItem zostanie zmieniony, nastąpi wywołanie setMenuItems. Może się to zdarzyć w przypadku tak prostych czynności jak kliknięcie przez użytkownika przełącznika MenuItem, które spowodowało przełączenie przełącznika. Zarówno ze względu na wydajność, jak i animację zalecamy obliczanie różnicy między starą a nową listą MenuItems i aktualizowanie tylko tych widoków, które faktycznie się zmieniły. MenuItems udostępniają pole key, które może w tym pomóc, ponieważ klucz powinien być taki sam w różnych wywołaniach setMenuItems dla tego samego MenuItem.
Konteksty
Wtyczka musi zachować ostrożność podczas używania kontekstów, ponieważ istnieją zarówno konteksty wtyczki, jak i „źródłowe”. Kontekst wtyczki jest podawany jako argument do getPluginFactory i jest jedynym kontekstem, który zawiera zasoby wtyczki. Oznacza to, że jest to jedyny kontekst, którego można użyć do rozwinięcia układów we wtyczce.
Kontekst wtyczki może jednak nie mieć prawidłowo skonfigurowanych ustawień. Aby uzyskać prawidłową konfigurację, udostępniamy konteksty źródłowe w metodach, które tworzą komponenty. Kontekst źródłowy jest zwykle aktywnością, ale w niektórych przypadkach może to być też usługa lub inny komponent Androida. Aby używać konfiguracji z kontekstu źródłowego z zasobami z kontekstu wtyczki, należy utworzyć nowy kontekst za pomocą createConfigurationContext. Jeśli nie użyjesz prawidłowej konfiguracji, nastąpi naruszenie trybu ścisłego Androida, a rozwinięte widoki mogą mieć nieprawidłowe wymiary.
Context layoutInflationContext = pluginContext.createConfigurationContext(
sourceContext.getResources().getConfiguration());
Zmiany trybu
Niektóre wtyczki mogą obsługiwać wiele trybów swoich komponentów, takich jak tryb sportowy i tryb ekonomiczny, które różnią się wizualnie. CarUi nie obsługuje wbudowanych funkcji tego typu, ale nic nie stoi na przeszkodzie, aby wtyczka zaimplementowała je w całości wewnętrznie. Wtyczka może monitorować dowolne warunki, aby określić, kiedy należy przełączyć tryby, np. nasłuchiwać transmisji. Wtyczka nie może wywołać zmiany konfiguracji, aby zmienić tryby, ale nie zalecamy polegania na zmianach konfiguracji, ponieważ ręczne aktualizowanie wyglądu każdego komponentu jest płynniejsze dla użytkownika i umożliwia przejścia, które nie są możliwe w przypadku zmian konfiguracji.
Jetpack Compose
Wtyczki można implementować za pomocą Jetpack Compose, ale jest to funkcja w wersji alfa i nie należy jej uważać za stabilną.
Wtyczki mogą używać
ComposeView
, aby utworzyć powierzchnię obsługującą Compose, na której można renderować. Ten ComposeView będzie zwracany do aplikacji z metody getView w komponentach.
Jednym z głównych problemów związanych z używaniem ComposeView jest to, że ustawia tagi w widoku głównym w układzie, aby przechowywać zmienne globalne, które są współdzielone przez różne ComposeView w hierarchii. Ponieważ identyfikatory zasobów wtyczki nie są oddzielone od identyfikatorów aplikacji, może to powodować konflikty, gdy zarówno aplikacja, jak i wtyczka ustawiają tagi w tym samym widoku. Poniżej znajdziesz niestandardowy
ComposeViewWithLifecycle, który przenosi te zmienne globalne do
ComposeView. Powtarzamy, że nie należy tego uważać za stabilne rozwiązanie.
ComposeViewWithLifecycle:
class ComposeViewWithLifecycle @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private val lifeCycle = LifecycleRegistry(this)
private val modelStore = ViewModelStore()
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private var composeView: ComposeView? = null
private var content = @Composable {}
init {
ViewTreeLifecycleOwner.set(this, this)
ViewTreeViewModelStoreOwner.set(this, this)
ViewTreeSavedStateRegistryOwner.set(this, this)
compositionContext = createCompositionContext()
}
fun setContent(content: @Composable () -> Unit) {
this.content = content
composeView?.setContent(content)
}
override fun getLifecycle(): Lifecycle {
return lifeCycle
}
override fun getViewModelStore(): ViewModelStore {
return modelStore
}
override fun getSavedStateRegistry(): SavedStateRegistry {
return savedStateRegistryController.savedStateRegistry
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
savedStateRegistryController.performRestore(Bundle())
lifeCycle.currentState = Lifecycle.State.RESUMED
composeView = ComposeView(context)
composeView?.setContent(content)
addView(composeView, LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
lifeCycle.currentState = Lifecycle.State.DESTROYED
modelStore.clear()
removeAllViews()
composeView = null
}
// Exact copy of View.createCompositionContext() in androidx's WindowRecomposer.android.kt
private fun createCompositionContext(): CompositionContext {
val currentThreadContext = AndroidUiDispatcher.CurrentThread
val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
PausableMonotonicFrameClock(it).apply { pause() }
}
val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
val recomposer = Recomposer(contextWithClock)
val runRecomposeScope = CoroutineScope(contextWithClock)
val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
"ViewTreeLifecycleOwner not found from $this"
}
viewTreeLifecycleOwner.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
@Suppress("NON_EXHAUSTIVE_WHEN")
when (event) {
Lifecycle.Event.ON_CREATE ->
// Undispatched launch since we've configured this scope
// to be on the UI thread
runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
Lifecycle.Event.ON_DESTROY -> {
recomposer.cancel()
}
}
}
)
return recomposer
}
// TODO: ComposeViewWithLifecycle should handle saving state and other lifecycle things
// override fun onSaveInstanceState(): Parcelable? {
// val superState = super.onSaveInstanceState()
// val bundle = Bundle()
// savedStateRegistryController.performSave(bundle)
// }
}