Service是一个应用程序组件,它能够在后台执行一些耗时较长的操作,并且不提供用户界面。服务能被其他应用程序的组件启动,即使用户切换到另外的应用时,还能保持后台运行。此外,应用程序组件还能与服务绑定,并与服务进行交互,还能进行进程间通信(IPC)。比如,服务可以处理网络传输、音乐播放、执行文件I/O、或者与contenProvider进行交互,所有这些都是后台进行的。
开启服务有2种方式,Service的生命周期也不一样:
左图调用startService方式开启服务;右图调用bindService方式启动服务
自定义服务
public class MyService extends Service {
@Override
public void onCreate() {
Log.d(Constant.TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constant.TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(Constant.TAG,"onBind");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(Constant.TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(Constant.TAG,"onDestroy");
super.onDestroy();
}
private class MyBinder extends Binder{
}
单一方式开启服务
- startService方式开启服务
在Activity中调用下面的代码开启服务
Intent intent = new Intent(this,MyService.class);
startService(intent);
log日志输出
调用的方法是onCreate → onStartCommand
如果再次调用startService,日志输出
由此可见,onCreate方法只会调用一次,如果再次开启服务,只会调用onStartCommand()方法
调用stopService代码
Intent intent = new Intent(this, MyService.class);
stopService(intent);
日志输出:
执行的是onDestroy方法,服务被销毁。再次执行停止服务,没有任何日志输出,说明onDestroy方法只会执行一次,停止已经停止的服务不会报错。
总结:通过startService()方法开启服务,服务的生命周期 onCreate → onStartCommand →onDestroy;
PS:如果服务已经开启,再次调用startService,只会执行onStartCommand方法。
- bindService方法开启服务
调用代码:
Intent intent = new Intent(this, MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当服务被连接,并且服务的onBind()方法返回的IBinder对象不为null时,执行
Log.d(Constant.TAG, "onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服务被意外销毁时执行,服务正常destroy时不执行该方法
Log.d(Constant.TAG, "onServiceDisconnected");
}
}, Service.BIND_AUTO_CREATE);
方法介绍
public boolean bindService(Intent service,ServiceConnection conn,int flags);
第一个参数:意图,需要绑定的service
第二个参数:ServiceConnection,服务连接的中间类
第三个参数:flags。一般都传Service.BIND_AUTO_CREATE,意思是:绑定服务时,如果服务没有被开启,服务就会被自动开启起来
MyService中需要在onBind方法中返回IBinder对象。IBinder是一个接口,需要写一个类去实现该接口。由于该接口需要实现的方法较多,写起来不方便,Android提供了它的实现类Binder。只需写一个类去继承Binder即可。
调用绑定服务的日志输出
如果在该类中再次调用绑定该服务的方法,日志输出:
只会执行ServiceConnection中的onServiceConnected方法,并且不会报错
调用解除服务,日志输出:
先执行onUndind方法,在执行onDestroy方法
如果再次调用onUnbind方法,程序会报错
原因是服务没有注册。
PS:小技巧:调用unbindService方法时,加上try catch
总结:绑定服务的生命周期方法执行onCreate→ onBind→onUnbind → onDestroy ,而且每个方法只执行一次
如果在其他的地方绑定已经被绑定的service会出现什么结果?
测试代码的日志输出:
上面的结果中返回的IBinder对象的地址值相同,表明是同一个对象
由此可见:当服务绑定成功后,可以在其他地方再次绑定服务。不管在哪个地方再次绑定该服务,ServiceConnection中返回的IBinder对象是同一个。
但是需要注意的是,调用解除绑定服务的方法,必须是第一次调用bindService()方法的类中,否侧绑定服务失败,程序还会crash,错误的原因是服务没有注册,需要try catch。
调用服务中的方法
通过上面2个开启服务的方法,可以发现如果想调用服务里面的方法,只有通过绑定服务的方式。绑定服务成功后,会返回服务里面的内部类的IBinder对象,通过内部类来调用外部类(服务)中的方法,即可实现进程间的通讯。
如果直接将Service的实现IBinder内部类暴露给外界,这样就有点不符合封装的思想。可以通过接口的方式实现。代码如下:
1,定义一个IService的接口
public interface IService {
void callServiceMethod();
}
2,MyService中的MyBinder实现方式:
public class MyService extends Service {
@Override
public void onCreate() {
Log.d(Constant.TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constant.TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(Constant.TAG, "onBind");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(Constant.TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(Constant.TAG, "onDestroy");
super.onDestroy();
}
//实现IService接口,暴露方法给外界调用
private class MyBinder extends Binder implements IService {
@Override
public void callServiceMethod() {
serviceMethod();
}
}
private void serviceMethod() {
Log.d(Constant.TAG, "method in My service is called ");
}
}
在Activity中的调用:
Intent intent = new Intent(this, MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当服务被连接,并且服务的onBind()方法返回的IBinder对象不为null时,执行
Log.d(Constant.TAG, "onServiceConnected");
//将IBinder对象强转成IService对象,调用Service中的方法
IService iService = (IService) service;
iService.callServiceMethod();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服务被意外销毁时执行,服务正常destroy时不执行该方法
Log.d(Constant.TAG, "onServiceDisconnected");
}
}, Service.BIND_AUTO_CREATE);
- 服务的混合开启方式
调用顺序 startService→bindService→unbindService→stopService 日志输入:
调用顺序 bindService→startService→stopService→unbindService 日志输出:
其他情况下的生命周期方法调用比较混乱。
由此可见开启和关闭服务需要使用配对的方法。即通过startService方法开启的服务只能通过stopService方法开关闭服务,通过bindService方法开启的服务需要调用unbindService方法关闭服务。
如果想服务长期在后台运行,同时又想调用服务中的方法,建议使用的混合开启的方式。
调用顺序 startService → bindService → unbindService → stopService
上述都是调用本地服务的用法。
使用AIDL调用服务,即调用其他应用的Service中的方法来交互。
- AIDL调用本地服务
在main目录下,新建一个aidl目录,再创建一个aidl文件
文件中的代码:
// IService.aidl
package com.lh.android.servicedemo;
// Declare any non-default types here with import statements
interface IService {
void callServiceMethod();
}
编译后会在build目录下生产一个文件
打开该文件可以看到下面的代码
即Stub这个类继承了Binder类并且实现了IService接口,在Service中可以这样写
public class MyService extends Service {
@Override
public void onCreate() {
Log.d(Constant.TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constant.TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(Constant.TAG, "onBind");
//返回的是继承了IService.Stub类的对象
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(Constant.TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(Constant.TAG, "onDestroy");
super.onDestroy();
}
/**
* MyBinder继承IService.Stub(抽象类),实现抽象的方法
* IService.Stub类实现了IService接口,即需要实现该接口中的方法
*
private class MyBinder extends IService.Stub {
@Override
public void callServiceMethod() throws RemoteException {
serviceMethod();
}
}
private void serviceMethod() {
Log.d(Constant.TAG, "method in My service is called ");
}
}
在Activity中的调用就变成了
Intent intent = new Intent(this, MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当服务被连接,并且服务的onBind()方法返回的IBinder对象不为null时,执行
Log.d(Constant.TAG, "onServiceConnected");
IService iService = IService.Stub.asInterface(service);
try {
iService.callServiceMethod();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服务被意外销毁时执行,服务正常destroy时不执行该方法
Log.d(Constant.TAG, "onServiceDisconnected");
}
}, Service.BIND_AUTO_CREATE);
当然,如果在一个app里面想要调用另外一个app里面的服务,即调用远程服务,实现方式跟上面一模一样。
注意:通过aidl调用远程服务时,aidl文件的包名要跟调用的服务的包名一致。
aidl文件中的代码规则:不能有public等权限修饰符
方法中如果有参数
- 参数是基本数据类型(四类八种)的写法:直接表明数据类型即可。
interface IService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
- 参数是引用数据类型(对象)
需要该类实现 Parcelable 接口,示例如下:
Person类的定义如下:可以在该类中添加无参和带参的构造方法
public class Person implements Parcelable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
public void readFromParcel(Parcel in){
setName(in.readString());
}
protected Person(Parcel in) {
readFromParcel(in);
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
Person.aidl中的代码:
// Person.aidl
package com.lh.android.servicedemo;
parcelable Person;
注意:parcelable小写
IService中的代码
// IService.aidl
package com.lh.android.servicedemo;
import com.lh.android.servicedemo.Person;//注意一定要导包
// Declare any non-default types here with import statements
interface IService{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
//注意添加inout
void setPerson(inout Person p);
void setPersonList(inout List<Person> persons);
}
注意:
- 参数是引用数据类型的需要添加 inout;如果是集合也是一样的。
- 一定要导包(手动导包)。
特别注意:
- 如果在同一个项目中使用aidl,并且定义的参数有引用数据类型,那么实体bean不能放在aidl目录下面,要放在java代码的目录下面,但是要保证与aidl文件的包名一致。
- 如果不是在另外一个项目中调用本项目的服务,那么在另外一个项目中就可以把实体bean,bean的aidl文件以及要实现的aidl文件放在同一个包下,同时也要保证该包的包名与调用的服务中定义的aidl的包名是一致的。