Android, как мне дождаться, пока служба действительно не будет подключена?

У меня есть действие, вызывающее службу, определенную в IDownloaderService.aidl:

public class Downloader extends Activity {
 IDownloaderService downloader = null;
// ...

В Downloader.onCreate(Bundle) я попытался привязатьService

Intent serviceIntent = new Intent(this, DownloaderService.class);
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
  // ...

и в объекте ServiceConnection sc я сделал это

public void onServiceConnected(ComponentName name, IBinder service) {
  Log.w("XXX", "onServiceConnected");
  downloader = IDownloaderService.Stub.asInterface(service);
  // ...

Добавив все виды Log.xx, я обнаружил, что код после if(bindService(...)) фактически идет ДО вызова ServiceConnection.onServiceConnected, то есть когда загрузчик все еще равен нулю, что вызывает у меня проблемы. Все образцы в ApiDemos избегают этой проблемы синхронизации, вызывая службы только тогда, когда они инициируются действиями пользователя. Но что мне делать, чтобы правильно использовать эту службу после успешного завершения работы bindService? Как я могу дождаться надежного вызова ServiceConnection.onServiceConnected?

Еще один связанный с этим вопрос. Все ли обработчики событий: Activity.onCreate, любой View.onClickListener.onClick, ServiceConnection.onServiceConnected и т. д. действительно вызываются в одном и том же потоке (упомянутом в документе как «основной поток»)? Есть ли между ними промежутки, или Android будет планировать, чтобы все события обрабатывались одно за другим? Или когда именно будет вызываться ServiceConnection.onServiceConnected? По завершении Activity.onCreate или когда-нибудь, когда A.oC все еще работает?


person Ryan    schedule 16.06.2010    source источник
comment
Если вам все еще нужен ответ, я дал решение на stackoverflow.com/a/22134635/1336747   -  person Alessio    schedule 03.03.2014
comment
@ Алессио, я не думаю, что твое решение действительно работает.   -  person Sam    schedule 19.06.2015


Ответы (8)


Как я могу дождаться надежного вызова ServiceConnection.onServiceConnected?

Вы не знаете. Вы выходите из onCreate() (или куда бы вы ни привязывались) и помещаете код «требуется установленное соединение» в onServiceConnected().

Все ли обработчики событий: Activity.onCreate, любые View.onClickListener.onClick, ServiceConnection.onServiceConnected и т. д. действительно вызываются в одном потоке

да.

Когда именно будет вызываться ServiceConnection.onServiceConnected? По завершении Activity.onCreate или когда-нибудь, когда A.oC все еще работает?

Ваш запрос на привязку, вероятно, даже не будет запущен до тех пор, пока вы не покинете onCreate(). Следовательно, onServiceConnected() будет вызываться через некоторое время после того, как вы покинете onCreate().

person CommonsWare    schedule 16.06.2010
comment
Спасибо за эту информацию. Надеюсь, что документ для Android может прояснить ситуацию. - person Ryan; 17.06.2010
comment
В различных демонстрациях Service API есть примеры; см., в частности, привязку локальной службы и привязку удаленной службы: developer.android.com/resources/samples/ApiDemos/src/com/ также в документации по Java службы есть пример кода для локальной привязки службы: developer.android.com/reference/android/app/ - person hackbod; 17.06.2010
comment
Хакбод спасибо. По-видимому, этот раздел не отображается в моей локальной копии документа. посмотрю онлайн. - person Ryan; 18.06.2010
comment
Хотя я могу понять, что onCreate() должен завершиться до того, как запустится onServiceConnected() (думаю, он находится в ожидании в основном потоке Looper), я заметил, что onStart() и onResume() ТАКЖЕ запускаются до onServiceConnected()! Не становится ли это опасным состоянием гонки? Компоненты представления, зависящие от службы, готовы к работе, но служба еще не подключена. - person jfritz42; 14.12.2011
comment
Связанный вопрос: вы уверены, что ваша активность все еще существует, когда вызывается onServiceConnected? - person Paul Praet; 27.04.2013
comment
@PaulPraet: Хороший вопрос. Не имею представления. Окно потенциальных проблем невелико, и вполне возможно, что если активность будет уничтожена, то onServiceConnected() просто не будет вызываться, особенно если вы вызвали unbindService() по пути. Тем не менее, я не пытался отслеживать этот конкретный сценарий, в основном потому, что я избегаю связывания, как чумы. - person CommonsWare; 27.04.2013
comment
@jfritz42 jfritz42 В этом проблема, не так ли .. Почему Android просто не дает нам синхронную версию bind? - person dcow; 29.06.2013
comment
спасибо @CommonsWare, но вопрос в том, когда onServiceConnected() будет вызываться, например. 1 сек, 1 мин или никогда? - person ericn; 26.11.2014
comment
@eric Вы не можете рассчитывать на какое-то конкретное время. Вот почему вызов является асинхронным. - person MikeL; 15.10.2015
comment
@ jfritz42 В onCreate, onStart или onResume представления могут попытаться запросить у объекта службы значения (но сначала проверьте, существует ли объект службы), и если объект службы существует, он должен иметь возможность дать какое-то значение, иначе вернуться к какой-то дефолт. В разделе onServiceConnected обновите представления. Независимо от того, что произойдет первым, кажется, что любое состояние в службе будет синхронизировано с представлениями, которые вы храните в действии. - person Kevin Lee; 15.03.2016
comment
Спасибо! Я провел целый день, пытаясь понять, почему мой сервис не запускается. Это было странно, потому что 1 из 10 раз он запускался. Я предположил, что он был подключен сразу после bindService . - person ybloodz; 10.08.2020

У меня такая же проблема. Я не хотел помещать код, зависящий от связанной службы, в onServiceConnected, потому что я хотел связать/развязать с onStart и onStop,, но я не хотел, чтобы код запускался снова каждый раз, когда активность возвращалась на передний план. Я хотел, чтобы он запускался только при первом создании активности.

Я, наконец, преодолел свое onStart() туннельное зрение и использовал логическое значение, чтобы указать, был ли это первый onServiceConnected запуск или нет. Таким образом, я могу развязать службу в onStop и снова связать службу в onStart, не запуская каждый раз все начальные действия.

person froman    schedule 09.11.2012

Я закончил с чем-то вроде этого:

1) чтобы дать вспомогательным вещам некоторую область применения, я создал внутренний класс. По крайней мере, уродливые внутренности отделены от остального кода. Мне нужен был удаленный сервис, делающий что-то, поэтому слово Something в имени класса

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}

2) для вызова метода удаленной службы необходимы две вещи: IBinder и исполняемый код. Поскольку мы не знаем, какой из них становится известным первым, мы сохраняем их:

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

Каждый раз, когда мы пишем в одно из этих полей, мы вызываем _startActionIfPossible():

    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

Это, конечно, предполагает, что Runnable имеет доступ к mISomethingService, но это справедливо для runnable, созданных в рамках методов класса RemoteSomethingHelper.

Очень хорошо, что ServiceConnection обратные вызовы вызываются в потоке пользовательского интерфейса: если мы собираемся вызывать методы службы из основного потока, нам не нужно заботиться о синхронизации.

ISomethingService, конечно же, определяется через AIDL.

3) Вместо того, чтобы просто передавать аргументы методам, мы создаем Runnable, который будет вызывать метод с этими аргументами позже, когда вызов будет возможен:

    private boolean mServiceBound;
    void startSomething(final String arg1) {
        // ... starting the service ...
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    // arg1 and arg2 must be final!
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

4) в итоге получаем:

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
    private ISomethingService mISomethingService;
    private Runnable mActionRunnable;
    private boolean mServiceBound;
    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        // the methods on this class are called from the main thread of your process.
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mISomethingService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mISomethingService = ISomethingService.Stub.asInterface(service);
            _startActionIfPossible();
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

    public void startSomething(final String arg1) {
        Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
        if (!mServiceBound) {
            mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
        }
        ComponentName cn = context.getApplicationContext().startService(intent);
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

context — это поле в моем классе; в Activity вы можете определить его как Context context=this;

Мне не нужно было ставить в очередь действия; если вы это сделаете, вы можете реализовать это.

Вероятно, вам понадобится обратный вызов результата в startSomething(); Я сделал, но это не показано в этом коде.

person 18446744073709551615    schedule 15.08.2013

Я делал что-то подобное раньше, разница только в том, что я не привязывался к сервису, а только запускал его.

Я бы передал намерение службы, чтобы уведомить вызывающего абонента/действия о том, что оно запущено.

person xandy    schedule 16.06.2010
comment
Спасибо за быстрый ответ. Итак, в широковещательном приемнике вы бы привязались к службе для вызова методов RPC, или вы не вызываете их в своем случае? - person Ryan; 16.06.2010
comment
Вы не можете привязаться к службе от широковещательного приемника (поскольку широковещательный прием выполняется после его возврата из onReceive). - person hackbod; 17.06.2010

Я хотел добавить некоторые вещи, которые вы должны или не должны делать:

  1. привязать службу не при создании, а при возобновлении и отвязать ее при паузе. Ваше приложение может перейти в режим паузы (в фоновом режиме) в любое время при взаимодействии с пользователем или на экранах операционной системы. Используйте отдельный try/catch для каждой отвязки службы, отмены регистрации получателя и т. д. в onPause, поэтому, если один из них не связан или не зарегистрирован, исключение не предотвратит уничтожение других.

  2. Обычно я связываю капсулу в общедоступном методе MyServiceBinder getService(). Я также всегда использую блокирующую логическую переменную, поэтому мне не нужно следить за всеми этими вызовами, использующими службу в действии.

Пример:

boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;

public MyService.Binder getMyService()
{
   if(serviceHelp==null)
   {
       //don't bind multiple times
       //guard against getting null on fist getMyService calls!
       if(isBindingOngoing)return null; 
       isBindingOngoing = true;
       myServiceCon = new ServiceConnection(
           public void onServiceConnected(ComponentName cName, IBinder binder) {
               serviceHelp = (MyService.Binder) binder;
               //or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
               isServiceBindingOngoing = false;
               continueAfterServiceConnect(); //I use a method like this to continue
           }

           public void onServiceDisconnected(ComponentName className) {
              serviceHelp = null;
           }
       );
       bindService(serviceStartIntent,myServiceCon);
   }
   return serviceHelp;
}
person FrankKrumnow    schedule 24.04.2020

Я понял, что эти обходные пути стоят усилий и ожидания только в том случае, если ваши связанные службы работают в другом процессе, чем основной процесс вашего приложения.

Для доступа к данным и методам в одном и том же процессе (или приложении) я реализовал одноэлементные классы. Если классам нужен контекст для некоторых методов, я передаю контекст приложения в одноэлементные классы. Есть, конечно, и плохое последствие, так как нарушается "мгновенный запуск". Но в целом это лучший компромисс, я думаю.

person Hamid Khan    schedule 14.12.2016

В Android 10 представлена ​​новая версия bindService при привязке к службе для предоставления Executor (которую можно создать из Исполнители).

    /**
     * Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
     * ServiceConnection callbacks.
     * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
     *      instance for the same instance of ServiceConnection.
    */
    public boolean bindService(@RequiresPermission @NonNull Intent service,
            @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
            @NonNull ServiceConnection conn) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

Это позволяет привязываться к сервису в потоке и ждать, пока он не подключится. Например. заглушка:


private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();

... 

private void myConnectMethod() {
// bind to service
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    context.bindService(new Intent(context, MyServiceClass.class), Context.BIND_AUTO_CREATE, executorService, new 
   ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder binder) {
        synchronized (lock) {
            // TODO: store service instance for calls in case of AIDL or local services
            connected.set(true);
            lock.notify();
        }
     });

    synchronized (lock) {
            while (!connected.get()) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                }
            }
        }
}

Также необходимо запустить службу в отдельном процессе:

        <service
            android:name=".MyServiceClass"
            android:process=":service"
            android:enabled="true"
            android:exported="true" />
person k_o_    schedule 23.02.2021

* Основная идея та же, что и у @18446744073709551615, но я также поделюсь своим кодом.

В качестве ответа на главный вопрос,

Но что мне делать, чтобы правильно использовать эту службу после успешного завершения работы bindService?

[Исходное ожидание (но не работает)]

подождите, пока служба не подключится, как показано ниже

    @Override
    protected void onStart() {
        bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
        synchronized (mLock) { mLock.wait(40000); }

        // rest of the code continues here, which uses service stub interface
        // ...
    }

Это не сработает, поскольку bindService() в onCreate()/onStart() и onServiceConnected() вызываются в одном и том же главном потоке. onServiceConnected() никогда не вызывается до завершения ожидания.

[Альтернативное решение]

Вместо «ожидания» определите собственный Runnable, который будет вызываться после подключения службы, и выполните этот runnable после подключения службы.

Реализуйте собственный класс ServiceConnection следующим образом.

public class MyServiceConnection implements ServiceConnection {

    private static final String TAG = MyServiceConnection.class.getSimpleName();

    private Context mContext = null;
    private IMyService mMyService = null;
    private ArrayList<Runnable> runnableArrayList;
    private Boolean isConnected = false;

    public MyServiceConnection(Context context) {
        mContext = context;
        runnableArrayList = new ArrayList<>();
    }

    public IMyService getInterface() {
        return mMyService;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.v(TAG, "Connected Service: " + name);
        mMyService = MyService.Stub.asInterface(service);

        isConnected = true;
        /* Execute runnables after Service connected */
        for (Runnable action : runnableArrayList) {
            action.run();
        }
        runnableArrayList.clear();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        try {
            mMyService = null;
            mContext.unbindService(this);
            isConnected = false;
            Log.v(TAG, "Disconnected Service: " + name);
        } catch(Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    public void executeAfterServiceConnected(Runnable action) {
        Log.v(TAG, "executeAfterServiceConnected");
        if(isConnected) {
            Log.v(TAG, "Service already connected, execute now");
            action.run();
        } else {
            // this action will be executed at the end of onServiceConnected method
            Log.v(TAG, "Service not connected yet, execute later");
            runnableArrayList.add(action);
        }
    }
}

А затем используйте его следующим образом (в своем классе Activity или т. д.),

private MyServiceConnection myServiceConnection = null;

@Override
protected void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();

    Intent serviceIntent = new Intent(getApplicationContext(), MyService.class);
    startService(serviceIntent);
    myServiceConnection = new MyServiceConnection(getApplicationContext());
    bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE);

    // Instead of "wait" here, create callback which will be called after service is connected
    myServiceConnection.executeAfterServiceConnected(new Runnable() {
        @Override
        public void run() {
            // Rest of the code comes here.
            // This runnable will be executed after service connected, so we can use service stub interface
            IMyService myService = myServiceConnection.getInterface();
            // ...
        }
    });
}

Это сработало для меня. Но может быть и более лучший способ.

person corochann    schedule 20.01.2016