Obsługa reklam displayowych

Poniżej znajdziesz informacje o zmianach wprowadzonych w tych obszarach związanych z wyświetlaniem:

Zmienianie rozmiaru aktywności i wyświetlaczy

Aby wskazać, że aplikacja może nie obsługiwać trybu wielu okien ani zmiany rozmiaru, aktywności używają atrybutu resizeableActivity=false. Typowe problemy, które występują w aplikacjach, gdy rozmiar aktywności jest zmieniany:

  • Aktywność może mieć inną konfigurację niż aplikacja lub inny komponent niewizualny. Częstym błędem jest odczytywanie danych wyświetlacza z kontekstu aplikacji. Zwracane wartości nie będą dostosowywane do danych widocznego obszaru, w którym wyświetlana jest aktywność.
  • Aktywność może nie obsługiwać zmiany rozmiaru i ulec awarii, wyświetlać zniekształcony interfejs lub utracić stan z powodu ponownego uruchomienia bez zapisania stanu instancji.
  • Aplikacja może próbować używać bezwzględnych współrzędnych wejściowych (zamiast względnych względem pozycji okna), co może spowodować przerwanie wprowadzania danych w trybie wielu okien.

W Androidzie 7 (i nowszym) aplikację można ustawić na resizeableActivity=false, aby zawsze działała w trybie pełnoekranowym. W takim przypadku platforma uniemożliwia przejście aktywnościom bez możliwości zmiany rozmiaru do trybu podzielonego ekranu. Jeśli użytkownik spróbuje uruchomić aktywność bez możliwości zmiany rozmiaru z poziomu programu uruchamiającego, gdy jest już w trybie podzielonego ekranu, platforma wyłączy ten tryb i uruchomi aktywność bez możliwości zmiany rozmiaru w trybie pełnoekranowym.

Aplikacje, które w pliku manifestu mają ten atrybut ustawiony na false, nie mogą być uruchamiane w trybie wielu okien, chyba że zastosowany jest tryb zgodności:

  • Do procesu, który zawiera wszystkie aktywności i komponenty inne niż aktywności, stosowana jest ta sama konfiguracja.
  • Zastosowana konfiguracja spełnia wymagania CDD dotyczące wyświetlaczy zgodnych z aplikacjami.

W Androidzie 10 platforma nadal uniemożliwia przejście aktywnościom bez możliwości zmiany rozmiaru do trybu podzielonego ekranu, ale można je tymczasowo skalować, jeśli aktywność zadeklarowała stałą orientację lub proporcje obrazu. W przeciwnym razie aktywność zmieni rozmiar, aby wypełnić cały ekran, tak jak w Androidzie 9 i starszych.

Domyślna implementacja stosuje te zasady:

Gdy aktywność jest zadeklarowana jako niezgodna z trybem wielu okien za pomocą atrybutu android:resizeableActivity i spełnia jeden z warunków opisanych poniżej, a zastosowana konfiguracja ekranu musi się zmienić, aktywność i proces są zapisywane z oryginalną konfiguracją, a użytkownik ma możliwość ponownego uruchomienia procesu aplikacji, aby użyć zaktualizowanej konfiguracji ekranu.

  • Stała orientacja dzięki zastosowaniu android:screenOrientation
  • Aplikacja ma domyślne maksymalne lub minimalne proporcje obrazu, ponieważ jest kierowana na poziom API lub deklaruje proporcje obrazu w sposób jawny

Na tym rysunku przedstawiono aktywność bez możliwości zmiany rozmiaru ze zadeklarowanymi proporcjami obrazu. Podczas składania urządzenia okno jest zmniejszane, aby dopasować się do obszaru, przy zachowaniu proporcji obrazu za pomocą odpowiedniego letterboxingu. Ponadto za każdym razem, gdy zmienia się obszar wyświetlania aktywności, użytkownik ma możliwość ponownego uruchomienia aktywności.

Po rozłożeniu urządzenia konfiguracja, rozmiar i proporcje obrazu aktywności nie zmieniają się, ale wyświetla się opcja ponownego uruchomienia aktywności.

Gdy resizeableActivity nie jest ustawiony (lub jest ustawiony na true), aplikacja w pełni obsługuje zmianę rozmiaru.

Implementacja

Aktywność bez możliwości zmiany rozmiaru ze stałą orientacją lub proporcjami obrazu jest w kodzie nazywana trybem zgodności rozmiaru (SCM). Warunek jest zdefiniowany w ActivityRecord#shouldUseSizeCompatMode(). Gdy uruchamiana jest aktywność SCM, konfiguracja związana z ekranem (np. rozmiar lub gęstość) jest ustalana w żądanej konfiguracji zastępowania, więc aktywność nie jest już zależna od bieżącej konfiguracji wyświetlacza.

Jeśli aktywność SCM nie może wypełnić całego ekranu, jest wyrównywana do góry i wyśrodkowywana w poziomie. Granice aktywności są obliczane przez AppWindowToken#calculateCompatBoundsTransformation().

Gdy aktywność SCM używa innej konfiguracji ekranu niż jej kontener (np. rozmiar wyświetlacza jest zmieniany lub aktywność jest przenoszona na inny wyświetlacz), ActivityRecord#inSizeCompatMode() ma wartość true, a SizeCompatModeActivityController (w interfejsie systemu) otrzymuje wywołanie zwrotne, aby wyświetlić przycisk ponownego uruchomienia procesu.

Rozmiary wyświetlaczy i proporcje obrazu

Android 10 obsługuje nowe proporcje obrazu, od wysokich proporcji długich i cienkich ekranów po proporcje 1:1. Aplikacje mogą definiować ApplicationInfo#maxAspectRatio i ApplicationInfo#minAspectRatioekranu, który mogą obsługiwać.

proporcje aplikacji w Androidzie 10,

Rysunek 1. Przykładowe proporcje aplikacji obsługiwane w Androidzie 10

Implementacje urządzeń mogą mieć wyświetlacze dodatkowe o rozmiarach i rozdzielczościach mniejszych niż wymagane przez Androida 9 i starsze (minimalna szerokość lub wysokość 2,5 cala, minimalna wartość 320 DP dla smallestScreenWidth), ale można na nich umieszczać tylko aktywności, które obsługują te małe wyświetlacze.

Aplikacje mogą się na to zdecydować, deklarując minimalny obsługiwany rozmiar, który jest mniejszy lub równy rozmiarowi wyświetlacza docelowego. Aby to zrobić, użyj atrybutów układu aktywności android:minHeight i android:minWidth w pliku AndroidManifest.

Zasady dotyczące wyświetlania

Android 10 oddziela i przenosi niektóre zasady dotyczące wyświetlania z domyślnej implementacji WindowManagerPolicy w PhoneWindowManager do klas dotyczących wyświetlacza, takich jak:

  • Stan i obrót wyświetlacza
  • Śledzenie niektórych klawiszy i zdarzeń ruchu
  • Interfejs systemu i okna dekoracji

W Androidzie 9 (i starszych) klasa PhoneWindowManager obsługiwała zasady dotyczące wyświetlania, stan i ustawienia, obrót, śledzenie ramek okien dekoracji i inne. Android 10 przenosi większość tych funkcji do klasy DisplayPolicy, z wyjątkiem śledzenia obrotu, które zostało przeniesione do DisplayRotation.

Ustawienia okna wyświetlania

W Androidzie 10 ustawienie okienkowe konfigurowane dla każdego wyświetlacza zostało rozszerzone o:

  • Domyślny tryb okienkowy wyświetlacza
  • Wartości overscanu
  • Obrót i tryb obrotu użytkownika
  • Wymuszony rozmiar, gęstość i tryb skalowania
  • Tryb usuwania treści (gdy wyświetlacz jest usuwany)
  • Obsługa dekoracji systemowych i IME

Klasa DisplayWindowSettings zawiera ustawienia tych opcji. Są one zapisywane na dysku w partycji /data w pliku display_settings.xml za każdym razem, gdy ustawienie jest zmieniane. Więcej informacji znajdziesz w sekcjach DisplayWindowSettings.AtomicFileStorage i DisplayWindowSettings#writeSettings(). Producenci urządzeń mogą podać wartości domyślne w pliku display_settings.xml dla konfiguracji urządzenia. Ponieważ jednak plik jest przechowywany w katalogu /data, może być potrzebna dodatkowa logika, aby przywrócić plik, jeśli zostanie usunięty przez wyczyszczenie.

Domyślnie Android 10 używa DisplayInfo#uniqueId jako identyfikatora wyświetlacza podczas zapisywania ustawień. uniqueId powinno być wypełnione w przypadku wszystkich wyświetlaczy. Ponadto jest ono stabilne w przypadku wyświetlaczy fizycznych i sieciowych. Jako identyfikator można też użyć portu wyświetlacza fizycznego, który można ustawić w DisplayWindowSettings#mIdentifier. Przy każdym zapisie zapisywane są wszystkie ustawienia, więc można bezpiecznie zaktualizować klucz używany do wpisu wyświetlacza w pamięci. Więcej informacji znajdziesz w sekcji Statyczne identyfikatory wyświetlaczy.

Ustawienia są zapisywane w katalogu /data z przyczyn historycznych. Pierwotnie były one używane do zapisywania ustawień użytkownika, takich jak obrót wyświetlacza.

Statyczne identyfikatory wyświetlaczy

Android 9 (i starsze) nie udostępniał stabilnych identyfikatorów wyświetlaczy w frameworku. Gdy wyświetlacz został dodany do systemu, dla tego wyświetlacza generowano Display#mDisplayId lub DisplayInfo#displayId, zwiększając statyczny licznik. Jeśli system dodał i usunął ten sam wyświetlacz, powstał inny identyfikator.

Jeśli urządzenie miało kilka wyświetlaczy dostępnych od momentu uruchomienia, wyświetlacze mogły mieć przypisane różne identyfikatory w zależności od czasu. Android 9 (i starsze) zawierał DisplayInfo#uniqueId, ale nie zawierał wystarczających informacji, aby odróżnić wyświetlacze, ponieważ wyświetlacze fizyczne były identyfikowane jako local:0 lub local:1, aby reprezentować wyświetlacz wbudowany i zewnętrzny.

Android 10 zmienia DisplayInfo#uniqueId, aby dodać stabilny identyfikator i odróżnić wyświetlacze lokalne, sieciowe i wirtualne.

Rodzaj wyświetlacza Format
Lokalny
local:<stable-id>
Sieć
network:<mac-address>
Wirtualne
virtual:<package-name-and-name>

Oprócz aktualizacji uniqueId, DisplayInfo.address zawiera DisplayAddress, czyli identyfikator wyświetlacza, który jest stabilny po ponownym uruchomieniu. W Androidzie 10 DisplayAddress obsługuje wyświetlacze fizyczne i sieciowe. DisplayAddress.Physical zawiera stabilny identyfikator wyświetlacza (taki sam jak w uniqueId) i można go utworzyć za pomocą DisplayAddress#fromPhysicalDisplayId().

Android 10 udostępnia też wygodną metodę uzyskiwania informacji o porcie (Physical#getPort()). Tej metody można używać w platformie do statycznego identyfikowania wyświetlaczy. Jest ona używana np. w DisplayWindowSettings). DisplayAddress.Network zawiera adres MAC i można go utworzyć za pomocą DisplayAddress#fromMacAddress().

Te dodatki umożliwiają producentom urządzeń identyfikowanie wyświetlaczy w statycznych konfiguracjach z wieloma wyświetlaczami oraz konfigurowanie różnych ustawień i funkcji systemu za pomocą statycznych identyfikatorów wyświetlaczy, takich jak porty wyświetlaczy fizycznych. Te metody są ukryte i mają być używane tylko w system_server.

Ta metoda zwraca (specyficzny dla platformy) 8-bitowy numer portu, który identyfikuje fizyczne złącze wyjścia wyświetlacza, oraz blob EDID wyświetlacza. SurfaceFlinger wyodrębnia informacje o producencie lub modelu z EDID, aby wygenerować stabilne 64-bitowe identyfikatory wyświetlaczy udostępniane platformie. Jeśli ta metoda nie jest obsługiwana lub wystąpi błąd, SurfaceFlinger wraca do starszego trybu MD, w którym DisplayInfo#address ma wartość null, a DisplayInfo#uniqueId jest zakodowany na stałe, jak opisano powyżej.

Aby sprawdzić, czy ta funkcja jest obsługiwana, uruchom:

$ dumpsys SurfaceFlinger --display-id
# Example output.
Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32"
Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i"
Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"

Używanie więcej niż 2 wyświetlaczy

W Androidzie 9 (i starszych) SurfaceFlinger i DisplayManagerService zakładały istnienie co najwyżej 2 wyświetlaczy fizycznych z zakodowanymi na stałe identyfikatorami 0 i 1.

Od Androida 10 SurfaceFlinger może używać interfejsu Hardware Composer (HWC) API do generowania stabilnych identyfikatorów wyświetlaczy, co umożliwia zarządzanie dowolną liczbą wyświetlaczy fizycznych. Więcej informacji znajdziesz w sekcji Statyczne identyfikatory wyświetlaczy.

Framework może wyszukać token IBinder dla wyświetlacza fizycznego za pomocą SurfaceControl#getPhysicalDisplayToken po uzyskaniu 64-bitowego identyfikatora wyświetlacza z SurfaceControl#getPhysicalDisplayIds lub ze zdarzenia hotplug DisplayEventReceiver.

W Androidzie 10 (i starszych) główny wyświetlacz wewnętrzny ma typ TYPE_INTERNAL, a wszystkie wyświetlacze dodatkowe są oznaczane jako TYPE_EXTERNAL niezależnie od typu połączenia. Dlatego dodatkowe wyświetlacze wewnętrzne są traktowane jako zewnętrzne. Aby obejść ten problem, kod specyficzny dla urządzenia może przyjmować założenia dotyczące DisplayAddress.Physical#getPort, jeśli HWC jest znany, a logika przydzielania portów jest przewidywalna.

To ograniczenie zostało usunięte w Androidzie 11 (i nowszym).

  • W Androidzie 11 pierwszy wyświetlacz zgłoszony podczas uruchamiania jest wyświetlaczem głównym. Typ połączenia (wewnętrzne czy zewnętrzne) nie ma znaczenia. Nadal jednak nie można odłączyć wyświetlacza głównego, co w praktyce oznacza, że musi to być wyświetlacz wewnętrzny. Pamiętaj, że niektóre telefony składane mają kilka wyświetlaczy wewnętrznych.
  • Wyświetlacze dodatkowe są prawidłowo klasyfikowane jako Display.TYPE_INTERNAL lub Display.TYPE_EXTERNAL (wcześniej znane odpowiednio jako Display.TYPE_BUILT_IN i Display.TYPE_HDMI) w zależności od typu połączenia.

Implementacja

W Androidzie 9 i starszych wyświetlacze są identyfikowane za pomocą 32-bitowych identyfikatorów, gdzie 0 to wyświetlacz wewnętrzny, 1 to wyświetlacz zewnętrzny, [2, INT32_MAX] to wirtualne wyświetlacze HWC, a -1 reprezentuje nieprawidłowy wyświetlacz lub wirtualny wyświetlacz inny niż HWC.

Od Androida 10 wyświetlacze mają stabilne i trwałe identyfikatory, co umożliwia SurfaceFlinger i DisplayManagerService śledzenie więcej niż 2 wyświetlaczy i rozpoznawanie wyświetlaczy, które były już widziane. Jeśli HWC obsługuje IComposerClient.getDisplayIdentificationData i udostępnia dane identyfikacyjne wyświetlacza, SurfaceFlinger analizuje strukturę EDID i przydziela stabilne 64-bitowe identyfikatory wyświetlaczy fizycznych i wirtualnych HWC. Identyfikatory są wyrażane za pomocą typu opcji, gdzie wartość null reprezentuje nieprawidłowy wyświetlacz lub wirtualny wyświetlacz inny niż HWC. Bez obsługi HWC SurfaceFlinger wraca do starszego działania z co najwyżej 2 wyświetlaczami fizycznymi.

Skupienie na wyświetlaczu

Aby obsługiwać kilka źródeł danych wejściowych, które jednocześnie są kierowane na poszczególne wyświetlacze, Android 10 można skonfigurować tak, aby obsługiwał wiele okien z fokusem, co najwyżej 1 na wyświetlacz. Jest to przeznaczone tylko dla specjalnych typów urządzeń, gdy wielu użytkowników wchodzi w interakcję z tym samym urządzeniem w tym samym czasie i używa różnych metod lub urządzeń wejściowych, np. Android Automotive.

Zdecydowanie zalecamy, aby nie włączać tej funkcji na zwykłych urządzeniach, w tym na urządzeniach z wieloma ekranami lub używanych do obsługi podobnej do komputerów. Wynika to przede wszystkim z obaw o bezpieczeństwo, które mogą powodować, że użytkownicy będą się zastanawiać, które okno ma fokus.

Wyobraź sobie użytkownika, który wpisuje poufne informacje w polu do wprowadzania danych, np. loguje się do aplikacji bankowej lub wpisuje tekst zawierający poufne informacje. Złośliwa aplikacja może utworzyć wirtualny wyświetlacz poza ekranem, na którym będzie wykonywać aktywność, również z polem tekstowym. Prawidłowe i złośliwe aktywności mają fokus i obie wyświetlają aktywny wskaźnik wprowadzania danych (migający kursor).

Ponieważ jednak dane wejściowe z klawiatury (sprzętowej lub programowej) są wprowadzane tylko do aktywności znajdującej się na wierzchu (czyli do aplikacji, która została uruchomiona jako ostatnia), złośliwa aplikacja może przechwytywać dane wejściowe użytkownika, nawet gdy używa on klawiatury ekranowej na głównym wyświetlaczu urządzenia.

Aby ustawić fokus na wyświetlaczu, użyj com.android.internal.R.bool.config_perDisplayFocusEnabled.

Zgodność

Problem: w Androidzie 9 i starszych w systemie może być tylko 1 okno z fokusem.

Rozwiązanie: w rzadkich przypadkach, gdy 2 okna z tego samego procesu mają fokus, system przyznaje fokus tylko oknu, które znajduje się wyżej w kolejności Z. To ograniczenie zostało usunięte w przypadku aplikacji kierowanych na Androida 10, w których oczekuje się, że będą one obsługiwać jednoczesne fokusowanie wielu okien.

Implementacja

WindowManagerService#mPerDisplayFocusEnabled określa dostępność tej funkcji. W ActivityManager zamiast globalnego śledzenia w zmiennej używa się teraz ActivityDisplay#getFocusedStack(). ActivityDisplay#getFocusedStack() określa fokus na podstawie kolejności Z zamiast buforowania wartości. Dzięki temu tylko 1 źródło, WindowManager, musi śledzić kolejność Z aktywności.

ActivityStackSupervisor#getTopDisplayFocusedStack() stosuje podobne podejście w przypadkach, gdy trzeba zidentyfikować stos z fokusem znajdujący się na wierzchu w systemie. Stosy są przeszukiwane od góry do dołu w poszukiwaniu pierwszego kwalifikującego się stosu.

InputDispatcher może teraz mieć wiele okien z fokusem (po 1 na wyświetlacz). Jeśli zdarzenie wejściowe jest specyficzne dla wyświetlacza, jest ono wysyłane do okna z fokusem na odpowiednim wyświetlaczu. W przeciwnym razie jest ono wysyłane do okna z fokusem na wyświetlaczu z fokusem, czyli na wyświetlaczu, z którym użytkownik ostatnio wchodził w interakcję.

Zobacz InputDispatcher::mFocusedWindowHandlesByDisplay i InputDispatcher::setFocusedDisplay(). Aplikacje z fokusem są też aktualizowane oddzielnie w InputManagerService za pomocą NativeInputManager::setFocusedApplication().

W WindowManager okna z fokusem są też śledzone oddzielnie. Zobacz DisplayContent#mCurrentFocus i DisplayContent#mFocusedApp oraz ich zastosowania. Powiązane metody śledzenia i aktualizowania fokusu zostały przeniesione z WindowManagerService do DisplayContent.