web-dev-qa-db-ja.com

Androidサービスが実際に接続されるまでどのように待つのですか?

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が実際に呼び出されるのはいつですか?アクティビティの完了時に。 onCreateまたはA.oCがまだ実行されているとき

44
Ryan

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

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

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

はい。

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

おそらく、onCreate()を離れるまで、バインド要求はstartに送られません。したがって、onServiceConnected()を離れると、onCreate()が呼び出されます。

49
CommonsWare

同じ問題がありました。ただし、バインドされたサービス依存コードをonServiceConnectedに入れたくありませんでした。なぜなら、onStartと_onStop,_でバインド/アンバインドしたかったのですが、アクティビティが戻ってくるたびにコードを再度実行したくなかったからですフロント。アクティビティが最初に作成されたときにのみ実行したかったのです。

私はついにonStart()トンネルのビジョンを乗り越え、これが最初のonServiceConnectedの実行であるかどうかを示すためにブール値を使用しました。こうすることで、毎回すべてのスタートアップを実行することなく、onStopでunbindServiceを、onStartでbindServiceを再度バインドできます。

2
froman

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

1)補助的なものにスコープを与えるために、内部クラスを作成しました。少なくとも、い内部構造は残りのコードから分離されています。リモートサービスでsomethingが必要だったため、クラス名のWord 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();
    }
_

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

ServiceConnectionコールバック Iスレッドで呼び出される :メインスレッドからサービスメソッドを呼び出す場合、同期を気にする必要はありません。

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

以前に似たようなことをしましたが、唯一の違いは、サービスにバインドしていませんでしたが、ただそれを開始したことです。

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

1
xandy

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

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

0
Hamid Khan

*基本的な考え方は@ 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を定義し、サービス接続後にこの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();
            // ...
        }
    });
}
_

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

0
corochann