Activity 和 Service 之间的通信

Posted

技术标签:

【中文标题】Activity 和 Service 之间的通信【英文标题】:Communication between Activity and Service 【发布时间】:2014-01-02 21:36:21 【问题描述】:

我正在尝试为 android 制作自己的 MusicPlayer。我遇到问题的地方是在后台运行一些东西。主要活动管理 GUI,到目前为止所有歌曲都在播放。我想将 GUI 和音乐播放类分开。我想将音乐管理部分放在服务中,而其他事情则保持原样。

我的问题是我无法组织 ,因为它们之间正在发生大量通信,包括双向移动对象。我尝试了许多在 Stack Overflow 上搜索过的技术,但每次遇到问题。我需要 Service 才能将对象发送到 Activity ,反之亦然。当我添加小部件时,我还希望它能够与服务通信。

感谢任何提示,如果您需要源代码,请在下面发表评论,但现在在此过渡中变得混乱。

有没有比调用一种从服务返回随机数的方法更高级的教程? :P

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

【问题讨论】:

【参考方案1】:

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

为了向服务发送数据,我使用了 Binder,它将服务实例返回到 Activity,然后 Activity 可以访问服务中的公共方法。

为了将数据从 Service 发送回 Activity,我使用了 Callbacks 接口,就像您在 Fragment 和 Activity 之间进行通信时使用的那样。

以下是每个代码示例: 下面的例子展示了 Activity 和 Service 的双向关系: Activity 有 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);
    

【讨论】:

我推荐使用bindService将Service与Activity绑定,然后调用service类中定义的方法。这对我来说更有意义,因为 Activity 可能会启动或暂停,但服务会一直存在。在上面的代码中,如果活动没有运行,“回调”变量(对活动的引用)可能会失败。它将导致 NPE。希望我的观点很清楚。 我建议在服务的 unBind 方法中将回调活动设置为 null,这样就不会发生内存泄漏 是的,我相信这个解决方案可能会有一些改进,这是基本模式。 在android中使用aidl怎么样?这是从服务发回对象的另一种方式。 @MotiBartov 你应该得到一个小说奖,谢谢你的解决方案! :)【参考方案2】:

更新:2016 年 7 月 10 日

IMO 我认为将 BroadcastReceiver 用于自定义事件是更好的方法 正如提到的信使不处理设备上的活动娱乐 旋转以及可能的内存泄漏。

您可以为活动中的事件创建自定义广播接收器,然后您也可以使用信使。

    在你的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);
    

    在您的Service 中,您从 Intent 接收此对象并在 Service as 中初始化 Messenger 变量

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

    然后写一个方法sendMessage给activity发送消息。

    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 接收消息时显示和隐藏 Activity 中的 ProgressBar。

【讨论】:

@DjDexter5GHz 谢谢你,但我会建议你整件事,在我的情况下,我必须隐藏/显示一个布局和隐藏/显示一个 ProgressBar,整个概念工作正常。你有很多对象,我希望它有效。 我正在尝试使用此代码解决它,将回复结果... 编辑:它适用于整数,我必须为对象实现 Parceable 但它有效。我可以从这里拿走。再次感谢! 让服务成为一个IntentService,那么你就可以很容易的将Intent发送给Service来做一些事情了。 MessageHandler如何访问Activity的progressBar,因为类是静态的? 在服务仍在运行时旋转设备怎么样?我假设随后将创建一个新的MessageHandler,并且服务的MessageHandler 将不再能够与活动通信。处理程序是您可以在 onSavedInstanceState() 中保存和恢复的东西吗?【参考方案3】:

Intent 是 Activitiy 和 Service 之间通信的良好解决方案。

在您的服务中接收 Intent 的快速解决方案是子类化 IntentService 类。它使用队列和工作线程处理表示为 Intent 的异步请求。

对于从服务到活动的通信,您可以广播意图,但不是使用来自 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);
    

示例活动

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

【讨论】:

如果我想在 onPause() 或 onDestroy() 之后收听服务怎么办??【参考方案4】:

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

正确答案: 活动调用 bindService() 来获取指向服务的指针是可以的。因为在维护连接时维护服务上下文。

答案错误: 指向 Activity 类的服务指针回调是不好的方法。在 Activity 上下文中,Activity 实例可能不为空,此处为 Release => 异常。

答案错误的解决方法: 服务向 Activity 发送意图。和通过 BroadcastReceiver 的 Activity 接收器意图。

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

【讨论】:

【参考方案5】:

这是一个简单的activity和service之间通信的例子

活动

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... 如果您在返回 Activity 时未注册 BroadcastReceiver,它将不会从服务中侦听任何内容...该服务将向 BroadcastReceiver 发送信息,但由于未注册,因此不会接收任何内容。 当您创建多个服务时,以下服务将从onStartCommand 开始。 您可以通过意图将信息传递给服务,并在onStartCommand 中获取信息 returnonStartCommand的区别:Difference between START_STICKY and START_REDELIVER_INTENT?,查看google官网:Services

【讨论】:

如果我想在 onPause() 或 onDestroy() 之后收听服务怎么办?? 你是什么意思,将输出保存在 getsharedpreference 中?并且当活动恢复时放它......例如:活动是== null ->保存在sharedPreference -> activity.resume ->从sharedPreference中获取数据.. 你的意思是verficarStatusServicio() 对不起,我没有放这个功能,它是一个验证器,比如检查状态服务,如果它正在运行或停止,或者你在服务中检查的东西......例如:我想展示在TextView 中运行或停止...我创建了一个检查状态的函数,(检查 service.class 中的静态变量)如果它具有“正在运行”或“停止”或者是否存在......【参考方案6】:

在这种情况下,最好的方法是通过从您的服务广播不同的操作并在您的活动中接收它来进行通信。您可以创建自定义广播并发送一些定义特定事件的代码,例如完成、更改、准备等...

【讨论】:

你知道任何教程吗?我尝试了 BrodacastReceivers 并最终遇到了实例化服务的问题。这是让人头疼的部分。 感谢任何显示初始化和双向对象通信的示例代码。 嗯...我自己做了,我不知道是否有完整的教程。但是你可以搜索它的部分......比如自定义广播接收器等并组装它一起...:)【参考方案7】:

最简单有效的方法是使用来自 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 上,Activity 和 Fragment 通常应该根据它们的生命周期进行注册:

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

【讨论】:

我实际使用过这个解决方案。如果你有大项目代码很容易混乱。我发现将 Dagger 和依赖注入与 Repository 模式和 MVP 结合使用可以得到最好的结果。【参考方案8】:

非常简单而强大的方法是使用 EventBus,您可以将其添加到您的 gradle 构建并享受简单的发布者/订阅者模式。

【讨论】:

我不推荐 EventBus,因为我确实尝试过它,但我在地狱中完成了,因为我最终调试了很多代码,却不知道从应用程序的哪个部分调用。

以上是关于Activity 和 Service 之间的通信的主要内容,如果未能解决你的问题,请参考以下文章

activity 与 service 之间的通信

示例:使用消息传递在 Activity 和 Service 之间进行通信

使用Messenger进行Activity与Service之间的相互通信

关于android编程中service和activity的区别

Android跨进程通信

Android自学之路——Service与Activity通信