前言
服务(Service)是Android中实现程序后台运行的解决方案,非常适合用于执行那些不需要和用户交互且长期运行的任务。
要点
- 依赖于创建服务时所在的应用进程,不是运行在一个独立的进程当中。
- 耗时操作需要在服务内部手动创建子线程,并在这里执行具体任务,否则就有可能会出现主线程被阻塞住的情况。
生命周期
Service 有三种启动服务的方式:
- 启动服务:startService()开启服务,stopService()关闭服务
startService() 回调 onCreate ——> onStartCommand
stopService() 回调 onDestroy
- 绑定服务:bindService()绑定服务,unbindService()解绑服务。客户端通过一个IBinder接口与服务进行通信
bindService() 回调 onCreate ——> onBind
unbindService() 回调 onUnBind——>onDestory
- 混合方式: 先开启服务,再绑定服务
startService() 回调 onCreate ——> onStartCommand; bindService() 回调 onBind,unbindService() 回调 onUnBind; stopService() 回调 onDestroy
要点:
- 无论开启service多少次,onCreate 方法仅会调用一次,并且系统只会创建Service的一个实例
- 整个生命周期方法里,只有onStartCommand 方法可以多次调用,其他方法只能调用1次
- onStartCommand() 调用次数= startService() 调用次数
- 多个客户端可以绑定到同一个服务上,当所有的客户端都解除绑定后,系统会销毁服务
小结:
- startService开启服务,但无法操作服务
- bindService绑定服务,还能操作服务
Service分类
- 按运行地点分为:本地服务和远程服务
本地服务
用于应用程序内部,实现一些耗时任务,最普通、最常用的后台服务Service。
实例
MyService 类
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
System.out.println("执行了onCreat()");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("执行了onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("执行了onDestory()");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private MyBinder mBinder = new MyBinder();
class MyBinder extends Binder {
public String printServiceInfo() {
return "Activity 和 Service 建立通信,传递数据";
}
}
}
在Activity 里构建Intent对象,并调用startService()启动Service、stopService停止服务,bindService绑定服务、unbindService解绑服务
@Override
public void onClick(View view) {
Intent i = new Intent(MainActivity.this, MyService.class);
switch (view.getId()) {
case R.id.btn_start_service:
startService(i);
break;
case R.id.btn_stop_service:
stopService(i);
break;
case R.id.btn_bind_service:
bindService(i, connection, BIND_AUTO_CREATE);
break;
case R.id.btn_unbind_service:
unbindService(connection);
break;
}
}
private MyService.MyBinder myBinder;
//创建ServiceConnection的匿名类
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//实例化Service的内部类myBinder
//通过向下转型得到了MyBinder的实例,Binder实现了IBinder 接口
myBinder = (MyService.MyBinder) iBinder;
//在Activity调用Service类的方法
String info = myBinder.printServiceInfo();
System.out.println("---------->" + info);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
在AndroidManifest.xml里注册Service
<service android:name=".MyService" />
运行结果为
远程服务
用于Android系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。
步骤
服务器端:
- 新建定义AIDL文件,并声明该服务需要向客户端提供的接口
- 在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法
- 在AndroidMainfest.xml中注册服务且声明为远程服务
服务器端实例
新建AIDL文件,在新建AIDL文件里定义Service需要与Activity进行通信的内容(方法),并进行编译(Make Project)
interface AIDLService {
/**
* //AIDL中支持以下的数据类型
//1. 基本数据类型
//2. String 和CharSequence
//3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型;
//4. AIDL自动生成的接口(需要导入-import)
//5. 实现android.os.Parcelable 接口的类(需要导入-import)
*/
void aidlService();
}
在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法
public class ServiceDemo extends Service {
public ServiceDemo() {
}
@Override
public IBinder onBind(Intent intent) {
System.out.println("-------->onBind");
return mBinder;
}
AIDLService.Stub mBinder = new AIDLService.Stub() {
@Override
public void aidlService() throws RemoteException {
System.out.println("客户端通过AIDL与远程后台成功通信");
}
};
}
在AndroidMainfest.xml中注册服务 & 声明为远程服务
<service
android:name=".service.ServiceDemo"
android:exported="true" //设置可被其他进程调用
android:process=":remote">//将本地服务设置成远程服务
<intent-filter>
// 此处Intent的action必须写成“服务器端包名.aidl文件名”
<action android:name="com.xf.AIDLService"/>
</intent-filter>
</service>
客户端:
- 拷贝服务端的AIDL文件到目录下
- 使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
- 通过Intent指定服务端的服务名称和所在包,绑定远程Service
客户端实例
将服务端的AIDL文件所在的包复制到客户端目录下(Project/app/src/main),并进行编译
在Activity里,使用Stub.asInterface接口获取服务器的Binder;通过Intent指定服务端的服务名称和所在包,进行Service绑定;根据需要调用服务提供的接口方法。
// 开启服务
case R.id.btn_remote_service:
Intent intent = new Intent("com.xf.AIDLService");
intent.setPackage("com.xf");
bindService(intent, reConn, BIND_AUTO_CREATE);
break;
// 连接 远程服务
private AIDLService mAidlService;
private ServiceConnection reConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//使用asInterface()方法获取服务器端返回的IBinder对象
//将IBinder对象传换成了AIDLService接口对象
mAidlService = AIDLService.Stub.asInterface(iBinder);
try {
mAidlService.aidlService();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
测试结果:
从上面测试结果可以看出,客户端调用了服务端Service的方法,即客户端和服务端进行了跨进程通信。
本地服务与远程服务比较
- 按运行类型分为前台服务和后台服务
前台服务
前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。
实例
在Service类的onCreate方法开启前台服务
@Override
public void onCreate() {
super.onCreate();
System.out.println("------->onCreate");
// 前台服务
String channelID = "com.xf.serviceclientdemo";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelID, "前台服务", NotificationManager.IMPORTANCE_HIGH);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, channelID)
.setContentTitle("前台服务通知的标题")
.setContentText("前台服务通知的内容")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);//让Service变成前台Service,并在系统的状态栏显示出来
}
要从前台移除服务,请调用stopForeground()方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知
测试结果
前台服务与后台服务比较
Service相关知识
- bindService() 在进行服务绑定的时,其flags有:
- Context.BIND_AUTO_CREATE
表示收到绑定请求的时候,如果服务尚未创建,则即刻创建; 在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧。 - Context.BIND_DEBUG_UNBIND
通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用 - Context.BIND_NOT_FOREGROUND
表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行
- Service 元素的属性:
- android:name :服务类名
- android:label :服务的名字,如果此项不设置,那么默认显示的服务名则为类名
- android:icon:服务的图标
- android:permission:申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
- android:process:表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
- android:enabled :如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
- android:exported:表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false
- onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行
- START_STICKY
如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。
- START_NOT_STICKY
如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。
- START_REDELIVER_INTENT
如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。