Service作为Android四大组件之一,承载着重要的作用,同时,熟悉Service也会为理解Binder打下重要的基础,这里是我初学Android时做的关于Service的笔记,现在总结到这篇文章中。
概述
android中的service与Windows中的服务类似,一般没有用户界面,运行在后台,可以执行耗时的操作,是安卓四大组件之一。其他组件可以启动service,并且当用户切换另外的场景,service可以一直在后台运行。
服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,访问(启动)者与服务之间没有关连,即使访问(启动)者退出了,服务仍然运行。采用Context.startService()方法启动服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
使用bindService()方法启用服务,访问者与服务绑定在了一起,访问者一旦退出,服务也就终止。
service的运行是单例模式,只会实例化一次,开启一次之后再开启还是之前的那个对象,不会再新实例化。 Service和其他的应用组件一样,运行在进程的主线程中。这就是说如果service需要很多耗时或者阻塞的操作,需要在其子线程中实现。
创建服务步骤
创建一个类,继承service
public class MyService extends Service {
//必须实现方法,方法返回IBinder对象,应用程序可通过该对象与Service组件通信
public IBinder onBind(Intent intent) {
Log.i("Other", "MyService.onBind");
return null;
}
//生命周期方法,当Service第一次创建后将立即回调该方法
public void onCreate() {
super.onCreate();
Log.i("Other", "MyService.onCreate");
}
//当Service被关闭之前将回调该方法
public void onDestroy() {
super.onDestroy();
Log.i("Other", "MyService.onDestroy");
}
//每次客户端调用startService(Intent)方法启动该Service时都会回调该方法
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Other", "MyService.onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
//当该Service上绑定的所有客户端都断开连接时将回调该方法
public boolean onUnbind(Intent intent) {
Log.i("Other", "MyService.onUnbind");
return super.onUnbind(intent);
}
在配置清单中注册该service
<!-- 注册服务 -->
<service android:name=".MyService">
<intent-filter>
<action android:name="cn.itcast.service.myservice.action" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
启动服务
服务不能自己启动,需要手动启动,有两种方法启动
1、Context.startService(Intent)
访问者与服务没有关联,即使访问者退出,服务仍然运行,
若服务没启动:onCreate()-->onStartCommand(),如服务已经启动,直接调用onStartCommand()
通过Context.stopService(Intent)关闭服务,调用此方法,此时若服务没启动:调用stopService()方法,service不做任何操作,若服务启动:调用onDestory()方法
2、Context.bindService()
访问者与服务绑定在一起,访问者一旦退出,服务终止
以上创建了一个Service,只是重写了周期方法,若希望Service组件做某些事情,那么只要在onCreate()或onStartCommand()方法中定义相关义务代码即可
绑定本地Service并与之通信(进程内部绑定服务)
如果service与访问者需要进行方法调用或数据交换,则应该使用bindService()和unbindService()方法绑定、解绑服务。访问者与服务通过IBinder对象联系在一起,bindService()方法调用时,需要Intent,ServiceConnection和flag参数,其中serviceConnetion对象用于监听访问者与service之间的连接情况,用于接收服务onBind()方法返回的IBinder对象,IBinder中包含service传给调用者的数据。当连接成功时,serviceConnection()将回调方法onServiceConnected(Component Name,IBinder service);IBinder对象会传入其中。图解如下:
bindService()完整的方法签名为:bindService(Intent service,ServiceConnection conn,int flags),解释如下:
- service:该参数通过Intent指定要启动的Service
- conn:连接,该参数用于接收绑定服务后接收服务传递过来Binder,数据封装在IBinder类中,一般会根据service业务继承ServiceConnection类编写Connection代码,在本例中
/**
* 服务连接对象,是调用者和服务联系的核心
*/
class CustomerServiceConnection implements ServiceConnection{
/**
* 服务连接上之后会回调该方法,服务传回来的数据在binder中
*/
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.i("Other","service connected");
ics = (ICustomerSerice) binder;
}
/**
* 服务连接断开之后会回调该方法.注意,当调用者主动通过unbindService()方法断开Service的连接时,
* onServiceConnected()方法不会被调用。只有Service所在宿主进程由 于异常或其他原因终止,才会调用到该方法
*/
public void onServiceDisconnected(ComponentName name) {
}
}
编写好ServiceConnection后,我们就可以通过bindService()来绑定服务了, 访问者调用bindService()绑定服务后,服务代码中会调用public IBinder onbind()方法,方法返回IBinder,IBinder是一个接口,我们可以编写我们的业务代码实现该接口,这样,我们的业务方法就也返回给了访问者,访问者就可以使用这些业务方法。
/**
* 进程内部通信,使用bind方法开启服务.调用者和服务通过
*IBinder对象联系在一 起.
*/
public class CustomerService extends Service {
//访问者绑定服务,服务调用该方法
public IBinder onBind(Intent intent) {
Log.i("Other", "CustomerService.onBind");
//返回自己编写的业务Binder,该类实现IBinder接口,访问者在ServiceConnection的onServiceConnected方法中接收到该Binder对象
return new CustomerServiceBinder();
}
public void onCreate() {
super.onCreate();
Log.i("Other", "CustomerService.onCreate,tid=" + Thread.currentThread().getId());
}
public void onDestroy() {
super.onDestroy();
Log.i("Other", "CustomerService.onDestroy");
}
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Other", "CustomerService.onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
public boolean onUnbind(Intent intent) {
Log.i("Other", "CustomerService.onUnbind");
return super.onUnbind(intent);
}
/**
* 组合体,既继承了Binder(IBinder的实现类),同时实现自定义业务接口.
*/
class CustomerServiceBinder extends Binder implements ICustomerSerice{
//业务方法
public String sayHello(String name) {
Log.i("Other","CustomerServiceBinder.sayHello("+name+")");
return "hello "+ name ;
}
//业务方法
public Customer findCustomerByName(String name) {
Customer c = new Customer();
c.id = 1000;
c.name = name ;
c.age = 33;
return c;
}
}
}
图示如下:
进程间绑定服务——使用AIDL
对于进程间绑定服务,有时需要进程间共享业务或对象,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式, 以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口定义语言)方式实现。
与本地绑定Service不同的是,本地Service的onBind()方法会直接把IBinder对象本身传给客户端的ServiceConnection的onServiceConnected方法的第二个参数,但远程Service的onBind()方法只是将IBinder对象的代理类传给onServiceConnected的第二个参数。当客户端获取代理以后,就可以通过该IBinder对象回调远程Service的属性或者方法了。
AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB所采用的CORBA很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。
进程间服务绑定步骤
服务端
1)创建服务类,进程间服务绑定,同样使用onbind()方法开启服务
2)定义AIDL服务类接口文件(xxx.aidl)
编写AIDL需要注意:
1>、接口名与AIDL文件名相同。
2>、接口名和方法名前不能使用访问权限修饰符public,private,protected等,也不能用final,static
3>、AIDL默认支持的类型包括Java的基本类型(int,long,boolean等)和(String,List,Map,CharSequence),在文件中使用这些类型时不需要import声明,对于list与map中的元素必须是AIDL支持的类型,如果使用自定义的类型作为参数或者返回值,自定义类型(Java对象)必须继承Parcelable接口
4>、自定义类型和AIDL和AIDL生成的其他接口在aidl描述文件中,应该显示import,即便该类和aidl文件在同一个包下。
5>、在aidl文件中所有非java基本类型的参数必须加上(in,out,inout)标记,以指明参数是输入或输出或输入输出参数
6>、Java原始默认标记为in,不能为其他标记
AIDL定义接口文件代码如下:
package cn.itcast.service3;
//符合要求4>
import cn.itcast.service3.Person;
interface IPersonService {
String sayHello(in String name);
Person findPersonByName(in String name);
}
android平台会根据aidl文件自动生成Java类,保存在gen文件夹下,注意该文件名与服务类名一致。
//生成的Java类中有一个抽象类Stub,它继承Binder并实现业务接口方法,如下:
IPersionService{
abstarct class Stub extends Binder implements IPersionService{
class proxy{...}
}
}
3)创建javabean
如果要求该Javabean在进程间传递,需要实现Parcelable(邮包,类似于Java中Serializable)接口,并且增加静态字段CREATOR,用于反序列化:
package cn.itcast.service3;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Javabean
*/
public class Person implements Parcelable {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
/**
* 将javabean信息写入邮包,等价于序列化过程,顺序很关键(反序列化时和序列化顺序相一致)
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(age);
}
/**
* 静态成员,必须是CREATOR,该对象实现Parcelable.Createor接口,用于反 * 序列化
*/
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
//从邮包中得到对象,反序列化过程
public Person createFromParcel(Parcel source) {
Person p = new Person();
p.setId(source.readInt());
p.setName(source.readString());
p.setAge(source.readInt());
return p ;
}
public Person[] newArray(int size) {
return new Person[size];
}
};
}
同时,需要定义 Javabean 的 AIDL 文件,文件名与 Javabean 类名相同,并且放在同一包下,代码如下:
package cn.itcast.service3;
parcelable Person;
4)编写service中onBind()方法
编写AIDL接口,gen目录下生成的IPersionService.java代码中当中,抽象类Stub 既继承了Binder类(是IBinder接口的实现类),也实现了AIDL接口文件中定义的业务方法,所以onBind方法返回Stub,符合要求。在进程内部业务绑定中,需要我们自己编写内部类来继承Binder和实现业务接口,而在进程间通信中,自动生成的Stub实现了该功能。
public IBinder onBind(Intent intent) {
Log.i("Other", "PersonService.onBind");
//AIDL接口文件中定义的业务方法
return new IPersonService.Stub() {
public String sayHello(String name) throws RemoteException {
Log.i("Other","PersonService.sayHello()=" + name);
return "hello " + name;
}
//AIDL接口文件中定义的业务方法
public Person findPersonByName(String name) throws RemoteException {
Person p = new Person();
p.setId(2000);
p.setName(name);
p.setAge(23);
return p;
}
};
}
客户端部分
1、复制服务端的AIDL文件到客户端src文件夹下,(所有aidl文件和javabean,包名一致)
2、在MainActivity中创建服务连接内部类PersionConnection implements ServiceConnetcion,并接收IBinder
/**
* 服务连接对象
*/
//服务端的业务接口
private IPersonService ips;
class PersonConnection implements ServiceConnection{
//通过生成的类调用其方法,返回业务实现对象,代理对象(封装了进行间的通信细节)
public void onServiceConnected(ComponentName name, IBinder service) {
ips = IPersonService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName name) {
}
}
这里调用asInterface()方法其实返回的是stub内部类Proxy,客户端通过该代理可以返回到服务端提供的业务方法。
3、绑定服务,进程间绑定采用隐示意图
//绑定远程服务的隐式意图
Intent i = new Intent();
i.setAction("cn.itcast.service.personservice.action");
this.bindService(i, conn, Context.BIND_AUTO_CREATE);
Toast.makeText(this, "远程绑定ok", 1).show();
4、调用业务方法
try {
Toast.makeText(this, ips.sayHello("kkk"),1).show();
} catch (RemoteException e) {
e.printStackTrace();
}
try {
Person p = ips.findPersonByName("jerry");
Toast.makeText(this,p.toString(),1).show();
} catch (RemoteException e) {
e.printStackTrace();
}
tips
如果开发者需要在Service中处理耗时任务,需要在Service中另外启动一条新线程处理耗时任务。并且不能再其他组件(Activity、BroadcastReceiver)中开启新线程进行耗时任务,Activity可能被用户退出,BroadcastReceiver的生命周期本来就很短,很可能出现的情况是在组件退出或已结束的情况下,耗时任务的进程就变成了空进程,系统内存很可能优先终止该进程,那么该进程的所有子线程也会被中止,这样很可能导致子线程无法执行完成。