Android: Binding to a remote service
Asked Answered
R

1

8

I'm building a remote service and a client application targetted at API 24 executing on a Nexus 6P device. I have a remote service that automatically starts at boot. Here are the code fragments:

Remote Service Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="a.b.c">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service android:name=".MyService" >
            <intent-filter>
                <action android:name="a.b.c.MY_INTENT" />
            </intent-filter>
        </service>

        <activity android:name=".MyActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Remote Service

package a.b.c;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service
{
    @Override
    public IBinder onBind(Intent intent) {
        return null;
        // EDIT: see StackOverflow answers/comments below:
        // Returning an IBinder here solves the problem.
        // e.g. "return myMessenger.getBinder()" where myMessenger
        // is an instance of Android's Messenger class.
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}

Remote Broadcast Receiver

package a.b.c;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Intent serviceIntent = new Intent(context, MyService.class);
            context.startService(serviceIntent);
        }
    }
}

Remote Activity (Android Studio insists on there being an Activity)

package a.b.c;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class MyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);
    }
}

I then have a separate project to implement a client activity in a different package that attempts to bind to the remote service. Here are the code fragments:

Client Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="x.y.z">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ClientActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Client Activity

package x.y.z;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class ClientActivity extends AppCompatActivity
{
    private final String TAG = "ClientActivity";

    private ServiceConnection mMyServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "ServiceConnection onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "ServiceConnection onServiceDisconnected");
        }
    };

    private boolean isServiceRunning(String className) {
        ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (className.equals(serviceInfo.service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    private void bindMyService() {
        Intent intent = new Intent("a.b.c.MY_INTENT");
        intent.setPackage("a.b.c");
        bindService(intent, mMyServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_client);
        if (isServiceRunning("a.b.c.MyService")) {
            bindMyService();
        }
        else {
            Log.e(TAG, "Service is not running");
        }
    }
}

The "isServiceRunning" function returns true so I know that a.b.c.MyService is running. The bindService function seems to succeed (no errors in Logcat) but the onServiceConnected callback is never executed.

How do I bind to a.b.c.Myservice from x.y.z.ClientActivity in Android target SDK 24?

Thanks!

Radicle answered 27/9, 2016 at 15:26 Comment(0)
M
8

I see at least 2 problems here:

  1. You are using an "implicit" Intent in the call to bindService() (ie: you haven't specified the component explicitly). This should throw an exception if you are targeting API 21 or higher. You should use an "explicit" Intent (ie: specify the package name and class name of the Service).

  2. Your Service returns null from onBind(). This means that clients will not be able to bind to the Service, because you aren't returning an IBinder that the client can then use to call methods on the Service. Try to find a how-to or some example code for how to use bound Services.

Misha answered 29/9, 2016 at 20:28 Comment(8)
ad 1) not really: he is calling intent.setPackage("a.b.c");, then the intent gets "explicit"Orsini
@Orsini really? Package name is enough? I wasn't aware of that, but am willing to be corrected.Misha
yes, its enough since it limits the intent to explicit package, of course you need to make it even "more explicit" by providing for example the action making that intent unique, try it outOrsini
The solution is David Wasser's comment (2)...I needed to return an IBinder. While this works there's also the discussion of the expilcit intent: do you have an example of "an even more explit intent" to send a.b.c.MY_INTENT to a.b.c.MyService?Radicle
I've edited the code in the Remote Service section of the original post with some comments to explain the IBinder solution.Radicle
A "more explicit" Intent for this would be intent.setClassName("a.b.c", "a.b.c.MyService");. In this case, you are explicitly stating the exact component (class) to be instantiated. If you do this, you don't need to specify the ACTION in the Intent, and you can remove the <intent-filter> from your <service> declaration because it isn't needed anymore (<intent-filter> is only needed for implicit Intent resolution. However, if you remove the <intent-filter> you will then need to add android:exported="true" to the <service> declaration because without <intent-filter> the ...Misha
... Service would be private, and not available to applications outside of package "a.b.c". If you provide an <intent-filter>, the default for android:exported is automatically true, but if you don't provide an <intent-filter> the default is false. I hope this is clear.Misha
I wish I could give you +50 for the "more explicit" comment above. You saved my day. Consider promoting the comment to the body of the answer.Subhuman

© 2022 - 2024 — McMap. All rights reserved.