[Android] 안드로이드 서비스가 실제로 연결될 때까지 어떻게 기다려야합니까?


Answers

ServiceConnection.onServiceConnected가 안정적으로 호출되기를 기다리는 방법은 무엇입니까?

너는하지 않는다. onCreate() 종료하거나 바인딩 할 때마다 onServiceConnected() 에 "연결이 필요합니다"코드를 넣습니다.

모든 이벤트 핸들러 : Activity.onCreate, 동일한 스레드에서 실제로 호출 된 View.onClickListener.onClick, ServiceConnection.onServiceConnected 등

예.

정확히 ServiceConnection.onServiceConnected가 실제로 호출 될 때입니까? Activity.onCreate가 완료되거나 언젠가 A.oC가 실행 중일 때가 있습니까?

바인드 요청은 onCreate() 종료 할 때까지 시작 되지 않을 것입니다. 따라서 onCreate() 끝낸 후에 onServiceConnected() 가 언젠가 호출됩니다.

Question

IDownloaderService.aidl에 정의 된 서비스를 호출하는 활동이 있습니다.

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

Downloader.onCreate (Bundle)에서 bindService를 시도했습니다.

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 등 (실제로 "주 스레드"와 같은 문서에서 언급 한) 같은 스레드에서 실제로 호출됩니까? 그들 사이에 인터리브가 있습니까, 아니면 안드로이드가 모든 이벤트를 하나씩 처리하도록 예약할까요? 또는 정확히 ServiceConnection.onServiceConnected가 실제로 호출 될 때입니까? Activity.onCreate가 완료되거나 언젠가 A.oC가 실행 중일 때가 있습니까?




나는 똑같은 문제가 있었다. onServiceConnected 바인딩 된 서비스 종속 코드를 넣고 싶지 않았습니다. onStartonStop, 을 바인드 / onStart 바인드하고 싶었 기 때문에 활성 코드가 다시 돌아올 때마다 코드를 다시 실행하지 않으려했습니다. 활동이 처음 생성 될 때만 실행하고 싶었습니다.

드디어 내 onStart() 터널 비전을 극복하고 이것이 onServiceConnected 실행 여부를 나타내는 Boolean을 사용했습니다. 그렇게하면 매번 모든 시작 작업을 실행하지 않고도 onStart 에서 onStop 및 bindService에서 서비스를 다시 바인딩 해제 할 수 있습니다.




* 기본 아이디어는 @ 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
        // ...
    }

onCreate()/onStart()onServiceConnected() bindService()동일한 주 스레드 에서 호출되기 때문에 작동하지 않습니다. wait가 끝나기 전에 onServiceConnected() 가 호출되지 않습니다.

[대안]

「기다리는」대신에, 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();
            // ...
        }
    });
}

그것은 나를 위해 일했습니다. 그러나 더 좋은 방법이있을 수 있습니다.




나는 이런 식으로 끝났다.

1) 보조 스터프에 어떤 범위를 주려면 내부 클래스를 만들었습니다. 적어도 추악한 내부 구조는 나머지 코드와 분리되어 있습니다. 원격 서비스가 필요하기 때문에 클래스 이름에 Something 이라는 단어가 있습니다.

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

2) 원격 서비스 메소드를 호출하는 데는 IBinder와 실행 코드가 필요합니다. 어느 것이 먼저 알려 지는지 모르기 때문에 우리는 그것을 저장합니다 :

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

우리가 이러한 fileds 중 하나에 쓸 때마다 _startActionIfPossible() 호출합니다.

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

물론 이것은 Runnable이 mISomethingService에 액세스 할 수 있다고 가정하지만 RemoteSomethingHelper 클래스의 메서드 내에서 만들어진 runnables에 대해서도 마찬가지입니다.

UI 스레드 에서 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; 로 정의 할 수 있습니다 Context context=this;

큐잉 작업이 필요하지 않았습니다. 그렇게하면 구현할 수 있습니다.

startSomething ()에서 결과 콜백이 필요할 것입니다. 그랬지 만이 코드에는 나와 있지 않습니다.




이전과 비슷한 일을했지만 다른 점은 서비스에 구속력이있는 것이 아니라 단지 시작하는 것뿐입니다.

호출자 / 활동에 알림을 보내려는 서비스의 의도를 방송합니다.




이 해결 방법은 바인딩 된 서비스가 응용 프로그램의 주 프로세스와 다른 프로세스에서 실행중인 경우에만 노력하고 기다릴 가치가 있다는 것을 알았습니다.

동일한 프로세스 (또는 응용 프로그램)에서 데이터와 메서드에 액세스하기 위해 싱글 톤 클래스를 구현했습니다. 클래스가 일부 메서드에 대한 컨텍스트가 필요한 경우에는 응용 프로그램 컨텍스트를 단일 클래스에 유출합니다. 물론 "즉시 실행"이 중단 될 때 나쁜 결과가 발생합니다. 그러나 그것은 전반적으로 더 나은 타협이라고 생각합니다.