Komponent Nawigacja obsługuje aplikacje Jetpack Compose. Możesz przechodzić między elementami kompozycyjnymi, korzystając jednocześnie z infrastruktury i funkcji komponentu Nawigacja.
Konfiguracja
Aby obsługiwać Compose, użyj w pliku build.gradle
modułu aplikacji tej zależności:
dependencies { def nav_version = "2.8.8" implementation "androidx.navigation:navigation-compose:$nav_version" }
dependencies { val nav_version = "2.8.8" implementation("androidx.navigation:navigation-compose:$nav_version") }
Rozpocznij
Podczas implementowania nawigacji w aplikacji zaimplementuj hosta, wykres i sterownik nawigacji. Więcej informacji znajdziesz w artykule o nawigacji.
Tworzenie kontrolera nawigacji
Informacje o tworzeniu elementu NavController
w sekcji „Compose” (Utwórz) znajdziesz w sekcji Tworzenie kontrolera nawigacji.
Tworzenie elementu NavHost
Informacje o tworzeniu NavHost
w sekcji Kompozycja znajdziesz w artykule Projektowanie grafu nawigacji.
Przejdź do komponentu.
Informacje o nawigowaniu do komponentu znajdziesz w sekcji Nawigowanie do miejsca docelowego w dokumentacji architektury.
Nawigacja za pomocą argumentów
Informacje o przekazywaniu argumentów między miejscami docelowymi do łączenia znajdziesz w sekcji „Tworzenie” w artykule Projektowanie wykresu nawigacji.
Pobieranie złożonych danych podczas nawigacji
Zdecydowanie zalecamy, aby podczas nawigacji nie przekazywać skomplikowanych obiektów danych, a zamiast tego przekazywać jako argumenty podczas wykonywania działań nawigacyjnych minimalną liczbę niezbędnych informacji, takich jak unikalny identyfikator lub inna forma identyfikatora:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
Złożone obiekty powinny być przechowywane jako dane w jednym źródle prawdy, np. w warstwie danych. Po dotarciu do miejsca docelowego możesz pobrać wymagane informacje z jednego źródła danych, używając przekazanego identyfikatora. Aby pobrać argumenty w funkcji ViewModel
, która odpowiada za dostęp do warstwy danych, użyj funkcji SavedStateHandle
funkcji ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
Takie podejście pomaga zapobiegać utracie danych podczas zmian konfiguracji i niezgodnościom, gdy dany obiekt jest aktualizowany lub modyfikowany.
Pełniejsze wyjaśnienie, dlaczego nie należy przekazywać złożonych danych jako argumentów, oraz lista obsługiwanych typów argumentów znajdują się w artykule Przesyłanie danych między miejscami docelowymi.
Precyzyjne linki
Narzędzie Navigation Compose obsługuje również precyzyjne linki, które można definiować w ramach funkcji composable()
. Jego parametr deepLinks
przyjmuje listę obiektów NavDeepLink
, które można szybko utworzyć za pomocą metody navDeepLink()
:
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Dzięki tym precyzyjnym linkom możesz powiązać określony adres URL, działanie lub typ mime z komponewalnym. Domyślnie te precyzyjne linki nie są widoczne dla aplikacji zewnętrznych. Aby udostępnić te precyzyjne linki zewnętrznie, musisz dodać odpowiednie elementy <intent-filter>
do pliku manifest.xml
aplikacji. Aby włączyć precyzyjny link z poprzedniego przykładu, w elemencie <activity>
w pliku manifestu dodaj:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
Gdy inna aplikacja uruchamia precyzyjny link, nawigacja automatycznie prowadzi do tego elementu kompozycyjnego.
Tych samych precyzyjnych linków możesz też używać do tworzenia PendingIntent
z odpowiednim precyzyjnym linkiem z komponowalnego:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
Możesz potem użyć tego deepLinkPendingIntent
tak samo jak każdego innego PendingIntent
, aby otworzyć aplikację w miejscu docelowym precyzyjnego linku.
Nawigacja zagnieżdżona
Informacje o tworzeniu zagnieżdżonych diagramów nawigacji znajdziesz w artykule Zagnieżdżone diagramy.
Integracja z dolnym paskiem nawigacji
Definiując NavController
na wyższym poziomie hierarchii komponentów, możesz połączyć komponent Nawigacja z innymi komponentami, takimi jak komponent dolnej nawigacji. Umożliwia to poruszanie się po ikonach na dolnym pasku.
Aby używać komponentów BottomNavigation
i BottomNavigationItem
, dodaj do aplikacji na Androida zależność androidx.compose.material
.
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
dependencies { implementation("androidx.compose.material:material:1.7.5") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Aby połączyć elementy na dolnym pasku nawigacji z trasami na diagramie nawigacji, zalecamy zdefiniowanie klasy, takiej jak TopLevelRoute
, która ma klasę trasy i ikonę.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
Następnie umieść te trasy na liście, której może używać BottomNavigationItem
:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
W komponentach BottomNavigation
pobieraj bieżącą wartość NavBackStackEntry
za pomocą funkcji currentBackStackEntryAsState()
. Ta pozycja zapewnia dostęp do bieżącej wersji NavDestination
. Wybrany stan każdego elementu BottomNavigationItem
można następnie określić, porównując trasę elementu z trasą bieżącego miejsca docelowego i jego nadrzędnych miejsc docelowych, aby obsłużyć przypadki, gdy korzystasz z nawierającej nawigacji w hierarchii NavDestination
.
Trasa elementu służy też do łączenia funkcji lambda onClick
z wywołaniem funkcji navigate
. Dzięki temu kliknięcie elementu powoduje przejście do niego. Dzięki flagom saveState
i restoreState
stan tego elementu i stos elementów do cofnięcia są prawidłowo zapisywane i przywracane podczas przełączania się między elementami nawigacji u dołu ekranu.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
Tutaj korzystasz z metody NavController.currentBackStackEntryAsState()
, aby wyodrębnić stan navController
z funkcji NavHost
i przekazać go komponentowi BottomNavigation
. Oznacza to, że BottomNavigation
automatycznie otrzymuje najbardziej aktualny stan.
Interoperacyjność
Jeśli chcesz użyć komponentu Nawigacja w komponencie Compose, masz 2 opcje:
- Zdefiniuj graf nawigacji za pomocą komponentu Nawigacja dla fragmentów.
- Zdefiniuj graf nawigacji za pomocą elementu
NavHost
w sekcji Compose (Tworzenie) przy użyciu miejsc docelowych Compose. Jest to możliwe tylko wtedy, gdy wszystkie ekrany w diagramie nawigacji są składanymi elementami.
Dlatego w przypadku aplikacji mieszanych, które łączą komponenty Compose i View, zalecamy używanie komponentu nawigacji opartej na fragmentach. Fragmenty będą zawierać ekrany oparte na widokach, ekrany Compose oraz ekrany, które korzystają zarówno z widoków, jak i Compose. Gdy zawartość każdego fragmentu znajdzie się w narzędziu Compose, należy połączyć wszystkie ekrany za pomocą funkcji Navigation Compose i usunąć wszystkie fragmenty.
Przechodzenie z tworzenia i nawigacji we fragmentach
Aby zmienić miejsca docelowe w kodzie usługi Compose, musisz ujawnić zdarzenia, które mogą być przekazywane do dowolnego komponentu w hierarchii i mogą być przez niego wywoływane:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
W fragmentie łączysz komponent Nawigacja oparty na fragmencie z komponentem Compose. Aby to zrobić, znajdź element NavController
i przejdź do miejsca docelowego:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Możesz też przekazać NavController
w hierarchii Compose.
Jednak ujawnianie prostych funkcji jest znacznie bardziej wielokrotnego użytku i testowalne.
Testowanie
Odłącz kod nawigacji od miejsc docelowych w komponowanych treściach, aby umożliwić testowanie poszczególnych treści w komponowanych treściach oddzielnie od NavHost
.
Oznacza to, że nie należy przekazywać wartości navController
bezpośrednio do żadnej funkcji kompozytowej, a zamiast tego należy przekazywać wywołania zwrotne nawigacji jako parametry. Dzięki temu wszystkie komponenty można testować osobno, ponieważ nie wymagają one instancji navController
w testach.
Poziom pośrednictwa zapewniany przez funkcję lambda composable
pozwala oddzielić kod nawigacji od samego komponentu. Ma to 2 kierunki:
- Przekazuj do funkcji kompozycyjnej tylko przeanalizowane argumenty
- Przekazywanie funkcji lambda, które powinny być wywoływane przez komponent, do nawigacji, a nie do
NavController
.
Na przykład kompozyt ProfileScreen
, który przyjmuje jako dane wejściowe userId
i pozwala użytkownikom przejść na stronę profilu znajomego, może mieć podpis:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dzięki temu funkcja kompozycyjna ProfileScreen
działa niezależnie od Nawigacji, co umożliwia testowanie jej niezależnie. Funkcja lambda composable
ujęłaby minimalną logikę potrzebną do połączenia interfejsów API nawigacji z komponowalnym:
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
Zalecamy napisanie testów, które obejmują wymagania dotyczące nawigacji w aplikacji. W tym celu przetestuj NavHost
, czyli czynności nawigacyjne przekazywane do komponentów, a także poszczególne komponenty ekranu.
Testowanie urządzenia NavHost
Aby rozpocząć testowanie NavHost
, dodaj tę zależność testowania nawigacji:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Zawiń funkcję NavHost
aplikacji w komponent, który akceptuje jako parametr funkcję NavHostController
.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Teraz możesz przetestować AppNavHost
i całą logikę nawigacji zdefiniowaną w NavHost
, przekazując instancję artefaktu testowania nawigacji TestNavHostController
. Test UI, który sprawdza miejsce docelowe na początku NavHost
, wygląda tak:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
Testowanie działań związanych z nawigacją
Implementację nawigacji możesz testować na kilka sposobów: klikając elementy interfejsu, a następnie weryfikując wyświetlone miejsce docelowe lub porównując oczekiwaną trasę z obecną.
Ponieważ chcesz przetestować implementację konkretnej aplikacji, kliknięcia w interfejsie są preferowane. Aby dowiedzieć się, jak testować te funkcje razem z poszczególnymi funkcjami składanymi, zapoznaj się z tym testem w Jetpack Compose w Codelab.
Możesz też użyć navController
, aby sprawdzić swoje twierdzenia, porównując bieżącą trasę z oczekiwaną, korzystając z funkcji navController
:currentBackStackEntry
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Więcej wskazówek dotyczących podstaw testowania w Compose znajdziesz w artykule Testowanie układu Compose i w codelab Testowanie w Jetpack Compose. Więcej informacji o zaawansowanym testowaniu kodu nawigacji znajdziesz w przewodniku Testowanie nawigacji.
Więcej informacji
Więcej informacji o komponencie Nawigacja w Jetpacku znajdziesz w artykule Pierwsze kroki z komponentem Nawigacja lub w laboratorium kodu Nawigacja w Jetpack Compose.
Aby dowiedzieć się, jak zaprojektować nawigację w aplikacji, aby dostosowywała się do różnych rozmiarów, orientacji i formatów ekranu, przeczytaj artykuł Nawigacja w elastycznym interfejsie użytkownika.
Więcej o bardziej zaawansowanej implementacji nawigacji za pomocą Compose w aplikacji modułowej, w tym takich koncepcji jak zagnieżdżone wykresy i dolny pasek nawigacyjny, znajdziesz w aplikacji Now in Android na GitHubie.
Próbki
Mir 2: Return of the King to wysokiej jakości gra mobilna o postaciach z Legendy, która została autoryzowana przez Actoz Soft i oparta na silniku Unity. Ta gra nie tylko doskonale odtwarza wrażenia z Mir 2, koreańskiej gry fantasy typu MMORPG, ale Wuthering Waves to fabularna gra akcji z grafiką wysokiej jakości stworzona przez Kuro Games.
Optymalizacja zużycia energii jest bardzo ważna, aby zapewnić użytkownikom najwyższą jakość wrażeń podczas długich sesji grania. Android Studio wprowadziło Godot Engine to popularny wieloplatformowy silnik gier typu open source, który zapewnia solidne wsparcie dla Androida. Godot może służyć do tworzenia gier praktycznie dowolnego gatunku. Umożliwia tworzenie grafiki 2D i 3D. Wersja 4 Godota wprowadziła Dynamiczny interfejs wydajności Androida (ADPF) to potężne narzędzie Google przeznaczone dla deweloperów, którzy chcą zoptymalizować wydajność swoich aplikacji. Za pomocą interfejsów API dotyczących temperatury aplikacja ADPF udostępnia informacje NCSoft Lineage W to wieloosobowa gra fabularna (MMORPG) opracowana przez NCSoft. Ta gra dziedziczy tradycję oryginalnej gry Lineage W, oferując środowisko, w którym gracze z całego świata mogą współpracować i rywalizować na serwerach globalnych. Poprawa wydajności i zarządzanie temperaturą są niezbędne do tworzenia udanych gier na Androida. Deweloperzy musieli do tej pory rozwiązywać te problemy przez zmniejszenie wierności odwzorowania lub dalszą optymalizację renderera.
Zmiany te są zwykle Call of Duty: Warzone Mobile to gra akcji z perspektywy pierwszej osoby należąca do popularnej serii Call of Duty. Wersja mobilna tej popularnej gry na konsole i komputery korzysta z interfejsów API na niskim poziomie, aby zapewnić graczom świetne Summoners War: Chronicles to mobilna gra MMORPG od południowokoreańskiego dewelopera Com2uS, która została wydana na całym świecie w marcu 2023 roku. Do tej pory gra Summoners War zarobiła ponad 2, 7 miliarda dolarów i została pobrana ponad Summoners War: Chronicles US(WW) i KR autorstwa Com2uS korzysta z Vulkana wyłącznie do renderowania na Androidzie, co zapewnia wzrost wydajności nawet o 30%. Vulkan to nowoczesny, wieloplatformowy interfejs API do obsługi grafiki 3D, który został Ares: Rise of Guardians to science fiction MMORPG na urządzenia mobilne i PC stworzona przez koreańskie studio Second Dive, które słynie z doświadczenia w tworzeniu gier z serii RPG akcji. Gra została opublikowana przez Kakao Games. Akcja gry toczy Cat Daddy Games to w 100% należące do 2K studio z Kirkland w stanie Waszyngton, które jest twórcą gry NBA 2K Mobile.
Zespół chciał poprawić ogólną jakość i stabilność gier, w szczególności poprzez zmniejszenie liczby błędów typu „Aplikacja nie Devsisters to globalny twórca i wydawca gier mobilnych, który tworzy gry rekreacyjne oparte na chronionej prawem własności intelektualnej marki Cookie Run. Do najpopularniejszych gier studia należą Cookie Run: OvenBreak (gra zręcznościowa) i Cookie NEW STATE Mobile to gra typu battle royale firmy Krafton, która została wydana w listopadzie 2021 r. na całym świecie. W pierwszym miesiącu od premiery osiągnęła ponad 45 mln pobrania. KRAFTON, Inc. to grupa niezależnych studiów tworzących gry, które Spokko to polska grupa ambitnych twórców, którzy pracują nad bardzo wymagającym projektem. Chociaż jest częścią rodziny CD PROJEKT, Spokko jest niezależną firmą, która przeniosła wspaniały świat The Witcher: Monster Slayer na smartfony. Wiedźmin: Cat Daddy Games to w całości należące do 2K studio z siedzibą w Kirkland w stanie Waszyngton. Zespoły odpowiedzialne za gry NBA 2K Mobile, NBA SuperCard i WWE SuperCard szukały rozwiązania, które pozwoliłoby im poprawić ogólną jakość gier dla Electronic Arts (EA) to firma zajmująca się produkcją gier z siedzibą w Kalifornii w Stanach Zjednoczonych. Firma produkuje wiele gier z różnych gatunków, takich jak sport, akcja, wyścigi i symulacje. Studio deweloperskie Firemonkeys należące do EA Unreal Engine to opracowany przez Epic Games silnik do tworzenia gier, który zapewnia twórcom z różnych branż swobodę i kontrolę nad tworzeniem zaawansowanych treści rozrywkowych, atrakcyjnych wizualizacji i wciągających światów wirtualnych. Niektóre Deweloper z Warszawy, studio CD Projekt RED (CDPR), przekształciło minigrę z gry Wiedźmin 3 w odrębną grę bezpłatne GWINT: Wiedźmińska Gra Karciana, która w marcu 2020 r. została udostępniona w Google Play.
Z powodu dużego początkowego rozmiaru pliku Od ponad 20 lat firma Gameloft tworzy innowacyjne gry na platformy cyfrowe, od gier mobilnych po gry na PC i konsole. Oprócz własnych, dobrze znanych serii firma Gameloft tworzy gry dla popularnych marek, takich jak LEGO, Universal i Hasbro. W 2000 r. powstała firma Gameloft, która z pasją tworzy gry i chce udostępniać je graczom na całym świecie. To jedni z pionierów w rozwijaniu gier mobilnych. Obecnie mają w portfolio ponad 190 gier. Wiele gier mobilnych Gameloft ma skomplikowane Amerykański deweloper RV AppStudios ma do tej pory ponad 200 milionów pobrań w portfolio gier casualowych, edukacyjnych aplikacji dla dzieci i aplikacji użytkowych. Jako jeden z pierwszych testowała ona funkcję Play Asset Delivery w swojej aplikacji Pixonic,
zespołu twórców gier wideo z siedzibą w Moskwie,
wykorzystują każdą okazję do ulepszenia swoich aplikacji mobilnych i dotarcia do jeszcze większej
graczy. Jedną z najbardziej znanych aplikacji firmy jest Roboty wojenne 12-osobowa rozgrywka Gameloft zawsze stara się być jednym z pierwszych deweloperów, którzy publikują gry na najnowszym przenośnym sprzęcie, aby zapewnić graczom niesamowite wrażenia w ruchu. Dlatego firma wiedziała, że ChromeOS to odpowiednie miejsce dla Asphalt 8:Mir 2 poprawia wydajność renderowania dzięki użyciu biblioteki Frame Pacing
Kuro Games zmniejsza zużycie energii o 9,68% dzięki profilowaniu zużycia energii w Android Studio i ODPM w przypadku gry Wuthering Waves
Optymalizacja Godot Engine Vulkan na Androida
Pierwsze kroki z ramami Android Dynamic Performance Framework (ADPF) w Unreal Engine
NCSoft Lineage W zwiększa stabilną wydajność i zapobiega ograniczaniu temperatury za pomocą ADPF
MediaTek zwiększa dynamiczną wydajność układów SOC Androida
Grafika Call of Duty Warzone Mobile zapewnia lepszą grafikę dzięki interfejsowi Vulkan
Com2uS – Gry Google Play na PC
Com2uS korzysta z interfejsu Vulkan, aby zwiększyć jakość grafiki
Kakao Games zwiększa stabilność liczby klatek na sekundę do 96% dzięki funkcji Android Adaptability
2K zmniejsza częstotliwość błędów ANR o 35% dzięki pakietowi Android Game Development Kit
Cookie Run: OvenBreak oszczędza ponad 200 tys. USD kosztów CDN dzięki Play Asset Delivery
NEW STATE Mobile zmniejsza wykorzystanie GPU o 22% dzięki narzędziu Android GPU Inspector
Wiedźmin: Pogromca Potworów zwiększa swój zasięg dzięki narzędziu Android Performance Tuner
Rozdzielczość 2K zapewnia wyższą jakość grafiki dzięki Play Asset Delivery
Firemonkeys skrócił czas tworzenia i debugowania aplikacji dzięki AGDE
„AGDE is freaking awesome!” (tworzenie aplikacji na Androida za pomocą Unreal Engine)
CD Projekt RED zmniejsza rozmiar aktualizacji o 90% i zwiększa ich częstotliwość o 10% dzięki rozwiązaniu Play Asset Delivery
Gameloft zmniejsza zużycie energii urządzenia o 70%, co przekłada się na wydłużenie czasu gry o 35% dzięki interfejsowi Game Mode API
Gameloft zdobywa o 10% więcej nowych użytkowników dzięki Google Play Asset Delivery
RV AppStudios poprawia utrzymanie użytkowników dzięki Google Play Asset Delivery
Firma Pixonic zwiększyła zaangażowanie użytkowników ChromeOS o 25% dzięki optymalizacji pod kątem dużych ekranów
Gameloft zwiększa przychody 9-krotnie dzięki optymalizacji pod kątem ChromeOS
Obecnie nie ma rekomendacji.
Zaloguj się na swoje konto Google.