Android中的Service与进程间通信(IPC)详解
Service
什么是Service
在后台长期运行的没有界面的组件。其他组件可以启动Service让他在后台运行,或者绑定Service与它进行交互,甚至实现进程间通信(IPC)。例如,可以让服务在后台处理网络交互,播放音乐,文件I/O,或者与ContentProvider交互。
创建一个Service
- 新建一个类,继承Service,重写相关方法,如onBind,onUnBind,onCreate,onDestorey。
- 在AndroidManifest.xml中配置Service和相关权限
<manifest ... > ... <application ... > <service android:name=".MyService" /> ... </application> </manifest>
开启服务:
Intent service = new Intent(this,Service.class); startService(service);
停止服务:
Intent service = new Intent(this,Service.class); stopService(service);
绑定服务:
private boolean mIsBound = false; private ServiceConnection mConnection = new ServiceConnection() { // 服务连接成功回调 @Override public void onServiceConnected(ComponentName name, IBinder service) { MyService.MyBinder binder = (MyService.MyBinder) service; } // 服务失去连接回调 @Override public void onServiceDisconnected(ComponentName name) { } }; @Event(value = R.id.btn_bind_service) private void onBindServiceClick(View view) { bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE);// 绑定时如果没有创建Service则自动创建Service。 mIsBound = true; }
解绑服务:
@Event(value = R.id.btn_unbind_service) private void onUnbindServiceClick(View view) { if (!mIsBound) { ToastUtil.show("未绑定服务"); return; } try { unbindService(mConnection);//注意:ServiceConnection要传绑定时的ServiceConnection对象,否则会报错。 } catch (Exception e) { ToastUtil.show("解除綁定服务失败"); e.printStackTrace(); } mIsBound = false; }
Service的生命周期
public class MyService extends Service { // 服务创建 @Override public void onCreate() { super.onCreate(); } // 每次startService都会调用;通过bindService方式启动服务,该方法不会被调用 @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } // 服务销毁 @Override public void onDestroy() { super.onDestroy(); } // bindService时调用,返回一个IBinder对象,用于与Service交互,IBinder就是Service的代理 @Nullable @Override public IBinder onBind(Intent intent) { return null; } // unbindService时调用 @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } }
startService:onCreate——>onStartCommand
stopService:onDestory
注意:服务只会被创建一次,如果服务已经创建,并且没有销毁,多次调用startService方法,只会执行onStartCommand方法。
bindService:onCreate——>onBind
unbindService:onUnbind——>onDestory
注意:
- 如果多次bindService,onBind方法只会在第一次绑定时被调用;同样,多次startService,onCreate方法也只会在第一次创建时被调用;
- 服务只能被解绑一次,服务需要先绑定才能解除绑定,多次解绑会报错。
- 通过bindService方式启动的Service,在调用unbindService时就会自动销毁。
- 服务只会停止一次,多次调用stopService()的方法无效,但不报错。
- 每次调用startService()开启服务都会执行onStartCommand()方法。
如何调用Service里的方法
由于系统框架在创建服务的时候会创建与之对应的上下文,直接new出来的服务对象是没有上下文的,所以直接new服务对象调用其方法会报异常。
与Service之间交互都是通过其代理人(IBinder)来间接调用Service里的方法的。
这样设计主要出于安全考虑,有限的暴露出一些方法,而不是直接返回服务对象,因为服务对象里可能有一些成员变量和方法不允许外界直接访问,需要保护起来。
一般IBinder(代理人)也应该设计成私有的,因为是IBinder中的一些数据也需要保护起来,只需要暴露出一些指定的方法,那么外界如何引用IBinder对象呢?通过接口引用代理人,在接口定义供外界调用的方法,让IBinder类实现该接口。
bindService与startService
bindService与startService的区别:
- 绑定服务:可以间接调用服务里面的方法;如果绑定的Activity被销毁了,服务也会跟着销毁。
- 开启服务:不可以调用服务里面的方法;如果开启服务的Activity销毁,服务还可以长期的在后台运行。
既要保证服务长期在后台运行,又想去调用服务里面的方法。
步骤:
1. startService(),保证服务在后台长期的运行;
2. bindService(),获取中间人(IBinder对象),间接的调用服务里面的方法;
这时,解绑服务并不会导致服务销毁,服务可长期在后台运行。
注意:如果服务已经被绑定,直接调用stopService()是停不掉的,必须先解除绑定服务再调stopService(),服务才会被销毁。
示例代码
@ContentView(value = R.layout.activity_main) public class MainActivity extends AppCompatActivity { private boolean mIsBound = false; private ServiceConnection mConnection = new ServiceConnection() { // 服务连接成功回调 @Override public void onServiceConnected(ComponentName name, IBinder service) { LogUtil.d(name + " onServiceConnected"); PayService.PayBinder binder = (PayService.PayBinder) service; binder.pay(100); } // 服务失去连接回调 @Override public void onServiceDisconnected(ComponentName name) { LogUtil.d(name + " onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); x.view().inject(this); } @Override protected void onDestroy() { super.onDestroy(); //当Activity被销毁时解绑服务,因为如果已经绑定服务不显式解绑会报异常。 onUnbindServiceClick(null); } private Intent getServiceIntent() { return new Intent(this, PayService.class); } // 启动服务 @Event(value = R.id.btn_start_service) private void onStartServiceClick(View view) { startService(getServiceIntent()); } // 绑定服务 @Event(value = R.id.btn_bind_service) private void onBindServiceClick(View view) { bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE);// 绑定时如果没有创建服务则自动创建Service。 mIsBound = true; } // 解绑服务 @Event(value = R.id.btn_unbind_service) private void onUnbindServiceClick(View view) { if (!mIsBound) { ToastUtil.show("未绑定服务"); return; } try { unbindService(mConnection);//注意:ServiceConnection要传绑定时的ServiceConnection对象,否则会报错。 } catch (Exception e) { e.printStackTrace(); } mIsBound = false; } // 停止服务 @Event(value = R.id.btn_stop_service) private void onStopServiceClick(View view) { stopService(getServiceIntent()); } }
参考文档: https://developer.android.com/reference/android/app/Service.html
https://developer.android.com/guide/components/services.html
使用AIDL实现进程间通信
AIDL(Android Interface Definition Language)用于进程间通信接口的定义,是一种进程间通讯的规范 。
Service端:
1.New一个aidl文件在src目录下
2.在aidl文件中定义Service中对外开放的接口
// IPayService.aidl package linchaolong.android.aidldemo.service; // Declare any non-default types here with import statements /** * AIDL Demo * * Created by linchaolong on 2016/4/22. */ interface IPayService { void pay(int price); void startTimer(); void stopTimer(); /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. * * 翻译: * * 展示一些可以在AIDL中用作参数和返回值的基本类型。 * */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
aidl语言中没有权限修饰符,因为进程间通信接口权限肯定是public的。
3.aidl编写完成后,make一下工程,在build目录下就会生成该aidl文件对应的java文件,比如我这是IPayService.java。
4.在IPayService中有一个Stub静态类,继承了Binder和实现了IPayService接口,定义一个Binder类继承IPayService.Stub并实现相关接口。
@Override public IBinder onBind(Intent intent) { if (mBinder == null) { mBinder = new PayBinder(); } return mBinder; // 其他应用绑定服务时返回binder对象 } // Binder public class PayBinder extends IPayService.Stub { public void pay(int price) { PayService.this.pay(price); } public void startTimer() { PayService.this.startTimer(); } public void stopTimer() throws RemoteException { PayService.this.stopTimer(); } @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { // Does nothing } }
5.在AndroidManifest.xml配置Service
<service android:name=".service.PayService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="linchaolong.android.aidldemo.service.PayService" /> </intent-filter> </service>
到这里Service端就完成了,其他应用需要调用该Service只需要把aidl文件拷贝到自己工程的src目录下(make一下),并绑定服务即可得到IBinder对象,通过IBinder对象可以实现与Service的交互。
调用示例:
在onServiceConnected回调里,调用YourServiceInterface.Stub.asInterface(service)把IBinder对象转换为YourServiceInterface类型。
private IPayService iPayService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获取远程接口实例 iPayService = IPayService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG, "Service has unexpectedly disconnected"); iPayService = null; } };
绑定远程服务并调用IPC方法
/** * 判断是否已经绑定远程服务 * * @return */ private boolean isBinded() { return mIsBound && iPayService != null; } private Intent getServiceIntent() { return new Intent("linchaolong.android.aidldemo.service.PayService"); } // 绑定远程服务 @Event(value = R.id.btn_bind_remote_service) private void bindRemoteService(View view) { if (isBinded()) { showToast("已绑定远程服务"); return; } bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } // 调用远程服务方法 @Event(value = R.id.btn_call_service_pay) private void callServicePay(View view) { if (!isBinded()) { showToast("未绑定远程服务"); return; } try { // 通过IBinder对象调用远程服务中方法 iPayService.pay(100); } catch (RemoteException e) { e.printStackTrace(); } }
Demo地址:https://coding.net/u/linchaolong/p/AIDLDemo/git
参考文档: http://developer.android.com/guide/components/aidl.html