web-dev-qa-db-ja.com

アクティビティとサービス間のコミュニケーション

Android用の独自のMusicPlayerを作成しようとしています。私が問題になったのは、バックグラウンドでいくつかのことを実行しています。メインアクティビティはGUIを管理し、現在まですべての曲が再生されています。 GUIと音楽の演奏クラスを分離したかった。私は音楽管理の部分をサービスに入れて、他のものは今のままにしておきたいと思っています。

私の問題は、双方向のオブジェクトの移動を含む多くの通信が行われているため、アクティビティとサービス間の通信を整理できないことです。ここでStack Overflowで検索した多くのテクニックを試しましたが、毎回問題が発生しました。オブジェクトをアクティビティに送信したり、その逆を行うにはサービスが必要です。ウィジェットを追加すると、サービスと通信できるようになります。

ソースコードの場所のコメントが必要な場合は、どんなヒントも歓迎しますが、この移行では混chaとしました。

これについて、サービスから乱数を返す1つのメソッドを呼び出すことよりも高度なチュートリアルはありますか? :P

編集:可能な解決策は、RoboGuiceライブラリを使用し、オブジェクトを注入して移動することです

50
Dejan

バインドとコールバックインターフェイスを使用して、アクティビティとサービス間の通信を実装しました。

サービスにデータを送信するために、アクティビティへのサービスインスタンスを再実行するバインダーを使用しました。その後、アクティビティはサービスのパブリックメソッドにアクセスできます。

サービスからアクティビティにデータを送り返すために、フラグメントとアクティビティ間で通信するときに使用しているように、コールバックインターフェイスを使用しました。

以下に、それぞれのコードサンプルを示します。次の例は、アクティビティとサービスの双方向関係を示しています。アクティビティには2つのボタンがあります。最初のボタンは、サービスを開始および停止します。 2番目のボタンは、サービスで実行されるタイマーを開始します。

サービスは、タイマーの進行状況とともにコールバックを通じてアクティビティを更新します。

私の活動:

    //Activity implements the Callbacks interface which defined in the Service  
    public class MainActivity extends ActionBarActivity implements MyService.Callbacks{

    ToggleButton toggleButton;
    ToggleButton tbStartTask;
    TextView tvServiceState;
    TextView tvServiceOutput;
    Intent serviceIntent;
    MyService myService;
    int seconds;
    int minutes;
    int hours;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         serviceIntent = new Intent(MainActivity.this, MyService.class);
        setViewsWidgets();
    }

    private void setViewsWidgets() {
        toggleButton = (ToggleButton)findViewById(R.id.toggleButton);
        toggleButton.setOnClickListener(btListener);
        tbStartTask = (ToggleButton)findViewById(R.id.tbStartServiceTask);
        tbStartTask.setOnClickListener(btListener);
        tvServiceState = (TextView)findViewById(R.id.tvServiceState);
        tvServiceOutput = (TextView)findViewById(R.id.tvServiceOutput);

    }

    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            Toast.makeText(MainActivity.this, "onServiceConnected called", Toast.LENGTH_SHORT).show();
            // We've binded to LocalService, cast the IBinder and get LocalService instance
            MyService.LocalBinder binder = (MyService.LocalBinder) service; 
            myService = binder.getServiceInstance(); //Get instance of your service! 
            myService.registerClient(MainActivity.this); //Activity register in the service as client for callabcks! 
            tvServiceState.setText("Connected to service...");
            tbStartTask.setEnabled(true);
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            Toast.makeText(MainActivity.this, "onServiceDisconnected called", Toast.LENGTH_SHORT).show();
            tvServiceState.setText("Service disconnected");
            tbStartTask.setEnabled(false);
        }
    };

    View.OnClickListener btListener =  new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(v == toggleButton){
                if(toggleButton.isChecked()){
                    startService(serviceIntent); //Starting the service 
                    bindService(serviceIntent, mConnection,            Context.BIND_AUTO_CREATE); //Binding to the service! 
                    Toast.makeText(MainActivity.this, "Button checked", Toast.LENGTH_SHORT).show();
                }else{
                    unbindService(mConnection);
                    stopService(serviceIntent);
                    Toast.makeText(MainActivity.this, "Button unchecked", Toast.LENGTH_SHORT).show();
                    tvServiceState.setText("Service disconnected");
                    tbStartTask.setEnabled(false);
                }
            }

            if(v == tbStartTask){
                if(tbStartTask.isChecked()){
                      myService.startCounter();
                }else{
                    myService.stopCounter();
                }
            }
        }
    };

    @Override
    public void updateClient(long millis) {
        seconds = (int) (millis / 1000) % 60 ;
        minutes = (int) ((millis / (1000*60)) % 60);
        hours   = (int) ((millis / (1000*60*60)) % 24);

        tvServiceOutput.setText((hours>0 ? String.format("%d:", hours) : "") + ((this.minutes<10 && this.hours > 0)? "0" + String.format("%d:", minutes) :  String.format("%d:", minutes)) + (this.seconds<10 ? "0" + this.seconds: this.seconds));
    }
}

そして、ここにサービスがあります:

 public class MyService extends Service {
    NotificationManager notificationManager;
    NotificationCompat.Builder mBuilder;
    Callbacks activity;
    private long startTime = 0;
    private long millis = 0;
    private final IBinder mBinder = new LocalBinder();
    Handler handler = new Handler();
    Runnable serviceRunnable = new Runnable() {
        @Override
        public void run() {
            millis = System.currentTimeMillis() - startTime;
            activity.updateClient(millis); //Update Activity (client) by the implementd callback
            handler.postDelayed(this, 1000);
        }
    };


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        //Do what you need in onStartCommand when service has been started
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    //returns the instance of the service
    public class LocalBinder extends Binder{
        public MyService getServiceInstance(){
            return MyService.this;
        }
    }

    //Here Activity register to the service as Callbacks client
    public void registerClient(Activity activity){
        this.activity = (Callbacks)activity;
    }

    public void startCounter(){
        startTime = System.currentTimeMillis();
        handler.postDelayed(serviceRunnable, 0);
        Toast.makeText(getApplicationContext(), "Counter started", Toast.LENGTH_SHORT).show();
    }

    public void stopCounter(){
        handler.removeCallbacks(serviceRunnable);
    }


    //callbacks interface for communication with service clients! 
    public interface Callbacks{
        public void updateClient(long data);
    }
}
70
Moti Bartov

更新:2016年7月10日

IMOカスタムイベントにBroadcastReceiverを使用することは、前述のメッセンジャーがデバイスのローテーションやメモリリークのアクティビティレクリエーションを処理しないため、より良い方法だと思います。

アクティビティのイベント用にカスタムBroadCast Receiverを作成できます。その後、メッセンジャーも使用できます。

  1. あなたのActivity

    messageHandlerクラスを作成します

    public static class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            int state = message.arg1;
            switch (state) {
            case HIDE:
                progressBar.setVisibility(View.GONE);
                break;
            case SHOW:
                progressBar.setVisibility(View.VISIBLE);
                break;
            }
        }
    }
    

    次のようにインスタンスを持つことができます

    public static Handler messageHandler = new MessageHandler();
    

    このHandlerオブジェクトを追加データとしてServiceを開始します

    Intent startService = new Intent(context, SERVICE.class)
    startService.putExtra("MESSENGER", new Messenger(messageHandler));
    context.startService(startService);
    
  2. Serviceで、このオブジェクトをインテントから受け取り、サービスのMessenger変数を次のように初期化します。

    private Messenger messageHandler;
    Bundle extras = intent.getExtras();
    messageHandler = (Messenger) extras.get("MESSENGER");
    sendMessage(ProgressBarState.SHOW);
    

    そして、アクティビティにメッセージを送信するメソッドsendMessageを作成します。

    public void sendMessage(ProgressBarState state) {
    Message message = Message.obtain();
    switch (state) {
        case SHOW :
            message.arg1 = Home.SHOW;
            break;
        case HIDE :
            message.arg1 = Home.HIDE;
            break;
    }
    try {
        messageHandler.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    }
    

上記のサンプルコードは、Serviceからメッセージを受信したときにアクティビティのProgressBarを表示および非表示にします。

55
Rachit Mishra

インテントは、ActivitiyとService間の通信に適したソリューションです。

サービスでインテントを受信するための高速なソリューションは、サブクラス化 IntentService classです。キューとワーカースレッドを使用して、インテントとして表現される非同期リクエストを処理します。

サービスからアクティビティへの通信では、インテントをブロードキャストできますが、コンテキストから通常のsendBroadcast()を使用する代わりに、サポートライブラリから LocalBroadcastManager を使用するのがより効率的な方法です。

サンプルサービス。

public class MyIntentService extends IntentService {
    private static final String ACTION_FOO = "com.myapp.action.FOO";
    private static final String EXTRA_PARAM_A = "com.myapp.extra.PARAM_A";

    public static final String BROADCAST_ACTION_BAZ = "com.myapp.broadcast_action.FOO";
    public static final String EXTRA_PARAM_B = "com.myapp.extra.PARAM_B";

    // called by activity to communicate to service
    public static void startActionFoo(Context context, String param1) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        context.startService(intent);
    }

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM_A);
                // do something
            }
        }
    }

    // called to send data to Activity
    public static void broadcastActionBaz(String param) {
        Intent intent = new Intent(BROADCAST_ACTION_BAZ);
        intent.putExtra(EXTRA_PARAM_B, param);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.sendBroadcast(intent);
    }
}

アクティビティ例

public class MainActivity extends ActionBarActivity {

    // handler for received data from service
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(MyIntentService.BROADCAST_ACTION_BAZ)) {
                final String param = intent.getStringExtra(EXTRA_PARAM_B);
                // do something
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        IntentFilter filter = new IntentFilter();
        filter.addAction(MyIntentService.BROADCAST_ACTION_BAZ);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.registerReceiver(mBroadcastReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.unregisterReceiver(mBroadcastReceiver);
        super.onDestroy();
    }

    // send data to MyService
    protected void communicateToService(String parameter) {
        MyIntentService.startActionFoo(this, parameter);
    }
}
15
mpolci

正解に問題があると思います。私はそれについてコメントするのに十分な評判がありません。

答えの右側:サービスへのポインタを取得するためのbindService()アクティビティ呼び出しは問題ありません。接続が維持されるときにサービスコンテキストが維持されるためです。

答えが間違っています:コールバックするActivityクラスへのサービスポインターは悪い方法です。アクティビティコンテキストがRelease =>例外である場合、アクティビティインスタンスはnullではない可能性があります。

答えの間違った解決策:サービスがアクティビティにインテントを送信します。 BroadcastReceiverを介したアクティビティ受信者の意図。

注:この場合、同じプロセスのサービスとアクティビティ、LocalBroadcastManagerを使用してインテントを送信する必要があります。パフォーマンスとセキュリティが向上します

5
HungNM2

この場合の最善の方法は、さまざまなアクションのためにサービスからブロードキャストを実行し、アクティビティでそれを受信することによって通信することです。カスタムブロードキャストを作成し、完了、変更、準備などの特定のイベントを定義するコードを送信できます。

2
Vikram

これは、アクティビティとサービス間の通信の簡単な例です

アクティビティ

MyReceiver myReceiver; //my global var receiver
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layourAwesomexD);
    registerReceiver();
}

//When the activity resume, the receiver is going to register...
@Override
protected void onResume() {
    super.onResume();
    checkStatusService(); // verficarStatusServicio(); <- name change
    registerReceiver();
}
//when the activity stop, the receiver is going to unregister...
@Override
protected void onStop() {
    unregisterReceiver(myReceiver); //unregister my receiver...
    super.onStop();
}
//function to register receiver :3
private void registerReceiver(){
    //Register BroadcastReceiver
    //to receive event from our service
    myReceiver = new MyReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(MyService.SENDMESAGGE);
    registerReceiver(myReceiver, intentFilter);
}

// class of receiver, the magic is here...
private class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        //verify if the extra var exist
        System.out.println(arg1.hasExtra("message")); // true or false
        //another example...
        System.out.println(arg1.getExtras().containsKey("message")); // true or false
        //if var exist only print or do some stuff
        if (arg1.hasExtra("message")) {
            //do what you want to
            System.out.println(arg1.getStringExtra("message"));
        }    
    }
}

public void checkStatusService(){
    if(MyService.serviceStatus!=null){
        if(MyService.serviceStatus == true){
            //do something
            //textview.text("Service is running");
        }else{
            //do something
            //textview.text("Service is not running");
        }
    }
}

サービス

public class MyService extends Service {

final static String SENDMESAGGE = "passMessage";

public static Boolean serviceStatus = false;

@Override
public void onCreate() {
    super.onCreate();
    serviceStatus=true;
}

@Nullable
@Override
    public IBinder onBind(Intent intent) {return null;}

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //you service etc...             
        passMessageToActivity("hello my friend this an example of send a string...");
        return START_STICKY;
    }

@Override
    public void onDestroy() {
        super.onDestroy();
        passMessageToActivity("The service is finished, This is going to be more cooler than the heart of your ex...");
        System.out.println("onDestroy");
        serviceStatus=false;
   }

private void passMessageToActivity(String message){
        Intent intent = new Intent();
        intent.setAction(SENDMESAGGE);
        intent.putExtra("message",message);
        sendBroadcast(intent);
    }
}
  • broadcastReceiverの登録を解除しないとエラーが発生します。アクティビティがonPause、onStop、onDestroyになったときに登録を解除する必要があります...
  • アクティビティに戻るときにBroadcastReceiverを登録しない場合、サービスから何もリッスンしません...サービスはBroadcastReceiverに情報を送信しますが、登録されていないため何も受信しません。
  • 複数のサービスを作成すると、次のサービスがonStartCommandで開始されます。
  • 情報を意図的にサービスに渡すことができ、onStartCommandで取得します
  • returnonStartCommandに関する違い: START_STICKYとSTART_REDELIVER_INTENTの違い? とGoogleの公式Webサイトを確認してください: Services
2
DarckBlezzer

最も簡単で効率的な方法は、GreenRobotの EventBus を使用することです。

簡単な3つの手順を使用します。

1イベントの定義

public static class MessageEvent { /* Additional fields if needed */ }

2サブスクライバーの準備:サブスクリプションメソッドを宣言して注釈を付け、オプションでスレッドモードを指定します:

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

サブスクライバーを登録および登録解除します。たとえば、Androidでは、アクティビティとフラグメントは通常、ライフサイクルに従って登録する必要があります。

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

3イベントのポスト:

EventBus.getDefault().post(new MessageEvent());
0
Khemraj

非常に簡単で強力な方法は、EventBusを使用することです。Gradleビルドに追加して、簡単なパブリッシャー/サブスクライバーパターンを楽しむことができます。

0
Melad
0
Dheeraj Sachan