活动与服务之间的沟通

Dej*_*jan 51 android message-passing android-service android-activity

我正在尝试为Android创建自己的MusicPlayer.我遇到问题的地方是在后台运行一些东西.主要活动管理GUI,到目前为止所有的歌曲都在播放.我想分开GUI和音乐播放课程.我想把音乐管理部分放在服务中,并留下现在的其他东西.

我的问题是我无法组织活动和服务之间的沟通,因为它们之间正在进行大量的沟通,包括双向移动物体.我尝试了很多技术,我在这里搜索Stack Overflow但每次遇到问题时都会这样做.我需要Service才能将对象发送到Activity,反之亦然.当我添加小部件时,我也希望它能够与服务进行通信.

任何提示都很受欢迎,如果你需要源代码发表评论,但现在在这个转变中它变得混乱.

是否有更高级的教程,而不是调用一个从服务返回随机数的方法?:P

编辑:可能的解决方案是使用RoboGuice库并注入移动对象

Mot*_*tov 70

我已经使用Bind和Callbacks接口实现了Activity和Service之间的通信.

为了将数据发送到服务,我使用了Binder,它将Service instace重新转换为Activity,然后Activity可以访问Service中的公共方法.

为了从服务中将数据发送回Activity,我使用了Callbacks界面,就像你想要在Fragment和Activity之间进行通信一样.

以下是每个代码示例:以下示例显示了Activity和Service双向关系:Activity有2个按钮:第一个按钮将启动和停止服务.第二个按钮将启动在服务中运行的计时器.

该服务将通过回调更新Activity以及计时器进度.

我的活动:

    //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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是服务:

 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我wolud建议在unBind服务方法中将Callbacks活动设置为null,这样就不会有内存泄漏 (3认同)
  • 我建议使用bindService将Service与Activity绑定,然后调用服务类中定义的方法。这对我来说更有意义,因为该活动可能已启动或暂停,但服务仍然存在。在上面的代码中,如果活动未运行,则“回调”变量(对活动的引用)可能会失败。它将导致NPE。希望我的观点清楚。 (2认同)

Rac*_*hra 55

更新:2016年7月10日

IMO我认为使用BroadcastReceiver进行自定义事件是更好的方法,因为所提到的Messengers不会处理设备轮换时的活动重新创建以及可能的内存泄漏.

您可以为活动中的事件创建自定义BroadCast Receiver,然后您也可以使用Messenger.

  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;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    现在你可以将它的实例作为

    public static Handler messageHandler = new MessageHandler();
    
    Run Code Online (Sandbox Code Playgroud)

    启动Service与该Handler对象作为一个额外的数据,

    Intent startService = new Intent(context, SERVICE.class)
    startService.putExtra("MESSENGER", new Messenger(messageHandler));
    context.startService(startService);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在你Service的意图中,你从意图中接收这个对象,并Messenger在Service as中初始化变量

    private Messenger messageHandler;
    Bundle extras = intent.getExtras();
    messageHandler = (Messenger) extras.get("MESSENGER");
    sendMessage(ProgressBarState.SHOW);
    
    Run Code Online (Sandbox Code Playgroud)

    然后编写一个方法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();
    }
    }
    
    Run Code Online (Sandbox Code Playgroud)

上面的示例代码显示并隐藏了Activity中的ProgressBar,因为从服务接收消息.

  • MessageHandler如何访问Activity的progressBar,因为该类是静态的? (5认同)
  • 在服务仍在运行时旋转设备怎么样?我假设将创建一个新的`MessageHandler`,服务的`MessageHandler`将不再能够与活动进行通信.Handler是否可以在onSavedInstanceState()中保存和恢复? (2认同)

mpo*_*lci 15

意图是Activitiy和Service之间通信的良好解决方案.

在服务中接收意图的快速解决方案是继承IntentService类.它使用队列和工作线程处理表示为Intents的异步请求.

对于从服务到Activity的通信,您可以广播意图,但不是使用Context中的普通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);
    }
}
Run Code Online (Sandbox Code Playgroud)

示例活动

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果我想在onPause()或onDestroy()之后听一个服务怎么办? (2认同)

Hun*_*NM2 5

我认为正确答案存在问题.我没有足够的声誉来评论它.

在答案中: Activity调用bindService()来获取指向Service的指针是正常的.因为在维护连接时会维护服务上下文.

错误的答案: 服务指针调用Activity类是不好的方法.在Activity上下文中,Activity实例可能不为null,这里是Release => exception.

解答错误的答案: 服务发送意图到Activity.和BroadcastReceiver的Activity接收器意图.

注意: 在这种情况下,Service和Activity在同一个Process中,你应该使用LocalBroadcastManager来发送intent.它使性能和安全性更好