2012-12-06 10 views
9

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ć.

Odpowiedz

15

Twoja ComponentName jest niepoprawnie skonstruowana. Po przejściu w nazwie klasy jest musi być w pełni kwalifikowane tak:

intentForMcuService.setComponent(new ComponentName("com.admetric.modemwatcher", 
     "com.admetric.modemwatcher.ModemWatcherService")); 

Inną rzeczą, jeśli odwołują się Service poza granicami wniosku, to prawdopodobnie najlepiej nie używać ComponentName odwołać go, nawet jeśli działa poprawnie. Bardziej powszechnym podejściem byłoby utworzenie niestandardowego ciągu ACTION dla twojego Intent i posiadanie tego działania w filtrze Service.

+1

Dzięki to działa. To był problem. I dziękuję również za lepszą praktykę. –

+3

Od czasu lollipopa zabronione jest wiązanie z usługą przy użyciu ukrytych intencji. Opcja ComponentName wydaje się być jedyną opcją. – Ov3r1oad