Binding to a service from another app
Your ComponentName
is incorrectly constructed. When passing in the class name is must be fully qualified like so:
intentForMcuService.setComponent(new ComponentName("com.admetric.modemwatcher",
"com.admetric.modemwatcher.ModemWatcherService"));
Another thing, if you are referencing a Service
outside the boundaries of the application, it's probably best not to use ComponentName
to reference it, even if it does work correctly. A more common approach would be to create a custom ACTION string for your Intent
and have the Service
filter that action.
Related videos on Youtube
Sylvain Huard
I work mainly on embedded system projects. I usually work on everything closer to the machine like hardware (analog, digital, FPGAs), BSPs, Kernel, drivers while still being involved in the application itself. In the past couple years, I mainly worked on building custom platforms SOC FPGAs porting Linux Kernel and Android to it. I also spent quiet a bit of time on system design and FPGA design with VHDL. I wrote a few simple Android apps. I've been using Python as a modelling tool for FPGA algorithms and as an analysis tool for data coming from FPGA simulations. All of that keeps quiet busy...
Updated on July 11, 2022Comments
-
Sylvain Huard almost 2 years
I wrote two apps (target Gingerbread). Let say app1 and app2. App1 has two services started with "BOOT_COMPLETED" and they are started with the return value START_STICKY. They run in separate threads. To make a long story short. One of the service is watching for incoming data on a serial port (a kind of proxy for app communicating with interfaces on the other end of the serial port). The other has a listener watching some system status and waiting for some "instructions" from other apps. I know they are running well because they are listed in the running services and I added some code that forces them to do some stuff when some specific data come from the serial port.
Now the problem: I wrote app2. It tries to bind to one of the service in app1. I used android-developper documentation and implemented a bidirectional communication between the service in app1 and app2. Since I just have a small amount of very simple data to send, I used a messenger, as suggested. I basically just use the "what, arg1 and arg2" I did not use the AIDL interface as the documentation was suggesting.
Here is the section of the androidmanifest declaring the service in app1 I try to bind too.
<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>
Then, here are the few method dealing with this issue in 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; }
For app2, here is the relevant code to establish the binding with the bidirectional communication:
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."); } }
Looking at the running services, I can see that the service I created in app2 is running. Logcat shows me that I try to bind the the ModemWatcherService but it is not found. Here is the interesting section of 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
My first thought was that I was missing a permission but bindService() can trow security exceptions and in this case it doesn't so, I checked and it returns false for an unknown reason. Also, I know that in app1, the onBind is never called proving that the binding never occurred. So the logcat message "not found" make sense but I declared that service public in its manifest. It's probably a simple mistake but I've been on this issu for a while now and I did not find why. Any idea why app2 can't find the service in app1 ? I used just cut and paste for names so I would not do stupid mistyping mistakes in names. Am I missing permissions of some sort? Do I need to do some extra step to publish the service for the whole system? That's the first time I try to access something in one app from another app so, I might have missed something.
-
Sylvain Huard over 11 yearsThanks it works. That was the problem. And thanks for the better practice note too.
-
Ov3r1oad about 8 yearsSince lollipop it's forbidden to bind to service using implicit intent. So ComponentName seems to be the only option.