Android: привязка к удаленному сервису

Я создаю удаленную службу и клиентское приложение, предназначенное для выполнения API 24 на устройстве Nexus 6P. У меня есть удаленная служба, которая автоматически запускается при загрузке. Вот фрагменты кода:

Манифест удаленной службы

<?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>

Удаленное обслуживание

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();
    }
}

Удаленный широковещательный приемник

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);
        }
    }
}

Удаленная активность (Android Studio настаивает на наличии активности)

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);
    }
}

Затем у меня есть отдельный проект для реализации клиентской активности в другом пакете, который пытается привязаться к удаленной службе. Вот фрагменты кода:

Манифест клиента

<?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>

Действия клиента

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");
        }
    }
}

Функция isServiceRunning возвращает true, поэтому я знаю, что a.b.c.MyService запущен. Кажется, что функция bindService выполнена успешно (в Logcat нет ошибок), но обратный вызов onServiceConnected никогда не выполняется.

Как выполнить привязку к a.b.c.Myservice из x.y.z.ClientActivity в целевом SDK 24 для Android?

Спасибо!


person pfp    schedule 27.09.2016    source источник


Ответы (1)


Я вижу здесь как минимум 2 проблемы:

1) Вы используете "неявный" Intent в вызове bindService() (т.е. вы не указали компонент явно). Это должно вызвать исключение, если вы ориентируетесь на API 21 или выше. Вы должны использовать "явный" Intent (т.е. указать имя пакета и имя класса Service).

2) Ваш Service возвращает null из onBind(). Это означает, что клиенты не смогут привязываться к Service, потому что вы не возвращаете IBinder, который клиент может затем использовать для вызова методов Service. Try to find a how-to or some example code for how to use boundService`s.

person David Wasser    schedule 29.09.2016
comment
объявление 1) не совсем: он звонит intent.setPackage("a.b.c");, тогда намерение становится явным - person pskink; 30.09.2016
comment
@pskink правда? Имя пакета достаточно? Я не знал об этом, но готов быть исправленным. - person David Wasser; 30.09.2016
comment
да, этого достаточно, поскольку он ограничивает намерение явным пакетом, конечно, вам нужно сделать его еще более явным, предоставив, например, действие, делающее это намерение уникальным, попробуйте. - person pskink; 30.09.2016
comment
Решение - комментарий Дэвида Вассера (2)... Мне нужно было вернуть IBinder. Хотя это работает, также обсуждается намерение expilcit: есть ли у вас пример еще более развернутого намерения для отправки abcMY_INTENT в a.b.c.MyService? - person pfp; 03.10.2016
comment
Я отредактировал код в разделе «Удаленное обслуживание» исходного сообщения с некоторыми комментариями, поясняющими решение IBinder. - person pfp; 03.10.2016
comment
Более явным Intent для этого будет intent.setClassName("a.b.c", "a.b.c.MyService");. В этом случае вы явно указываете точный компонент (класс), который нужно создать. Если вы сделаете это, вам не нужно указывать ДЕЙСТВИЕ в Intent, и вы можете удалить <intent-filter> из своего объявления <service>, потому что он больше не нужен (<intent-filter> нужен только для неявного разрешения Intent. Однако, если вы удалите <intent-filter> вам нужно будет добавить android:exported="true" к объявлению <service>, потому что без <intent-filter> ... - person David Wasser; 03.10.2016
comment
... Service будет закрытым и недоступным для приложений за пределами пакета a.b.c. Если вы укажете <intent-filter>, по умолчанию для android:exported автоматически будет true, но если вы не укажете <intent-filter>, по умолчанию будет false. Я надеюсь, что это ясно. - person David Wasser; 03.10.2016
comment
Хотел бы я дать вам +50 за более подробный комментарий выше. Вы спасли мой день. Рассмотрите возможность продвижения комментария в тело ответа. - person Uri; 04.11.2018