Napisałem dwie aplikacje (docelowa piernik). Powiedzmy app1 i app2. App1 ma dwie usługi uruchomione z "BOOT_COMPLETED" i są one uruchamiane z wartością zwracaną START_STICKY. Działają w osobnych wątkach. Mówiąc w skrócie. Jedną z usług jest oglądanie danych przychodzących na porcie szeregowym (rodzaj proxy dla aplikacji komunikującej się z interfejsami na drugim końcu portu szeregowego). Drugi ma słuchacza, który obserwuje jakiś status systemu i czeka na "instrukcje" z innych aplikacji. Wiem, że działają dobrze, ponieważ są one wymienione w działających serwisach i dodałem kod, który zmusza ich do zrobienia pewnych rzeczy, gdy pewne konkretne dane pochodzą z portu szeregowego.Wiązanie z usługą z innej aplikacji
Teraz problem: napisałem app2. Próbuje powiązać jedną z usług w aplikacji1. Użyłem dokumentacji android-developper i zaimplementowałem dwukierunkową komunikację między usługą w app1 i app2. Ponieważ mam tylko niewielką ilość bardzo prostych danych do wysłania, skorzystałem z komunikatora, zgodnie z sugestią. Zasadniczo używam po prostu "co, arg1 i arg2" Nie używałem interfejsu AIDL, jak sugerowała dokumentacja.
Oto sekcja androidmanifest deklarująca usługę w aplikacji1 Próbuję również powiązać.
<service android:name=".ModemWatcherService"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- Service name -->
<action android:name="com.admetric.modemwatcher.Service" />
</intent-filter>
</service>
Następnie, oto kilka sposób radzenia sobie z tym problemem w APP1:
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "entering onBind");
return mMessenger.getBinder();
}
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String logMessage = "Received meaasge what= %d, arg1= %d, arg2= %d" + String.valueOf(msg.what) + String.valueOf(msg.arg1) + String.valueOf(msg.arg2);
Log.d(TAG, logMessage);
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case .....
more code here for the application
default:
super.handleMessage(msg);
}
}
}
@Override
public void onCreate() {
mHandler = new Handler();
startSignalLevelListener();
Log.i(TAG, "Just did onCreated");
// Display a notification about us starting. We put an icon in the status bar.
// showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received start id " + startId + ": " + intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
Dla App2, Oto odpowiedni kod w celu ustalenia wiązania z dwukierunkowej komunikacji:
public final class ComWithIoMcu extends Service {
private static final String TAG = "ComWithIoMcu";
/** Messenger for communicating with service. */
static Messenger mServiceMcu = null;
/** Flag indicating whether we have called bind on the service. */
boolean mIsBound;
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* Command to forward a string command to the I/O MCU
*/
public static final int MSG_SEND_STRING_TO_IOMCU = 3;
/** List of supported commands
*
*/
...... more code ....
/**
* Handler of incoming messages from service.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UNSOL_MESSAGE:
Log.d(TAG, "Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mServiceMcu = new Messenger(service);
Log.d(TAG, "Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
Message msg = Message.obtain(null,
MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mServiceMcu.send(msg);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
Log.e(TAG, "ModemWatcherService is not running");
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mServiceMcu = null;
mBound = false;
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
//bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
try {
Intent intentForMcuService = new Intent();
Log.d(TAG, "Before init intent.componentName");
intentForMcuService.setComponent(new ComponentName("com.admetric.modemwatcher", "ModemWatcherService"));
Log.d(TAG, "Before bindService");
if (bindService(intentForMcuService, mConnection, 0)){
Log.d(TAG, "Binding to Modem Watcher returned true");
} else {
Log.d(TAG, "Binding to Modem Watcher returned false");
}
} catch (SecurityException e) {
Log.e(TAG, "can't bind to ModemWatcherService, check permission in Manifest");
}
mIsBound = true;
Log.d(TAG, "Binding.");
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mServiceMcu != null) {
try {
Message msg = Message.obtain(null, MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mServiceMcu.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
Log.d(TAG, "Unbinding.");
}
}
Patrząc na działające usługi, widzę, że usługa, którą utworzyłem w aplikacji2, działa. Logcat pokazuje mi, że próbuję powiązać usługę ModemWatcherService, ale jej nie znaleziono. Oto ciekawy odcinek LogCat
12-05 17:22:59.884 D/ComWithIoMcu( 547): Before init intent.componentName
12-05 17:22:59.884 D/ComWithIoMcu( 547): Before bindService
12-05 17:22:59.888 D/ComWithIoMcu( 547): Binding to Modem Watcher returned false
12-05 17:22:59.888 D/ComWithIoMcu( 547): Binding.
12-05 17:22:59.888 W/ActivityManager( 89): Unable to start service Intent { cmp=com.admetric.modemwatcher/ModemWatcherService }: not found
Moją pierwszą myślą było to, że brakowało pozwolenia ale bindService() może trow wyjątki zabezpieczeń, aw tym przypadku to nie tak, sprawdziłem i zwraca wartość false dla nieznany powód. Ponadto wiem, że w app1, onBind nigdy nie jest nazywane dowodem, że wiązanie nigdy nie miało miejsca. Tak więc komunikat logcat "nie znaleziono" ma sens, ale oświadczyłem, że usługa publiczna w swoim manifeście. To prawdopodobnie zwykły błąd, ale od jakiegoś czasu jestem na tym stanowisku i nie znalazłem przyczyny. Masz pomysł, dlaczego app2 nie może znaleźć usługi w aplikacji1? Użyłem po prostu wycinania i wklejania nazw, więc nie robiłbym głupich pomyłek w nazwach. Czy brakuje mi jakichś uprawnień? Czy muszę zrobić dodatkowy krok, aby opublikować usługę dla całego systemu? Po raz pierwszy próbuję uzyskać dostęp do czegoś w jednej aplikacji z innej aplikacji, więc mogłem coś przeoczyć.
Dzięki to działa. To był problem. I dziękuję również za lepszą praktykę. –
Od czasu lollipopa zabronione jest wiązanie z usługą przy użyciu ukrytych intencji. Opcja ComponentName wydaje się być jedyną opcją. – Ov3r1oad