Multi-Display Communications API, Farklı bir uygulamada çalışan aynı uygulamayla (aynı paket adı) iletişim kurmak için AAOS yolcu bölgesi. Bu sayfada, API'nin nasıl entegre edileceği açıklanmaktadır. Öğrenmek için diğer seçeneklerle birlikte CarOccupantZoneManager.OccupantZoneInfo.
Konuk bölgesi
Yolcu bölgesi kavramı, kullanıcıyı bir dizi ekranla eşleştirir. Her biri yolcu bölgesinde DISPLAY_TYPE_MAIN. Yolcu bölgesinde, küme ekranı gibi ek ekranlar da bulunabilir. Her bir yolcu bölgesine bir Android kullanıcısı atanır. Her kullanıcının kendi hesabı var ve uygulamalar.
Donanım yapılandırması
Comms API yalnızca tek bir SoC'yi destekler. Tek SoC modelinde tüm konuklar ve alt bölgelerin ve kullanıcıların aynı çip üzerinde çalışması gerekir. Comms API üç bileşenden oluşur:
Power management API, istemcinin yolcu bölgelerinde gösterilir.
Discovery API, istemcinin diğer yolcuların durumunu izlemesine olanak tanır. ayarlayabilir ve bu yolcu bölgelerindeki benzer müşterileri izleyebilirsiniz. Tekliflerinizi otomatikleştirmek ve optimize etmek için Connection API'yi kullanmadan önce Discovery API'yi inceleyin.
Connection API, istemcinin eş istemcisine ve eş müşteriye yük göndermek için kullanılır.
Bağlantı için Discovery API ve Connection API gerekir. Güç management API isteğe bağlıdır.
Comms API, farklı uygulamalar arasında iletişimi desteklemez. Bunun yerine yalnızca aynı paket adına sahip uygulamalar arasındaki iletişim için tasarlanmıştır ve yalnızca görünür olan farklı kullanıcılar arasındaki iletişim için kullanılır.
Entegrasyon kılavuzu
AbstractReceiverService Uygulamasını Uygulama
Alıcı uygulamanın Payload
almak için soyut yöntemleri uygulaması GEREKİR
AbstractReceiverService
içinde tanımlanmıştır. Örnek:
public class MyReceiverService extends AbstractReceiverService {
@Override
public void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone) {
}
@Override
public void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
@NonNull Payload payload) {
}
}
Gönderen istemci bir istek yaptığında onConnectionInitiated()
çağrılır:
bağlantı kuruluyor. Doğrulama için kullanıcı onayı gerekiyorsa
MyReceiverService
bu yöntemi geçersiz kılarak
ve izin etkinliği için acceptConnection()
veya rejectConnection()
numaralı telefonu arayın
yardımcı olur. Aksi takdirde, MyReceiverService
yalnızca telefonla arama yapabilir
acceptConnection()
.
onPayloadReceived()is invoked when
MyReceiverServicehas received a
Yükfrom the sender client.
MyReceiverService bunu geçersiz kılabilir
yöntemini kullanarak:
- Varsa
Payload
öğesini ilgili alıcı uç noktalarına yönlendirin. Alıcı: kayıtlı alıcı uç noktalarını al,getAllReceiverEndpoints()
çağrısı yap. Alıcı:Payload
öğesini belirli bir alıcı uç noktasına yönlendirin,forwardPayload()
çağrısı yapın
VEYA
Payload
öğesini önbelleğe alın ve beklenen alıcı uç noktası şu olduğunda gönderinMyReceiverService
için bildirim gönderilir.onReceiverRegistered()
AbstractReceiverService Bildirimi
Alıcı uygulamanın, uygulanan AbstractReceiverService
öğesini kendi
manifest dosyası, işlem içeren bir intent filtresi ekleyin
Bu hizmet için android.car.intent.action.RECEIVER_SERVICE
ve şunu gerektirir:
android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
izni:
<service android:name=".MyReceiverService"
android:permission="android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.car.intent.action.RECEIVER_SERVICE" />
</intent-filter>
</service>
android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
İzni
Bu hizmete yalnızca çerçevenin bağlanmasını sağlar. Bu hizmet
başka bir uygulama buna bağlanabilir
ve doğrudan ona bir Payload
gönderin.
İzin beyan et
İstemci uygulamasının, manifest dosyasında izinleri beyan etmesi ZORUNLUDUR.
<!-- This permission is needed for connection API -->
<uses-permission android:name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<!-- This permission is needed for discovery API -->
<uses-permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
<!-- This permission is needed if the client app calls CarRemoteDeviceManager#setOccupantZonePower() -->
<uses-permission android:name="android.car.permission.CAR_POWER"/>
Yukarıdaki üç izinden her biri ayrıcalıklı izinler olup
izin verilenler listesindeki dosyalar tarafından önceden izin verilir. Örneğin, şunun izin verilenler listesi dosyası:
MultiDisplayTest
uygulama:
// packages/services/Car/data/etc/com.google.android.car.multidisplaytest.xml
<permissions>
<privapp-permissions package="com.google.android.car.multidisplaytest">
… …
<permission name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<permission name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
<permission name="android.car.permission.CAR_POWER"/>
</privapp-permissions>
</permissions>
Araç yöneticileri edinme
API'yi kullanmak için istemci uygulamasının bir CarServiceLifecycleListener
kaydettirmesi GEREKİR
ilişkili Araba yöneticilerini al:
private CarRemoteDeviceManager mRemoteDeviceManager;
private CarOccupantConnectionManager mOccupantConnectionManager;
private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
if (!ready) {
Log.w(TAG, "Car service crashed");
mRemoteDeviceManager = null;
mOccupantConnectionManager = null;
return;
}
mRemoteDeviceManager = car.getCarManager(CarRemoteDeviceManager.class);
mOccupantConnectionManager = car.getCarManager(CarOccupantConnectionManager.class);
};
Car.createCar(getContext(), /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
mCarServiceLifecycleListener);
(Gönderen) Keşfet
Alıcı istemciye bağlanmadan önce, gönderen istemcinin
Alıcı bir istemci (CarRemoteDeviceManager.StateCallback
) kaydederek:
// The maps are accessed by the main thread only, so there is no multi-thread issue.
private final ArrayMap<OccupantZoneInfo, Integer> mOccupantZoneStateMap = new ArrayMap<>();
private final ArrayMap<OccupantZoneInfo, Integer> mAppStateMap = new ArrayMap<>();
private final StateCallback mStateCallback = new StateCallback() {
@Override
public void onOccupantZoneStateChanged(
@androidx.annotation.NonNull OccupantZoneInfo occupantZone,
int occupantZoneStates) {
mOccupantZoneStateMap.put(occupantZone, occupantZoneStates);
}
@Override
public void onAppStateChanged(
@androidx.annotation.NonNull OccupantZoneInfo occupantZone,
int appStates) {
mAppStateMap.put(occupantZone, appStates);
}
};
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.registerStateCallback(getActivity().getMainExecutor(),
mStateCallback);
}
Alıcıyla bağlantı isteğinde bulunmadan önce gönderen, alıcının alıcı yolcu bölgesinin ve alıcı uygulamasının bayrakları ayarlanır. Aksi halde ortaya çıkabilir. Örnek:
private boolean canRequestConnectionToReceiver(OccupantZoneInfo receiverZone) {
Integer zoneState = mOccupantZoneStateMap.get(receiverZone);
if ((zoneState == null) || (zoneState.intValue() & (FLAG_OCCUPANT_ZONE_POWER_ON
// FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED is not implemented yet. Right now
// just ignore this flag.
// | FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
| FLAG_OCCUPANT_ZONE_CONNECTION_READY)) == 0) {
return false;
}
Integer appState = mAppStateMap.get(receiverZone);
if ((appState == null) ||
(appState.intValue() & (FLAG_CLIENT_INSTALLED
| FLAG_CLIENT_SAME_LONG_VERSION | FLAG_CLIENT_SAME_SIGNATURE
| FLAG_CLIENT_RUNNING | FLAG_CLIENT_IN_FOREGROUND)) == 0) {
return false;
}
return true;
}
Gönderenin, alıcıyla bağlantıya geçmek istemesi için alıcının bayrakları ayarlanır. Bununla birlikte, bazı istisnalar söz konusudur:
FLAG_OCCUPANT_ZONE_CONNECTION_READY
veFLAG_CLIENT_INSTALLED
bir bağlantı kurmak için gereken minimum gereksinimleri tanımlayın.Alıcı uygulamanın bağlantı,
FLAG_OCCUPANT_ZONE_POWER_ON
veFLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
ek şartlar haline geldi. Örneğin, daha iyi kullanıcı deneyimi,FLAG_CLIENT_RUNNING
veFLAG_CLIENT_IN_FOREGROUND
önerilir. Aksi takdirde kullanıcı şaşıracaksınız.Şu an için (Android 15)
FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
uygulanmamaktadır. İstemci uygulaması bunu yoksayabilir.Comms API şimdilik (Android 15) yalnızca aynı Benzer uygulamaların aynı uzun sürüm koduna sahip olabilmesi için Android örneği (
FLAG_CLIENT_SAME_LONG_VERSION
) ve imza (FLAG_CLIENT_SAME_SIGNATURE
). Sonuç olarak, uygulamaların uyumlu olması gerekir.
Daha iyi bir kullanıcı deneyimi için, işaret olmadığında gönderen istemci
ayarlandı. Örneğin, FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
ayarlanmazsa gönderen
kullanıcıya bir ileti mesajı veya bir iletişim kutusu gösterilebilir.
yolcu bölgesi.
Gönderenin artık alıcıları keşfetmesi gerekmediğinde (örneğin, tüm alıcıları ve kurulan bağlantıları bulursa veya devre dışı kalırsa) CANNOT TRANSLATE keşfi durdurabilir.
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.unregisterStateCallback();
}
Keşif durdurulduğunda mevcut bağlantılar etkilenmez. Gönderen,
Payload
öğesini bağlı alıcılara göndermeye devam edin.
(Gönderen) Bağlantı iste
Alıcının tüm flag'leri ayarlandığında, gönderen bağlantı isteyebilirsiniz. alıcı:
private final ConnectionRequestCallback mRequestCallback = new ConnectionRequestCallback() {
@Override
public void onConnected(OccupantZoneInfo receiverZone) {
}
@Override
public void onFailed(OccupantZoneInfo receiverZone, int connectionError) {
}
@Override
public void onDisconnected(OccupantZoneInfo receiverZone) {
}
};
if (mOccupantConnectionManager != null && canRequestConnectionToReceiver(receiverZone)) {
mOccupantConnectionManager.requestConnection(receiverZone,
getActivity().getMainExecutor(), mRequestCallback);
}
(Alıcı hizmeti) Bağlantıyı kabul edin
Gönderen, alıcıyla bağlantı kurmak istediğinde
Alıcı uygulamadaki AbstractReceiverService
araba hizmetine bağlı olacak,
ve AbstractReceiverService.onConnectionInitiated()
çağrılır. Farklı
(Gönderen) İsteği Bağlantısı bölümünde açıklandığı gibi
onConnectionInitiated()
soyut bir yöntemdir ve
istemci uygulaması.
Alıcı, bağlantı isteğini kabul ettiğinde gönderenin
ConnectionRequestCallback.onConnected()
çağrılır, ardından bağlantı
kurulduğundan
emin olun.
(Gönderen) Yükü gönder
Bağlantı kurulduktan sonra, gönderen, Payload
CANNOT TRANSLATE
alıcı:
if (mOccupantConnectionManager != null) {
Payload payload = ...;
try {
mOccupantConnectionManager.sendPayload(receiverZone, payload);
} catch (CarOccupantConnectionManager.PayloadTransferException e) {
Log.e(TAG, "Failed to send Payload to " + receiverZone);
}
}
Gönderen, Payload
içine bir Binder
nesnesi veya bir bayt dizisi yerleştirebilir. Öğe
Gönderenin başka veri türleri göndermesi ve verileri bir bayt olarak serileştirmesi ZORUNLUDUR
dizisinden birini kullanıyorsanız, bir Payload
nesnesi oluşturmak için bayt dizisini kullanın ve
Payload
Ardından alıcı istemci, alınan
Payload
ve bayt dizisini beklenen veri nesnesinde seri durumdan çıkarır.
Örneğin, gönderen, alıcıya bir Dize hello
göndermek isterse
FragmentB
kimliğine sahip uç nokta varsa bir veri türü tanımlamak için Proto Arabellekleri kullanabilir
aşağıdaki gibidir:
message MyData {
required string receiver_endpoint_id = 1;
required string data = 2;
}
Şekil 1'de Payload
akışı gösterilmektedir:
(Alıcı hizmeti) Yükü alıp gönderin
Alıcı uygulama Payload
bilgisini aldıktan sonra,
AbstractReceiverService.onPayloadReceived()
çağrılır. Şurada açıklandığı gibi:
Yükü gönder, onPayloadReceived()
bir
soyutlanmış bir yöntemdir ve istemci uygulaması tarafından Uygulanması ZORUNLUDUR. Bu yöntemde
istemci, Payload
öğesini ilgili alıcı uç noktalarına yönlendirebilir veya
Payload
öğesini önbelleğe alın ve ardından beklenen alıcı uç noktası oluşturulduğunda
kayıtlı.
(Alıcı uç noktası) Kaydolma ve kaydı iptal etme
Alıcı uygulama, alıcıyı kaydetmek için registerReceiver()
numarasını çağırmalı
uç noktalar. Tipik bir kullanım örneği, bir Parçanın Payload
alıcısının gerekmesidir.
Bir alıcı uç noktası kaydeder:
private final PayloadCallback mPayloadCallback = (senderZone, payload) -> {
…
};
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.registerReceiver("FragmentB",
getActivity().getMainExecutor(), mPayloadCallback);
}
Alıcı istemcideki AbstractReceiverService
öğesi,
Alıcı uç noktasına Payload
gönderilirse ilişkilendirilmiş PayloadCallback
olur
çağrılır.
İstemci uygulaması, birden fazla alıcı uç noktasını CANNOT TRANSLATE
receiverEndpointId
öğeleri, istemci uygulaması arasında benzersizdir. receiverEndpointId
hangi alıcıya karar vermek için AbstractReceiverService
tarafından kullanılacaktır
uç noktaları bulunur. Örnek:
- Gönderen,
Payload
içindereceiver_endpoint_id:FragmentB
değerini belirtiyor. ZamanPayload
, alıcı aramalarındakiAbstractReceiverService
alınıyor Yükü göndermek içinforwardPayload("FragmentB", payload)
FragmentB
- Gönderen,
Payload
içindedata_type:VOLUME_CONTROL
değerini belirtiyor. ZamanPayload
alındığında alıcıdakiAbstractReceiverService
bunu bilir. bu türPayload
öğelerininFragmentB
numaralı telefona gönderilmesi gerektiğini söyler, bu nedenleforwardPayload("FragmentB", payload)
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.unregisterReceiver("FragmentB");
}
(Gönderen) Bağlantıyı sonlandırma
Gönderenin, alıcıya Payload
öğesini artık göndermesi gerekmediğinde (örneğin,
devre dışı kalırsa) bağlantının sonlandırılması GEREKİR.
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.disconnect(receiverZone);
}
Bağlantı kesildikten sonra gönderen, alıcıya Payload
gönderemez.
Bağlantı akışı
Bir bağlantı akışı Şekil 2'de gösterilmektedir.
Sorun giderme
Günlükleri kontrol etme
İlgili günlükleri kontrol etmek için:
Günlük kaydı için şu komutu çalıştırın:
adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
CarRemoteDeviceService
veCarOccupantConnectionService
:adb shell dumpsys car_service --services CarRemoteDeviceService && adb shell dumpsys car_service --services CarOccupantConnectionService
Null CarRemoteDeviceManager ve CarOccupantConnectionManager
Aşağıdaki olası temel nedenleri inceleyin:
Araba servisi çöktü. Daha önce de belirtildiği gibi iki yönetici araba servisi arızalandığında kasıtlı olarak
null
olacak şekilde sıfırlanır. Araba servisi ne zaman yeniden başlatılırsa, iki yönetici boş olmayan değerlere ayarlanır.CarRemoteDeviceService
veyaCarOccupantConnectionService
değildir etkin. İkisinden birinin etkin olup olmadığını belirlemek için şu komutu çalıştırın:adb shell dumpsys car_service --services CarFeatureController
Şunları içermesi gereken
mDefaultEnabledFeaturesFromConfig
alanını buluncar_remote_device_service
vecar_occupant_connection_service
. Örneğin, örnek:mDefaultEnabledFeaturesFromConfig:[car_evs_service, car_navigation_service, car_occupant_connection_service, car_remote_device_service, car_telemetry_service, cluster_home_service, com.android.car.user.CarUserNoticeService, diagnostic, storage_monitoring, vehicle_map_service]
Varsayılan olarak bu iki hizmet devre dışıdır. Cihaz şunları desteklediğinde: kullanıyorsanız, bu yapılandırma dosyası için yer paylaşımı GEREKİR. Etkinleştirebileceğiniz iki hizmet de ekleyebilirsiniz:
// packages/services/Car/service/res/values/config.xml <string-array translatable="false" name="config_allowed_optional_car_features"> <item>car_occupant_connection_service</item> <item>car_remote_device_service</item> … … </string-array>
API çağrılırken istisna
İstemci uygulaması API'yi gerektiği gibi kullanmıyorsa bir istisna oluşabilir. Bu durumda, istemci uygulaması istisnadaki mesajı ve kilitlenme yığınını da kullanabilirsiniz. API'nin hatalı kullanımına ilişkin örnekler şunlardır:
registerStateCallback()
Bu müşteri zatenStateCallback
kaydettirdi.unregisterStateCallback()
Bu tarafından hiçbirStateCallback
kaydedilmediCarRemoteDeviceManager
örneği.registerReceiver()
receiverEndpointId
zaten kayıtlı.unregisterReceiver()
receiverEndpointId
kayıtlı değil.requestConnection()
Beklemede veya kurulmuş bir bağlantı zaten var.cancelConnection()
İptal edilecek bekleyen bağlantı yok.sendPayload()
Kurulu bağlantı yok.disconnect()
Kurulu bağlantı yok.
Client1, Yük'ü client2'ye gönderebilir ancak bunun tersi yapılamaz
Bağlantı, tek yönlüdür. İki yönlü bağlantı kurmak için
client1
ve client2
birbirleriyle bağlantı kurmalarını ZORUNLUDUR ve ardından
onay almanız gerekir.