android - studio - Enlace a un servicio desde otra aplicación
que es service en android studio (1)
Escribí dos aplicaciones (objetivo Gingerbread). Digamos app1 y app2. La aplicación 1 tiene dos servicios iniciados con "BOOT_COMPLETED" y se inician con el valor de retorno START_STICKY. Se ejecutan en hilos separados. Para acortar una historia larga. Uno de los servicios está atento a los datos entrantes en un puerto serie (un tipo de proxy para la aplicación que se comunica con las interfaces en el otro extremo del puerto serie). El otro tiene un oyente viendo el estado del sistema y esperando algunas "instrucciones" de otras aplicaciones. Sé que están funcionando bien porque están listados en los servicios en ejecución y agregué un código que los obliga a hacer algunas cosas cuando algunos datos específicos provienen del puerto serie.
Ahora el problema: escribí app2. Intenta vincularse a uno de los servicios en la aplicación1. Usé la documentación de desarrollo android e implementé una comunicación bidireccional entre el servicio en app1 y app2. Como solo tengo una pequeña cantidad de datos muy simples para enviar, utilicé un mensajero, como se sugirió. Básicamente utilizo "what, arg1 y arg2". No utilicé la interfaz AIDL como sugería la documentación.
Aquí está la sección del androidmanifest que declara el servicio en la aplicación 1 que intento vincular también.
<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>
Entonces, aquí están los pocos métodos que tratan este problema en la aplicación1:
@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;
}
Para app2, aquí está el código relevante para establecer el enlace con la comunicación bidireccional:
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.");
}
}
Al mirar los servicios en ejecución, puedo ver que el servicio que creé en app2 se está ejecutando. Logcat me muestra que intento vincular el ModemWatcherService pero no se encuentra. Aquí está la sección interesante de 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
Lo primero que pensé fue que me faltaba un permiso, pero bindService () puede controlar las excepciones de seguridad y en este caso no es así, lo revisé y devuelve falso por un motivo desconocido. Además, sé que en la aplicación 1, onBind nunca se llama probar que el enlace nunca se produjo. Entonces, el mensaje de logcat "no encontrado" tiene sentido, pero declaro que el servicio es público en su manifiesto. Probablemente sea un error simple, pero he estado en este asunto por un tiempo y no encontré por qué. ¿Alguna idea de por qué app2 no puede encontrar el servicio en la aplicación1? Utilicé solo cortar y pegar para nombres, así no cometería errores estúpidos en los nombres. ¿Me faltan permisos de algún tipo? ¿Debo hacer un paso adicional para publicar el servicio para todo el sistema? Esa es la primera vez que intento acceder a algo en una aplicación desde otra aplicación, así que es posible que me haya perdido algo.
Su ComponentName
está incorrectamente construido. Cuando se pasa el nombre de la clase, se debe calificar completamente así:
intentForMcuService.setComponent(new ComponentName("com.admetric.modemwatcher",
"com.admetric.modemwatcher.ModemWatcherService"));
Otra cosa, si hace referencia a un Service
fuera de los límites de la aplicación, probablemente sea mejor no usar ComponentName
para hacer referencia a él, incluso si funciona correctamente. Un enfoque más común sería crear una cadena de ACCIÓN personalizada para su Intent
y hacer que el Service
filtre esa acción.