Binding to a service from another app

18,047

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.

Share:
18,047

Related videos on Youtube

Sylvain Huard
Author by

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, 2022

Comments

  • Sylvain Huard
    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
    Sylvain Huard over 11 years
    Thanks it works. That was the problem. And thanks for the better practice note too.
  • Ov3r1oad
    Ov3r1oad about 8 years
    Since lollipop it's forbidden to bind to service using implicit intent. So ComponentName seems to be the only option.