HIDL Java

W Androidzie 8.0 zmieniliśmy architekturę systemu, aby wyraźnie oddzielić platformę Androida od kodu związanego z konkretnym urządzeniem lub dostawcą. Android zdefiniował już wiele takich interfejsów w postaci interfejsów HAL, zdefiniowanych jako nagłówki C w pliku hardware/libhardware. HIDL zastąpił te interfejsy HAL stabilnymi interfejsami z wersją, które mogą być w języku Java (opisane poniżej) lub interfejsami HIDL po stronie klienta i serwera w języku C++.

Interfejsy HIDL są przeznaczone głównie do używania w kodzie natywnym, dlatego HIDL koncentruje się na automatycznym generowaniu wydajnego kodu w C++. Jednak interfejsy HIDL muszą być też dostępne do użycia bezpośrednio z Java, ponieważ niektóre podsystemy Androida (np. telefonia) mają interfejsy HIDL w Java.

Strony w tej sekcji opisują interfejs Java dla interfejsów HIDL, podają szczegółowe informacje o tworzeniu, rejestrowaniu i używaniu usług oraz wyjaśniają, jak interfejsy HAL i klienci HAL napisane w języku Java współdziałają z systemem HIDL RPC.

Przykład klienta

Oto przykład klienta dla interfejsu IFoo w pakiecie android.hardware.foo@1.0, który jest zarejestrowany jako nazwa usługi default i usługi dodatkowej o niestandardowej nazwie second_impl.

Dodawanie bibliotek

Jeśli chcesz z niego korzystać, musisz dodać zależności od odpowiedniej biblioteki stub HIDL. Zwykle jest to biblioteka statyczna:

// in Android.bp
static_libs: [ "android.hardware.foo-V1.0-java", ],
// in Android.mk
LOCAL_STATIC_JAVA_LIBRARIES += android.hardware.foo-V1.0-java

Jeśli wiesz, że już używasz tych bibliotek, możesz też użyć wspólnego linkowania:

// in Android.bp
libs: [ "android.hardware.foo-V1.0-java", ],
// in Android.mk
LOCAL_JAVA_LIBRARIES += android.hardware.foo-V1.0-java

Dodatkowe kwestie dotyczące dodawania bibliotek w Androidzie 10

Jeśli masz aplikację systemową lub aplikację dostawcy przeznaczoną na Androida 10 lub nowszego, możesz statycznie uwzględnić te biblioteki. Możesz też używać (tylko) klas HIDL z niestandardowych plików JAR zainstalowanych na urządzeniu za pomocą stabilnych interfejsów Java API udostępnionych za pomocą dotychczasowego mechanizmu uses-library dla aplikacji systemowych. Drugie podejście pozwala zaoszczędzić miejsce na urządzeniu. Więcej informacji znajdziesz w artykule Implementowanie biblioteki Java SDK. W przypadku starszych aplikacji zachowane jest stare działanie.

Począwszy od Androida 10 dostępne są też wersje „płytkie” tych bibliotek. Obejmują one klasę, której dotyczy problem, ale nie obejmują żadnych klas zależnych. Na przykład: android.hardware.foo-V1.0-java-shallow zawiera klasy w pakiecie foo, ale nie zawiera klas w android.hidl.base-V1.0-java, który zawiera klasę podstawową wszystkich interfejsów HIDL. Jeśli tworzysz bibliotekę, która zawiera już preferowane klasy podstawowe interfejsu dostępne jako zależności, możesz użyć:

// in Android.bp
static_libs: [ "android.hardware.foo-V1.0-java-shallow", ],
// in Android.mk
LOCAL_STATIC_JAVA_LIBRARIES += android.hardware.foo-V1.0-java-shallow

Biblioteki bazowe i biblioteki menedżera HIDL nie są też już dostępne w klasie ścieżki uruchamiania aplikacji (wcześniej były one czasami używane jako ukryte interfejsy API ze względu na mechanizm Androida polegający na tym, że najpierw ładuje się zastępnik). Zamiast tego zostały przeniesione do nowej przestrzeni nazw z jarjar, a aplikacje, które z nich korzystają (koniecznie aplikacje prywatne), muszą mieć ich osobne kopie. Moduł na ścieżce ładowania klas używający HIDL musi używać płytkich wersji tych bibliotek Java i dodawać jarjar_rules: ":framework-jarjar-rules" do Android.bp, aby używać wersji tych bibliotek, która istnieje na ścieżce ładowania klas.

Modyfikowanie kodu źródłowego w Javie

Ta usługa ma tylko jedną wersję (@1.0), więc ten kod pobiera tylko tę wersję. Informacje o obsługiwaniu wielu różnych wersji usługi znajdziesz w rozszerzeniach interfejsu.

import android.hardware.foo.V1_0.IFoo;
...
// retry to wait until the service starts up if it is in the manifest
IFoo server = IFoo.getService(true /* retry */); // throws NoSuchElementException if not available
IFoo anotherServer = IFoo.getService("second_impl", true /* retry */);
server.doSomething(…);

Świadczenie usług

Kod frameworka w Javie może wymagać interfejsów do odbierania asynchronicznych wywołań zwrotnych z HAL-i.

W przypadku interfejsu IFooCallback w wersji 1.0 pakietu android.hardware.foo możesz go zaimplementować w języku Java w ten sposób:

  1. Zdefiniuj interfejs w HIDL.
  2. Otwórz /tmp/android/hardware/foo/IFooCallback.java jako odniesienie.
  3. Utwórz nowy moduł dla implementacji w Javie.
  4. Zapoznaj się z klasą abstrakcyjną android.hardware.foo.V1_0.IFooCallback.Stub, a potem napisz nową klasę, aby ją rozszerzyć i zaimplementować metody abstrakcyjne.

Wyświetlanie plików wygenerowanych automatycznie

Aby wyświetlić automatycznie wygenerowane pliki, uruchom:

hidl-gen -o /tmp -Ljava \
  -randroid.hardware:hardware/interfaces \
  -randroid.hidl:system/libhidl/transport android.hardware.foo@1.0

Te polecenia generują katalog /tmp/android/hardware/foo/1.0. W przypadku pliku hardware/interfaces/foo/1.0/IFooCallback.hal generuje plik /tmp/android/hardware/foo/1.0/IFooCallback.java, który zawiera interfejs Java, kod serwera proxy i stuby (zarówno serwer proxy, jak i stuby są zgodne z interfejsem).

-Lmakefile generuje reguły, które wykonują to polecenie w czasie kompilacji, i pozwalają uwzględnić android.hardware.foo-V1.0-java oraz połączyć odpowiednie pliki. Skrypt, który automatycznie wykonuje tę czynność w przypadku projektu zawierającego wiele interfejsów, znajdziesz na stronie hardware/interfaces/update-makefiles.sh. Ścieżki w tym przykładzie są względne; sprzęt/interfejsy mogą być tymczasowym katalogiem w drzewie kodu, aby umożliwić opracowanie interfejsu HAL przed jego opublikowaniem.

Uruchamianie usługi

HAL udostępnia interfejs IFoo, który musi wywoływać asynchroniczne callbacki do platformy za pomocą interfejsu IFooCallback. Interfejs IFooCallback nie jest rejestrowany według nazwy jako usługa możliwa do znalezienia. Zamiast tego interfejs IFooCallback musi zawierać metodę, taką jak setFooCallback(IFooCallback x).IFoo

Aby skonfigurować IFooCallback z wersji 1.0 pakietu android.hardware.foo, dodaj android.hardware.foo-V1.0-java do Android.mk. Kod służący do uruchamiania usługi:

import android.hardware.foo.V1_0.IFoo;
import android.hardware.foo.V1_0.IFooCallback.Stub;
....
class FooCallback extends IFooCallback.Stub {
    // implement methods
}
....
// Get the service from which you will be receiving callbacks.
// This also starts the threadpool for your callback service.
IFoo server = IFoo.getService(true /* retry */); // throws NoSuchElementException if not available
....
// This must be a persistent instance variable, not local,
//   to avoid premature garbage collection.
FooCallback mFooCallback = new FooCallback();
....
// Do this once to create the callback service and tell the "foo-bar" service
server.setFooCallback(mFooCallback);

Rozszerzenia interfejsu

Zakładając, że dana usługa implementuje interfejs IFoo na wszystkich urządzeniach, możliwe jest, że na danym urządzeniu usługa może udostępniać dodatkowe funkcje zaimplementowane w rozszerzeniu interfejsu IBetterFoo, jak np.:

interface IFoo {
   ...
};

interface IBetterFoo extends IFoo {
   ...
};

Kod wywołujący, który jest świadomy rozszerzonego interfejsu, może użyć metody Java castFrom(), aby bezpiecznie zmienić interfejs podstawowy na interfejs rozszerzony:

IFoo baseService = IFoo.getService(true /* retry */); // throws NoSuchElementException if not available
IBetterFoo extendedService = IBetterFoo.castFrom(baseService);
if (extendedService != null) {
  // The service implements the extended interface.
} else {
  // The service implements only the base interface.
}