android service和activity跨进程通讯

一,service的两种启动方式

service 作为一个应用程序组件,则意味着它可以在不同组件间、甚至在 不同应用间进行复用,还意味着可以配置成在另一个独立的进程中运行。

startService单向传递数据

bindService可通过binder实现数据的交互以及监听。


两种方式的生命周期

service未启动,然后调用

startService:onCreate()->onStartCommand()-----service running -----onDestroy()

bindService:onCreate()->onBind()----------service running  -------onUnbind()->onDestroy()


service已经通过startService启动之后,再调用

startService:onStartCommand()-----service running -----onDestroy()

bindService:onBind()----------service running  -------onUnbind()->onDestroy()


service已经通过bindService启动之后,再调用

startService:onStartCommand()-----service running -----onDestroy()

bindService:----------service running  -------onUnbind()->onDestroy()

注意:这里的onBind()不再调用,因为binder已经存在并缓存起来,绑定service成功后会直接返回binder,并不需要再调用onBind()了。


onCreate()

该方法仅在 service 第一次被创建时会被系统调用,可以用来执行一些一次性的初始化操作。

也就是,无论startservice,bindservice多少次,就永远只有一个service。


onStartCommand()

一个应用组件(例如 Activity)可以通过调用 startService()并传入一个 Intent 来启动指定的 service,Intent 可以带上所需的数据,Service 会在 onStartCommand() 回调中收到该 Intent。
例如,假如一个 Activity 需要上传一些数据,则可以 start 一个service 并透 过 Intent 把需要保存的数据(或数据的路径)传入。service在 onStartCommand() 中 收到 Intent,可以提取出数据并上传。上传完毕,你需要负责在适当的时 候,通过调用 stopService() 或 stopSelf() 来停止 service 的运行。

onStartCommand() 方法返回了一 个整数,该整数描述了当系统杀掉你的 service 时,后续会如何处置你的 service。 该返回值的取值范围如下:
START_NOT_STICKY
如果你的 service 被杀掉了,将不会自动重启(除非系统中还有未处理的 intent 请求等待投递)。
START_STICKY
如果系统杀掉了 service,将会在资源不再紧张时重新启动你的 service, 并调用 onStartCommand() 方法。注意,该方法 不会 重新传入上次的 intent,而是传入一个 null intent(除非系统中有待投递的请求,则会传 入待投递请求的 Intent)。这种情况适合类似媒体播放器的应用, 这类 service 在启动时并非执行某个指令,而是持续运行并等待任务。
START_REDELIVER_INTENT
如果 service 被杀掉了,将会被重启并且会传入上一次的 intent,其他未 处理的 intent 将会依次被投递。该选项适合用来执行一些希望立即被恢复的 任务,例如下载文件


onBind()

要创建一个 bindservice,你必须实现 onBind() 回调方法,并返回一个 IBinder 供 client 使用。其他的应用组件通过调用 bindService() 来绑定你 的 service,并获得 IBinder 接口以便和你的 service 交互。

这类 service 存在的目的就是为了服务于 bind 它的其他组件,所以,一旦没有组件 bind 它 了,则会被系统销毁。就是说如果执行了unbindService(),那么service就有可能会被销毁。

多个 clients 可以同时 bind 一个 service,当所有 clients 通过调用 unbindService() 解除绑定时,系统便会销毁该 service。

当一个 service 被多个 clients 绑定时,onBind() 只 会在第一次绑定时被系统调用一次,用于获取 IBinder 实例,后续的 bind 操作 系统会直接返回相同的 IBinder 实例,而不再调用 onBind()。这点和 onStartCommand() 有很大的区别。


onDestroy()

Called by the system to notify a Service that it is no longer used and is being removed. 

当 service 不再被使用了且正在被删除,系统会调用该方法以销毁该 service。你应该在 该方法中执行必要的清理工作,如停掉线程、反注册 listener、receiver 等。该 方法是系统最后调用的方法。



二,binder的跨进程通讯原理


binder机制是贯穿整个Android系统的进程间访问机制,经常被用来访问service。


Bound service 的创建和使用相对 started service 来说会复杂一些。这里重点介绍一下 IBinder 的几种创建方式。
要创建一个 bound service,最重要的是要先定义好与 client 交互 的接口,即 IBinder。根据应用场景的不同,Android 提供了三种创建 IBinder 的方法:
继承 Binder 类
这是最简单的一种方式。如果你的 service 是私有的(即仅供本 app 使 用),并且和 client 运行在同一个进程空间,则可以通过继承 Binder 来创建 你的接口类,并在 onBind() 方法中返回该类的实例。client 拿到该实例, 便可以访问任何你在该类中定义的 public 方法。
使用 Messenger 类
如果你需要跨进程访问你的 service,你可以通过 Messenger 来创建 IBinder 接口。这是一种最简单的实现 IPC 的方式,因为 Messenger 会把 所有的请求放入一个请求队列,这样你就不用担心同时处理多个请求,也就不 用担心线程安全问题了(你只需一次处理一个请求)。
使用 AIDL
AIDL (Android Interface Definition Language) 是最复杂的创建 IBinder 的方式。与 Messenger 相比,它允许你同时处理多个请求,也因此, 你需要编写多线程代码并处理多线程安全问题。事实上,Messenger 在底层 也是基于AIDL 实现的。在 Android 的官方文档中,特别提到一点,就是大 多数应用程序不需要也不应该使用这种方式来构建 IBinder 接口,因为这会 引入多线程代码,从而招致不必要的复杂性,除非你真的、真的确定你的 service 需要具备同时处理多个请求的能力。


三,service利用binder实现跨进称通讯

以下两种常用的通讯方式。

1,activity启动service,单向传送指令。

客户端启动service:

		Intent startIntent = new Intent();
		ComponentName componentName = new ComponentName(
				PACKAGE_SERVICE,
				NAME_SERVICE);
		startIntent.setComponent(componentName);
		startService(startIntent);
Intent是可以携带你想要的参数。

service端接收Intent:

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return START_STICKY;
	}

这里我想到一种应用场景,音乐播放,activity端选择一首歌,通过startService把music的id发过去,service收到id就启动音乐播放或音乐切换,并不需要返回数据给activity端。

有个问题,是不是每次切歌都是用startservice来传送指令呢?是的,有一篇介绍的IntentService,它处理这个问题的能力就更合适。IntentService本身也是继承Service的。


2,activity跟service进行数据交互(binder通讯)

这里有三种实现方式,原理上面已经讲过。

  • 通过继承Binder的方式实现本地service和activity通讯

public class LocalService extends Service {  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return new LocalBinder();  
    }     
          
    public void sayHelloWorld(){  
        Toast.makeText(this.getApplicationContext(), "Hello World Local Service!", Toast.LENGTH_SHORT).show();  
    }     
          
    public class LocalBinder extends Binder {  
        LocalService getService() {  
            // Return this instance of LocalService so clients can call public methods  
            return LocalService.this;  
        }     
    }     
}  
local servcie 的代码如上,在onBinder方法中返回binder,binder包含了service的句柄,客户端得到句柄以后就可以调用servcie的公共方法了,这种调用方式是最常见的。


客户端代码:

@Override  
    protected void onStart() {  
        super.onStart();  
        Log.d(TAG, this.getApplicationContext().getPackageCodePath());  
        Intent service = new Intent(this.getApplicationContext(),LocalService.class);  
        this.bindService(service, mSc, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        //must unbind the service otherwise the ServiceConnection will be leaked.  
        this.unbindService(mSc);  
    }  

mSc = new ServiceConnection(){  
            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                Log.d(TAG, "service connected");  
                LocalService ss = ((LocalBinder)service).getService();  
                ss.sayHelloWorld();  
            }  
  
            @Override  
            public void onServiceDisconnected(ComponentName name) {  
                Log.d(TAG, "service disconnected");  
            }  
        };  


  • 通过Messager实现通讯

这是基于Handler的IPC通信。Handler既是可以作为线程通讯,也可以作为进程间通讯工具。

		Intent startIntent = new Intent();
		ComponentName componentName = new ComponentName(
				PACKAGE_SERVICE,
				NAME_SERVICE);
		startIntent.setComponent(componentName);
		startService(startIntent);

Messenger 在进程间通信的方式和 Hanlder-Message 类似,Hanlder在A进程中,B进程持有A的 Messenger 通过此发送 Message 到A实现进程间通信。Messenger 是对 Binder 的简单包装。相对于 AIDL 方式,Messenger 是将请求放入消息队列中然后逐条取出处理的,而纯 AIDL 接口可以将多条请求并发传递到服务端(多线程处理请求)。如果不需要并发处理请求时,采用 Messenger 是更好的选择,这种方法更为简单易于实现,不需要额外写 AIDL,不需要考虑多线程,对于 Handler-Message 机制更为广大安卓开发者熟悉不易出错。

Messager也是google官方建议使用的一种进程通讯方式。这里先介绍其基本使用,后面会详细介绍aidl实现进程通讯。


客户端:

		Intent startIntent = new Intent();
		ComponentName componentName = new ComponentName(
				PACKAGE_SERVICE,
				NAME_SERVICE);
		startIntent.setComponent(componentName);
<span style="white-space:pre">		</span>bindService(startIntent, mSc, Context.BIND_AUTO_CREATE);

mSc = new ServiceConnection(){  
            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                Log.d(TAG, "service connected");  
                Messenger messenger = new Messenger(service);  
                Message msg = new Message();  
                msg.what = RemoteService.MSG_SAY_HELLO;
                msg.replyTo = messenger_reciever;  
                try {  
                    messenger.send(msg);</span>  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
  
            @Override  
            public void onServiceDisconnected(ComponentName name) {  
                Log.d(TAG, "service disconnected");  
            }  
        };  

服务端:

public class RemoteService extends Service {  
  
    public static final int MSG_SAY_HELLO = 0;  
  
    @Override  
    public IBinder onBind(Intent intent) {  
       return messager.getBinder(); 
    }  
  
    Handler IncomingHandler = new Handler() {  
  
        @Override  
        public void handleMessage(Message msg) {  
            if(msg.replyTo != null){  
                Message msg_client = this.obtainMessage();  
                msg_client.what = RemoteServiceTestActivity.SAY_HELLO_TO_CLIENT;  
                try {  
                    ((Messenger)msg.replyTo).send(msg_client);  
                } catch (RemoteException e) {  
                    // TODO Auto-generated catch block  
                    e.printStackTrace();  
                }  
            }  
            switch (msg.what) {  
                case MSG_SAY_HELLO:  
                    Toast.makeText(RemoteService.this.getApplicationContext(), "Hello World Remote Service!",  
                            Toast.LENGTH_SHORT).show();  
                    break;  
                default:  
            super.handleMessage(msg);  
            }  
        }  
  
    };  
      
    Messenger  messager = new Messenger (IncomingHandler);  
}  

  • 通过aidl实现IPC通讯

Android 是进程间内存是分离的,因此需要将对象分解成操作系统可理解的元数据,并将此打包让操作系统帮忙传递对象到另一个进程。这个过程是十分复杂繁重的,因此 Google 定义了 AIDL(Android Interface Definition Language)帮助开发者简化工作。一般,你只有在客户端需要访问另一进程的 Service ,且需要 Service 多纯种处理客户端请求时才有必要使用 AIDL;如果只需要在进程内和 Service 通信,只需要实现 Binder 通过 onBind() 返回对象;如果需要进程间通信但不需要并发处理请求,可考虑使用 Messenger,Messenger 底层实现和 AIDL 类似,上层采用 handler-message 方式通信,更为简单易用(具体请参考上文)。用 AIDL 实现进程间通信的步骤为:

创建 .aidl 文件

实现 .aidl 文件中定义的接口
向客户端曝露接口

关于aidl说来就话长了,为此我专门写了一篇aidl通讯的:http://blog.csdn.net/aa841538513/article/details/52221656

这里就不多说了。


3,activity监听service数据变化

这个问题也在上面那片文章中:http://blog.csdn.net/aa841538513/article/details/52221656


四,其他问题

1,service可实现自启动?

监听开机启动广播:android.intent.action.BOOT_COMPLETED

<receiver
            android:name="com.tencent.tws.watchgesture.receiver.BootCompleteReceiver"
            android:enabled="true" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />

                <category android:name="android.intent.category.HOME" />
            </intent-filter>
        </receiver>
在开机广播发送后在onReceive处开启服务startService(intent); intent包含了你想启动的service。

2,service如何常驻?

  • 系统只有在内存资源紧张且需要回收资源以供当前 Activity 使用时,才会强制 停止 service。
如果该 service 是 bind 到当前 Activity 的,则被杀的几率很 小,如果该 service 是在前台运行的(调用了 startForeground()方法),则几 乎不可能被杀掉。

如果 service 是普通的 started 的,则随着运行时间的增长,系统会降低其优 先级,在内存紧张时很可能会被杀掉。因此,你应该设计好你的 service,以便 可以优雅的应对重启的情况。如果系统杀掉了你的 service,一旦系统有足够的 资源,便会重新启动被杀的 service (依赖于你的onStartCommand() 的返回值, 此行为会有所不同。)。

  • service的onStartCommand()中返回START_STICKY或START_REDELIVER_INTENT可在资源不紧张的情况下重新自启动service。前面已有介绍。

3,同一个service的startService和bindService同时被调用会怎么样?

started 和 bind 并非互斥的概念,而是可以同时进行的,即一个 service 可以同时被 start 和 bind。

4,多次startService和多次bindService会怎么样?

每次 startService() 调用,都会产生一次 onStartCommand() 回调
每次 bindService() 调用,不一定会产生一次 onBind() 回调。只有 service 第一次被绑定时,才会产生 onBind() 调用

5,调用了多次startservice和bindservice后,再调用stopservice,那么service还在吗?

多个 start 请求对应多次 onStartCommand() 调用,但是仅需要一次 stop 请 求便可停止 service。

多次 bindService() 调用,需要对应多次 unbindService() 调用

6,有数据交互的时候用bindservice,没有数据交互的时候用startservice,那么他们具体有哪些应用场景呢?

7,service如何监督绑定它的activity活动?

8,如果只是通过bindservice实现同一进程下的activity和service通信,是不是可以不用aidl

9,service没有界面,如何实现便捷性的提示呢?

在 Service 中可以通过 toast 或者状态来通知用户某个事件的发生。
通常情况下,当某个后台任务完成时,使用状态栏通知是比较好的选择。例如, 当一个文件在 service 中下载完成了,可以创建一个状态栏通知来通知用户。当 用户点击该通知时,便可启动相应的 activity 来查看结果。

10,开发中要注意的地方
一般这种项目都是服务端和客户端分开开发的,需要有文档说明。服务端和客户端要沟通好aidl文件,保证一直。Messager的使用也是需要规定好接口规则和参数规则。

		Intent startIntent = new Intent();
		ComponentName componentName = new ComponentName(
				PACKAGE_SERVICE,
				NAME_SERVICE);
		startIntent.setComponent(componentName);
		startService(startIntent);
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页