Подтвердить что ты не робот

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

У меня есть 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)) {
  // ...

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

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

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

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

4b9b3361

Ответ 1

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

Нет. Вы выходите из onCreate() (или везде, где вы привязываетесь), и вы ставите "код, установленный на соединение" в onServiceConnected().

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

Да.

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

* Основная идея такая же с @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, который будет вызван после подключения к службе, и выполните эту 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);
        }
    }
}

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

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

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

Ответ 6

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

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