Android, как мне ждать, пока сервис действительно подключится? - PullRequest
44 голосов
/ 16 июня 2010

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

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

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

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

и внутри объекта ServiceConnectionsc Я сделал это

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 все еще работает?

Ответы [ 6 ]

48 голосов
/ 16 июня 2010

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

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

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

Да.

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

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

2 голосов
/ 15 августа 2013

У меня получилось что-то вроде этого:

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, но это верно для runnables, созданных в методах класса 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 - это поле в моем классе; в действии вы можете определить его как Context context=this;

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

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

2 голосов
/ 09 ноября 2012

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

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

1 голос
/ 16 июня 2010

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

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

0 голосов
/ 14 декабря 2016

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

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

0 голосов
/ 20 января 2016

* Основная идея та же, что и у @ 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() никогда не вызывается до завершения ожидания.

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

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

Реализуйте пользовательский класс 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();
            // ...
        }
    });
}

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

...