48

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 などは実際に同じスレッドで呼び出されていますか (ドキュメントでは「メイン スレッド」として言及されています)。それらの間にインターリーブがありますか、または Android はすべてのイベントが 1 つずつ処理されるようにスケジュールしますか? または、 ServiceConnection.onServiceConnected が実際に呼び出されるのはいつですか? Activity.onCreate の完了時、または A.oC がまだ実行中のときですか?

4

8 に答える 8

55

ServiceConnection.onServiceConnected が確実に呼び出されるのを待つにはどうすればよいですか?

あなたはそうしない。から(またはバインドしている場所から)終了し、onCreate()「接続を確立する必要がある」コードを に入れますonServiceConnected()

すべてのイベント ハンドラー: Activity.onCreate、任意の View.onClickListener.onClick、ServiceConnection.onServiceConnected など。実際には同じスレッドで呼び出されます。

はい。

ServiceConnection.onServiceConnected が実際に呼び出されるのはいつですか? Activity.onCreate の完了時、または A.oC がまだ実行中のときですか?

あなたのバインド要求は、あなたが去るまでおそらく開始onCreate()されません。したがって、onServiceConnected()あなたが去った後、いつか呼び出されますonCreate()

于 2010-06-16T17:35:59.777 に答える
3

私はこのようなものになりました:

1)補助的なものにスコープを与えるために、内部クラスを作成しました。少なくとも、醜い内部はコードの残りの部分から分離されています。何かをするリモートサービスが必要だったので、Somethingクラス名の単語

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

2) リモート サービス メソッドを呼び出すには、IBinder と実行するコードの 2 つが必要です。どちらが最初に知られるようになるかわからないため、それらを保存します。

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

RemoteSomethingHelperもちろん、これは Runnable が mISomethingService にアクセスできることを前提としていますが、これはクラスのメソッド内で作成された runnable に当てはまります。

ServiceConnectionコールバックが UI スレッドで呼び出されるのは本当に良いことです。メイン スレッドからサービス メソッドを呼び出す場合、同期を気にする必要はありません。

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(); で結果のコールバックが必要になる可能性があります。私はしましたが、これはこのコードには示されていません。

于 2013-08-15T12:33:53.780 に答える
3

私も同じ問題を抱えていました。onServiceConnectedただし、バインド/バインド解除したいので、バインドされたサービスに依存するコードを に入れたくonStartありonStop,ませんでしたが、アクティビティが前面に戻るたびにコードを再度実行したくありませんでした。アクティビティが最初に作成されたときにのみ実行したかったのです。

私はついにonStart()視野狭窄を克服し、ブール値を使用して、これが最初のonServiceConnected実行かどうかを示しました。そうすれば、毎回すべての起動処理を実行することなくonStop、unbindService と bindService を再度実行できます。onStart

于 2012-11-09T07:50:29.327 に答える
1

私は以前に似たようなことをしましたが、唯一の違いは、サービスに拘束されていなかったのですが、サービスを開始しただけです。

サービスからインテントをブロードキャストして、発信者/アクティビティにサービスが開始されたことを通知します。

于 2010-06-16T17:20:13.653 に答える
0

これらの回避策は、バインドされたサービスがアプリケーションのメイン プロセスとは異なるプロセスで実行されている場合にのみ、労力と待機の価値があることがわかりました。

同じプロセス (またはアプリケーション) でデータとメソッドにアクセスするために、シングルトン クラスを実装することになりました。クラスがいくつかのメソッドのコンテキストを必要とする場合、アプリケーション コンテキストをシングルトン クラスにリークします。もちろん、「即時実行」を中断するため、悪い結果があります。しかし、それは全体的により良い妥協だと思います。

于 2016-12-14T11:33:46.943 に答える
-1

*基本的な考え方は @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()inonCreate()/onStart()との両方が同じ main threadonServiceConnected() で呼び出されるため、機能しません。 待機が終了する前に呼び出されることはありません。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();
            // ...
        }
    });
}

それは私のために働いた。しかし、もっと良い方法があるかもしれません。

于 2016-01-20T12:14:07.800 に答える