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

У меня есть активность, вызывающая службу, определенную в 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)) {
  // ...

и в рамках объекта 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 все еще работает?

8 ответов

Решение

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

Вы не Вы выходите из onCreate() (или где бы вы ни связывались), и вы вводите код "необходимо установить соединение" в onServiceConnected(),

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

Да.

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

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

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

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(); Я сделал, но это не показано в этом коде.

В 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" />

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

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

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

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

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

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

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

Пример:

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

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

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

* Основная идея такая же, как у @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 для вызова после Service Connected и выполните этот 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();
            // ...
        }
    });
}

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

Другие вопросы по тегам