Service是一种可以在后台执行耗时操作而没有用户界面的应用组件。它默认运行在主线程中,不可以直接进行耗时操作,关于在Service中进行耗时操作详见本文末尾 —— IntentService。
Android四大组件中只有Activity和Service是Context的子类
如果用户在应用管理界面手动停止了Service所在进程,Service就会停止;如果是内存不足导致Android系统杀死了Service所在进程,Service也会停止,只是当内存充足时系统又会重启该Service所在进程(服务进程),Service也会被重新启动。
Android进程优先级
-
前台进程(Foreground process):优先级最高,最重要并且最后一个被Android系统杀死。满足下面任意一条的进程就是前台进程
- 拥有一个正在与用户交互的Activity(onResume()被调用)
- 拥有一个与其他进程中正在与用户交互的Activity绑定的Service
- 拥有一个正在执行onCreate()、onStartCommand()、onDestroy()其中一个生命周期方法的Service(Service在执行生命周期方法时,短暂提高其所在进程的优先级,以保证进程不会被系统杀死)
- 拥有一个正在执行onReceive()的BroadcastReceiver(BroadcastReceiver在收到广播时,短暂提高其所在进程的优先级,以保证进程不会被系统杀死)
-
可见进程(Visible process):满足下面任意一条的进程就是可见进程
- 拥有一个不在前台(失去焦点)但是对用户依然可见的Activity(onPause()被调用)
- 拥有一个与其他进程中可见Activity绑定的Service
- 拥有一个调用了
service.startForeground(id, notification)
运行在前台的Service
服务进程(Service process):拥有一个通过
content.startService(intent)
启动的Service,不到万不得已时不会被Android系统杀死。即使在内存不足时被系统杀死了,等到内存充足时仍然可以被重新启动,继续运行该Service。只有服务进程才可以用来做文件下载、音乐播放等后台操作后台进程(Background process):拥有一个用户不可见的Activity(onStop()被调用),很容易被Android系统杀死,且不会被重新启动
空进程(Empty process):不含有任何活动的应用组件(主要是Activity和Service,BroadcastReceiver的生命周期很短),保留空进程的唯一目的就是作为缓存,以加快下次在此进程中运行组件的启动速度,优先级最低,最容易被Android系统杀死,且不会被重新启动
对于优先级相同的进程,当内存不足时,Android系统会依据LRU算法决定杀死哪一个进程
Service的定义
创建一个类XxxService继承Service,并重写onBind()
public class XxxService extends Service {
/**
* bindService时才会回调,必须实现的方法
*
* @param intent
* @return
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在清单文件中配置该Service
<service android:name=".XxxService"></service>
Service的两种启动方式
从Android 5.0开始,Google要求必须使用显式Intent启动Service
在Android系统中启动Service有如下两种方式:
1. startService
通过content.startService(intent)
启动Service,将触发Service的生命周期方法:onCreate() —> onStartCommand()
因为Service没有前台界面所以Google使用onStartCommand()替代onStart(),它有一个int类型的返回值,如果返回START_STICKY,意味着如果Service所在进程因为系统内存不足而被杀掉,当内存充足时系统还会尝试重新创建这个Service,重新创建Service又会回调onStartCommand(),但这次传入onStartCommand()中的Intent将为null
Intent intent = new Intent(this, XxxService.class);
startService(intent);
重复的startService()不会回调onCreate(),只会回调onStartCommand()
不再使用时,可以通过service.stopSelf()
或 context.stopService(intent)
停止Service,将触发Service的生命周期方法:onDestroy()
Intent intent = new Intent(this, XxxService.class);
stopService(intent);
通过startService()启动的Service与启动它的Activity没有任何关系,即使Activity被销毁了,Service也不会停止。另外,通过这种方式启动Service,该Service所在进程的优先级将不会低于服务进程。
2. bindService
通过content.bindService(intent, conn, flags)
启动Service,需要定义ServiceConnection接口的实现类
public class XxxConnection implements ServiceConnection {
/**
* 当到Service的连接被建立了(Service的onBind()执行成功)
* 并返回了一个非空的IBinder对象,此方法才会回调
*
* @param name
* @param service 这个对象就是onBind()返回的中间人IBinder
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
/**
* 当到Service的连接因为异常而中断,此方法才会回调,正常的解绑不会回调此方法
*
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
然后创建该实现类的对象
XxxConnection conn = new XxxConnection();
通过bindService启动(绑定)Service,将触发Service的生命周期方法:onCreate() —> onBind()
Intent intent = new Intent(this, XxxService.class);
bindService(intent, conn, BIND_AUTO_CREATE); // BIND_AUTO_CREATE表示如果XxxService不存在则自动创建它
重复的bindService不会回调onCreate()和onBind().
通过context.unbindService(conn)
停止(解绑)Service将触法生命周期方法:onUnbind() —> onDestroy()
unbindService(conn);
通常不推荐使用ApplicationContext去bindService,如果通过这种方式bind了一个Local Service,此后每次start Remote Service都会抛出异常:android.os.BinderProxy cannot be cast to XxxService$XxxServiceBinder
如果Service想要同整个应用的生命周期一致,可与MainActivity进行bind
通过bindService()启动Service,也叫绑定(多个Activity可以绑定一个Service),它使Service与启动它的Activity建立连接:如果Activity被销毁了,Service也会被解绑并销毁;但是如果Service被销毁了,Activity则不会被销毁。另外,通过这种方式启动(绑定)Service,该Service所在的进程优先级不变(仍取决于启动服务的Activity)。
startService的应用:后台操作
Android系统通话有三种状态:空闲、响铃、摘机,我们可以在摘机时通过Service进行后台录音来实现通话录音机。
定义一个RecorderService让其在创建时就开始监听电话状态
public class RecorderService extends Service {
@Override
public void onCreate() {
super.onCreate();
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); // 获取电话管理器
tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE); // 监听电话状态
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
自定义电话状态监听器,当响铃时进行音频录制的初始化(申请硬件资源),摘机时开始录制,空闲时回收音频录制所占用的资源
class MyListener extends PhoneStateListener {
/**
* 电话状态改变时回调
*
* @param state
* @param incomingNumber
*/
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) { // 判断当前是什么状态
case TelephonyManager.CALL_STATE_IDLE: // 空闲
if (mediaRecorder != null) {
mediaRecorder.stop(); // 停止录制音频
mediaRecorder.release(); // 释放录音所占用的硬件资源(C代码所占用的)
mediaRecorder = null; // 等待垃圾回收器回收java对象资源
}
break;
case TelephonyManager.CALL_STATE_RINGING: // 响铃,进行音频录制的初始化
if (mediaRecorder == null) {
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);// 设置所录制的音频文件格式为3gp
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置所录制音频的编码格式AMR_NB(3gp文件格式的一种最常见的音频编码格式)
mediaRecorder.setOutputFile("sdcard/voice.3gp");// 设置所录制的音频文件的保存位置
try {
mediaRecorder.prepare();// 准备录制音频
} catch (IOException e) {
e.printStackTrace();
}
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK: // 摘机
if (mediaRecorder == null) {
mediaRecorder.start(); // 开始录制音频
}
break;
}
}
}
注意不要忘了在清单文件中配置RecorderService以及申请所需要的权限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/> <!-- 读取通话状态权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <!-- 录制音频权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- 写SD卡权限 -->
<service android:name=".RecorderService"></service> <!-- 配置RecorderService -->
bindService的应用:调用Service中的方法
在应用中启动Service,系统会自动为我们创建这个Service对象,但是我们无法直接拿到这个Service对象的引用,也就无法在前台调用这个Service中的非静态方法。
想要找人办个证,但不认识领导,需要通过中间人冯秘书牵线!
把Service看成一个领导,服务中有一个banZheng()
,用bindService()
绑定Service时,会触发Service的onBind()
,此方法会返回一个中间人Ibinder对象,前台可以在bindService()
时传入的ServiceConnection实现类对象的onServiceConnected()
中拿到这个Ibinder对象,通过这个对象就可以访问Service中的banZheng()
。
定义一个服务LeaderService,在服务中定义一个FengMiShu继承Binder(实现了Ibinder接口)作为中间人,以在onBind()中返回该类对象。
public class LeaderService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new FengMiShu(); // 返回中间人IBinder对象
}
/**
* 办证
*/
public void banzheng() {
System.out.println("成功办证");
}
/**
* 冯秘书(中间人)
*/
class FengMiShu extends Binder implements PublicBusiness {
/**
* 中间人的牵线
*/
@Override
public void qianXian() {
banzheng(); // 调用领导的办证
}
/**
* 没有抽象到接口中,不能随便调用(捡肥皂不是对公业务)
*/
public void jianFeiZao() {
}
}
}
把qianXian()
抽象到PublicBusiness接口中
public interface PublicBusiness {
void qianXian(); // 把需要被Activity调用的方法抽象到接口中(牵线属于对公业务)
}
在Activity中绑定服务时传入一个ServiceConnection实现类的对象,如果不考虑解绑服务可直接用匿名内部类定义
Intent intent = new Intent(this, LeaderService.class);
bindService(intent, new ServiceConnection() {
/**
* 到Service的连接被建立了(Service的onBind()执行成功),并返回了一个非空的IBinder对象,此方法才会回调
*
* @param name
* @param service 这个对象就是onBind()返回的中间人IBinder
*/
@Override
public void onServiceConnected (ComponentName name, IBinder service){
publicBusiness = (PublicBusiness) service; // 拿到中间人冯秘书,强转成PublicBusiness使其只能调用对公业务
}
/**
* 到Service的连接因为异常而中断,此方法才会回调,正常的解绑不会回调此方法
*
* @param name
*/
@Override
public void onServiceDisconnected (ComponentName name){
}
},BIND_AUTO_CREATE);
最后在Activity需要调用LeaderService的banzheng()
处调用以下代码
publicBusiness.qianXian(); // 冯秘书的对公业务牵线
// publicBusiness.jianFeiZao(); // 非对公业务,调用不了
Service的混合启动:音乐播放器
实现音乐播放时,要保证在Activity销毁后音乐仍在后台播放,进程不会变成空进程而在内存不足时被系统杀死,必须要通过startService()
把进程变成服务进程。可是音乐Service中的方法,需要被前台Activity所调用(例如当用户点击开始或暂停按钮要分别触发Service中的播放音乐和暂停音乐),又必须要用bindService()
绑定服务,获取Binder对象。所以需要用到上述两种方式混合启动音乐Service,并且startService()必须在bindService()之前执行。详见多媒体编程:二、音频播放
混合启动服务时先startService(),再bindService();停止服务时先unbindService(),再stopService()
Local Service 与 Remote Service
对于Web开发,Local Service是指提供服务的程序在本地,而Remote Service是指提供服务的程序在服务器
对于Android开发,Local Service和Remote Service都运行在我们的手机上
Local Service
Local Service是指与启动它的组件都在同一进程的Service,前面启动的Service均是Local Service.
Remote Service
Remote Service是指与启动它的组件不在同一进程的Service,它分为两种:
-
同一应用中的Remote Service:在清单文件中明确指定了Service所在的进程,对于该应用中位于不同进程的其它组件,该Service就是一个Remote Service
<service android:name=".RemoteService" android:process=":newProcess" />
-
不同应用中的Remote Service:在清单文件中配置了intent-filter子节点(此时
android:exported="true"
),并指定action,对于其他应用(一般位于不同进程,特例Android中如何设置两个应用程序为同一个进程?)中的组件,该Service就是一个Remote Service<service android:name=".RemoteService"> <intent-filter> <action android:name="a.b.c" /> </intent-filter> </service>
从Android 5.0以后,开发者只能显式的(指定包名)跨应用启动Remote Service,下面是通过startService的方式跨应用启动Remote Service的代码:
Intent intent = new Intent(); intent.setPackage("edu.neu.steve.remoteservice");// Android 5.0之 后必须指明远程Service所在的应用包名 intent.setAction("a.b.c"); // 与远程Service注册时的action匹配 startService(intent);
AIDL
AIDL(Android Interface Defination Language)即Android接口定义语言,用于Android进程间通信。
下面使用AIDL实现在Client端Bind Server端的Remote Service,从而调用Server端提供的远程服务,这里假设Client和Server不在同一应用中。
Server端
- 在java目录上右键,创建一个AIDL文件PublicBusiness.aidl,并在其中声明中间人的对公业务
qianXian()
,然后Make一下,会自动生成一个PublicBusiness.java(位于app/build/generated/source/aidl/debug下)
interface PublicBusiness {
void qianXian();
}
aidl接口中所有成员都是public的,不需要也不能自己设置访问修饰符
- 在RemoteService内定义中间人FengMiShu直接继承抽象类PublicBusiness.Stub(查看生成的PublicBusiness.java源码可知,这个内部类Stub继承自Binder并实现了PublicBusiness接口),实现对公业务
qianXian()
,然后在onBind()
中返回FengMiShu的实例。RemoteService在清单文件中的配置同上
public class RemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new FengMiShu(); // 返回中间人的实例
}
public void remoteBanzheng() {
System.out.println("领导在国外远程办证");
}
/**
* 中间人冯秘书直接继承Stub并实现qianXian()
*/
class FengMiShu extends PublicBusiness.Stub {
@Override
public void qianXian() {
remoteBanzheng();
}
}
}
Client端
由于Client和Server不在同一应用中,需要把Server端的PublicBusiness.aidl文件复制一份放到Client端,保持该AIDL文件所在包名与Server端的AIDL文件所在包名一致。(如果Client和Server在同一应用中,则跳过该步)
在客户端bind RemoteService,绑定成功后使用
PublicBusiness.Stub.asInterface()
将从onServiceConnected()
中获取到的中间人IBinder对象强转成PublicBusiness类型(不同进程中实际上返回的是一个代理),在需要时调用这个PublicBusiness对象的qianXian()
即可实现远程办证
public class MainActivity extends AppCompatActivity {
public PublicBusiness publicBusiness;
private MyConnection myConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myConn = new MyConnection();
}
class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
publicBusiness = PublicBusiness.Stub.asInterface(service); // 强转成PublicBusiness
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
/**
* 绑定服务
*
* @param v
*/
public void bind(View v) {
Intent intent = new Intent();
intent.setPackage("edu.neu.steve.remoteservice");
intent.setAction("a.b.c");
bindService(intent, myConn, BIND_AUTO_CREATE);
}
/**
* 解绑服务
*
* @param v
*/
public void unbind(View v) {
unbindService(myConn);
}
/**
* 在Activity中办证
*
* @param v
*/
public void banZheng(View v) {
try {
publicBusiness.qianXian(); // 中间人前线,领导在国外远程办证
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
IntentService
在Android开发中,我们一般需要在Service中开启一个子线程来执行类似下载这样的后台耗时操作,因为Activity可能会被用户退出,而BroadcastReceiver的生命周期本身就很短,在Service中开启子线程进行耗时操作可以避免应用进程变为空进程在内存不足时被系统杀死。如果仍然担心Service所在进程被杀,还可以通过调用service.startForeground(id, notification)
提升应用进程的优先级,这是一种比较常见的进程保活方式。
然而自己去管理Service的生命周期以及子线程并非是个优雅的做法,好在Android给我们提供了IntentService,IntentService是Service的子类,用来处理异步请求,在IntentService内有一个worker线程来处理耗时操作。使用IntentService,我们不需要在Service中自己去开启一个子线程,也不需要考虑在什么时候停止Service.
扩展IntentService实现Service无须重写onBind()
、onStartCommand()
,只需要重写onHandleIntent()
public class MyIntentService extends IntentService {
/**
* IntentService的构造函数一定是参数为空的构造函数
* 然后再在其中调用super("name")这种形式的构造函数
* 因为IntentService的实例化是系统用参数为空的构造函数来完成的
*/
public MyIntentService() {
super("MyIntentService");
}
/**
* IntentService会使用单独的线程来执行该方法内的代码
* 该方法内可以执行任何耗时任务,比如下载文件等,此处让线程暂停20秒来模拟耗时操作
*
* @param intent
*/
@Override
protected void onHandleIntent(Intent intent) {
long endTime = System.currentTimeMillis() + 20 * 1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
System.out.println("---耗时任务执行完成---");
}
}
不要忘了在清单文件中配置IntentService,因为它继承于Service,所以它仍是一个Service
<service android:name=".MyIntentService"></service>
客户端可以通过startService(Intent)
方法传递请求给IntentService
Intent intent = new Intent(this, MyIntentService.class);// 创建需要启动的IntentService的Intent
startService(intent); // 启动IntentService
IntentService会将该Intent加入到队列中,然后开启一条新的worker线程来处理该Intent以及onHandleIntent()
中的耗时操作,不会阻塞主线程。
对于异步的startService()请求,IntentService会按次序依次处理队列中的Intent,worker线程保证同一时刻只处理一个Intent。当所有请求处理完成后,IntentService会自动停止。