前言
Service
作为 Android 的四大组成之一,其重要性是不言而喻的,它主要用于在后台处理那些耗时但又不需要与用户发生交互的工作。例如当我们在 Activity
中开启一个下载任务时,我们可能会将应用置于后台然后去打开别的应用。这时我们的 Activity
是有可能被杀死的,如果我们将下载任务直接放在 Activity
中进行,那么就有可能导致下载中断。在这种情况下我们就最好将我们的下载任务置于 Service
中进行处理。
在介绍过 Service
的使用场景之后,我们接下来了解一下本篇文章我们要解决的问题:
Service
的基本使用方法。Service
的两种启动方式。Service
的生命周期。- 前台
Service
。 - 系统服务
那么接下来我们就开始来学习 Service
的相关知识吧!首先我们要学习的是 Service 的基本使用方法。
Service 的基本使用方法
使用 Service
的第一步,当然就是先创建一个 Service
了,在这里要特别注意既然 Service
是 Android
的四大基本组件之一,那么我们就需要在 Manifest.xml
中注册我们的 Service
,例如我的 Service
名称为 MyService
,那么我就应该在 Manifest.xml
的 Application
标签中添加如下语句:
<application
... 省略无关代码 ...
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
</application>
其中 exported
属性表示是否允许除当前程序之外的其他程序访问这个 Service
,enabled
属性表示是否启用这个 Service
。
接下来我们就来看看在 MyService
这个类中我们应当如何使用:
public class MyService extends Service {
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
可以看到,在这个类中我们重写了 4 个父类方法,分别是 onCreate
、onStartCommand
、onDestroy
和 onBind
。其中前 3 个方法属于 Service
生命周期中的方法,而 onBind
方法适用于绑定 Activity
的,我们会在后面对这个方法进行介绍,首先我们先对三个生命周期的方法打 Log
,然后在 Activity
中开启和关闭 Service
,然后根据打印出来的 Log 判断 Service
开启和关闭过程中调用了那些方法。代码如下所示:
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
......
}
接下来就是 MainActivity
的代码了,我们在这里通过两个按钮来开启和关闭 Service
,开启和关闭的方式是通过创建 Intent
对象,然后分别调用 startService
和 stopService
方法,将 Intent
作为参数传入完成的,代码如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button startServiceBtn;
private Button stopServiceBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startServiceBtn = (Button)findViewById(R.id.start_service);
stopServiceBtn = (Button)findViewById(R.id.stop_service);
startServiceBtn.setOnClickListener(this);
stopServiceBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
break;
}
}
}
运行程序,可以观察到,当我们按下开启 Service
的按钮时,打印出了如下日志:
D/MyService: onCreate
D/MyService: onStartCommand
而如果我们重复按开启 Service
的按钮的话,将会不停地打印 onStartCommand
,如下所示:
D/MyService: onCreate
D/MyService: onStartCommand
D/MyService: onStartCommand
D/MyService: onStartCommand
…
当我们按下关闭 Service
按钮时,打印出如下日志:
D/MyService: onDestroy
所以在这里我们可以得知,在 Activity
中通过 Intent
开启 Service
时,首先会调用 onCreate
方法,紧接着还会调用 onStartCommand
方法。
在关闭 Service
的时候,会调用 onDestroy
方法将 Service
进行销毁。
Service的两种启动方式
在上面的例子中,我们已经介绍了一种启动方式了,即在 Activity
中通过 Intent
来启动 Service
,接下来我们来介绍第二种启动方式:通过 Binder
绑定 Service
。
是否还记得我们的 MyService
中还有一个重写方法:onBind
,在第二种启动方式中,它就会派上用场了。这里你可能会有一个疑问:为什么 Android
会给我们提供两种方式来启动 Service
呢?
答案就是,在我们前面启动 Service
中,我们的 Service
是无法和 Activity
产生通信的,在 Activity
中启动 Service
后,我们只能知道 Service
已经开启了,但是它的逻辑运行到了哪里,根本无从知晓。而 Binder
就提供了一种方式,可以让 Activity
控制 Service
的行为。
我们先来看根据第二种启动方式修改的 MyService
的代码:
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
private DownloadBinder mBinder;
public MyService() {
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
mBinder = new DownloadBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mBinder;
}
public class DownloadBinder extends Binder{
public void startDownload(){
Log.d(TAG, "startDownload");
}
}
}
在这里我们定义了一个内部类 DownloadBinder
,它是 Binder
的子类,我们在这个类中定义了一个方法 startDownload
。然后在 MyService
的开头,我们维护了一个私有成员 mBinder
,它在 onCreate
方法中初始化,然后在 onBind
中直接返回。分析完了 MyService
的代码,我们接下来再来看看 MainActivity
中的代码,我们在我们之前的基础上再添加 2 个按钮,分别用于绑定和解绑 Service
,代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button startServiceBtn;
private Button stopServiceBtn;
private Button bindServiceBtn;
private Button unbindServiceBtn;
private MyService.DownloadBinder mBinder; // DownloadBinder
// 关键点
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (MyService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startServiceBtn = (Button)findViewById(R.id.start_service);
stopServiceBtn = (Button)findViewById(R.id.stop_service);
bindServiceBtn = (Button)findViewById(R.id.bind_service);
unbindServiceBtn = (Button)findViewById(R.id.unbind_service);
startServiceBtn.setOnClickListener(this);
stopServiceBtn.setOnClickListener(this);
bindServiceBtn.setOnClickListener(this);
unbindServiceBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
break;
case R.id.bind_service:
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(connection);
break;
}
}
}
我们在 MainActivity
中维护了一个 DownloadBinder
类型的成员 mBinder
,然后还创建了一个匿名内部类 ServiceConnection
,重写了它的两个方法 onServiceConnected
和 onServiceDisconnected
,在 onServiceConnected
方法中,我们就可以获得 Service
中的 DownloadBinder
实例,然后赋值给 mBinder
。这样,我们就能够通过 mBinder
获取到它的 startDownload
方法了,然后我们就可以在 Activity
中通过该方法的调用控制 Service
的行为。
接下来我们看到 onClick
方法中是如何绑定和解绑 Service
的,首先依然是要创建 Intent 对象,然后调用 bindService
方法绑定 Service
,bindService
有 3 个参数,第一个是 Intent
,第 2 个是 ServiceConnection
,第 3 个则是一个标志位,这里我们传入 BIND_AUTO_CREATE
表示绑定之后自动创建 Service
。
而解除绑定同样也需要 Intent
,然后调用 unbindService
方法,传入我们的 ServiceConnection
对象即可。
接下来我们同样运行一下这段代码,看看打印出来的 Log
是什么样子的。首先按下绑定服务的按钮,打印出来的是如下信息:
D/MyService: onCreate
D/MyService: onBind
可以看到打印出了如上信息,调用的是 onCreate 方法和 onBind 方法,而我们无论再怎么按绑定按钮,这些信息都不会再打印了。接下来我们按下解绑按钮,打印出如下信息:
D/MyService: onDestroy
解绑后它就调用了 onDestroy
方法销毁了 Service
。
而在这里,我们还有一点需要注意的是,如果我们既按下了开启 Service
按钮来开启服务(即第 1 中启动方式),又按下绑定按钮对 Service
进行了绑定,那么我们在单独按下关闭 Service
的按钮或者解除绑定的按钮时,我们会发现 Log
中并没有打印出 onDestroy
的信息,也就是说我们的 Service
并没有执行 onDestroy
方法销毁 Service
。
在这种情况下,我们既要关闭服务,又要解除绑定(也就是两个销毁的按钮都按下),最终才会调用 onDestroy
方法销毁我们的 Service
。
讲解到这里,我们来分别对两种方式进行一个总结:
- 第一种启动方式是通过
startService
方法进行启动,在我们的 Activity 中调用该方法就会使我们的服务进入运行状态,此时会调用onCreate
和onStartCommand
方法,onCreate
方法只会调用一次,而在重复调用startService
方法时,onStartCommand
方法会重复执行。 - 与
startService
相对应的方法是stopService
,它用于关闭服务,它会调用onDestroy
方法销毁Service
。 - 第二种启动方式是通过
bindService
方法进行启动,即绑定我们的Service
,它可用于Activity
操控Service
的行为,在Activity
中我们通过ServiceConnection
的onServiceConnected
方法获得Binder
的实例,然后通过该实例调用相对应的方法来操纵Service
的行为。调用bindService
会调用onCreate
和onBind
方法,这两个方法均只会执行一次,即重复调用bindService
不会重复调用这两个方法。 - 与
bindService
方法相对应的是unbindService
方法,它用于解除绑定,它会调用onDestroy
方法销毁Service
。 - 如果即调用了
startService
,又调用了bindService
,那么单独调用stopService
或unbindService
并不会销毁Service
,需要这两个方法同时调用才能销毁Service
。
在讲解完 Service
的两种启动方式之后,接下来我们来了解一下 Service
的生命周期。
Service 的生命周期
官方文档提供的 Service
生命周期图如下所示:
可以很清晰的看到,在通过 startService
方法启动 Service
的方式中,生命周期是 onCreate - onStartCommand - onDestroy
,而通过 bindService
方法启动 Service
时,生命周期为 onCreate - onBind - onUnbind - onDestroy
。接下来我们来了解一下这么方法的具体含义:
- onCreate:第一次启动
Service
由系统调用。 - onStartCommand:通过
startService
启动服务时由系统调用,它的调用位于onCreate
之后。 - onBind:绑定
Service
时调用,会返回一个Binder
实例。 - onUnbind:解绑
Service
时调用。 - onDestroy:停止
Service
时调用,用于销毁Service
。
在了解完 Service
的生命周期之后,我们接下来讲解下如何创建前台 Service
。
前台 Service
在我们之前的例子中,我们的 Service
都是在后台运行的。但是 Service
的系统优先级并不高,在内存不足的时候,它还是有被回收掉的风险,所以我们可以通过一个前台 Service
来提高它的系统优先级,这样子它就可以一直保持运行状态。
要实现前台 Service
,其实非常简单,只要在 onCreate
中添加一些代码即可,onCreate
的代码如下所示:
@Override
public void onCreate() {
......
Notification notification;
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
NotificationChannel channel = new NotificationChannel("service",
"fore_service", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
notification = new NotificationCompat
.Builder(this, "service")
.setContentTitle("Foreground Service")
.setContentText("This is Foreground Service's test")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pi)
.build();
} else {
notification = new NotificationCompat
.Builder(this)
.setContentTitle("Foreground Service")
.setContentText("This is Foreground Service's test")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pi)
.build();
}
startForeground(1, notification); // 启动前台服务
}
在这段代码中,其实主要的逻辑就是创建 Notification
对象,然后通过 startForeground
方法进行调用产生前台服务。而我们创建 PendingIntent
对象是用于点击前台服务后可以返回我们的 MainActivity
。
这里还有一个需要注意的点,我们在这里对于不同版本进行了适配,因为从 Android 8.0 开始对于 Notification
的创建做了很大的改动,所以有必要对不同版本进行适配,Notification
的适配相关问题可以查看这篇文章:https://blog.csdn.net/guolin_blog/article/details/79854070 ,讲的非常详细,由于与本主题无关,故不做介绍。
我们来看看它实现的效果:
Service 的其他注意事项
我们前面一起提到,在 Service
中我们常用于处理一些耗时的任务,耗时的任务自然是放在子线程里面去做的,那么我们的 Service 是运行在哪个线程中的呢?
事实上 Service
默认是运行在主线程中的,它并不会为我们开启一个子线程,所以在 Service
中我们的耗时操作需要自己开启子线程来进行处理,否则可能会出现 ANR
问题。当然还有其他的解决方案如使用 IntentService
,在 IntentService
是默认为我们开启了子线程的。
另外,停止 Service
除了在 Activity
中调用 stopService
方法外,我们还可以在 Service
内部合适的地方调用 stopSelf
方法,同样也可以实现停止 Service
的效果,这里我们在 onStartCommand
方法中开启子线程写入相应逻辑,代码如下所示:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 具体逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
系统服务
除了自定义 Service
之外,我们还可以使用现成的系统 Service
,例如我们在上面前台 Service
的例子中创建 NotificationManager
对象就是使用的系统 Service
,它的使用方式也相当简单,就是通过 getSystemService
方法,传入一个标志位,然后将返回值进行类型强转就可以了,接下来我们通过一个例子,来充分感受下调用系统服务的用法。
例子:通过调用系统的 AlarmManager
,在后台执行一个模拟的定时任务,定时时间为 5s。
我们接下来看看 AlarmManager
的一些参数说明:
工作类型:
参数 | 含义 |
---|---|
ELAPSED_REALTIME | 让定时任务的触发时间从系统开机算起,但不会唤醒 CPU |
ELAPSED_REALTIME_WAKEUP | 让定时任务的触发时间从系统开机算起,并且会唤醒 CPU |
RTC | 让定时任务的触发时间从1970年1月0日0时开始算起,但不会唤醒 CPU |
RTC_WAKEUP | 让定时任务的触发时间从1970年1月0日0时开始算起,并且会唤醒 CPU |
定时任务触发的时间计算:
如果工作类型为 ELAPSED_REALTIME
或 ELAPSED_REALTIME_WAKEUP
,那么定时时间应该为: SystemClock.elapsedRealtime() + delayTimes,单位为 ms。
如果工作类型为 RTC
或 RTC_WAKEUP
,那么定时时间应该为: System.currentTimeMillis() + delayTimes,单位为 ms。
在了解这些参数以及如何计算定时时间之后,我们定时 5s 就可以写成如下代码:
long time = SystemClock.elapsedRealtime() + 5000; // 延时 5s
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);
可以看到我们调用的是 AlarmManager
的 setExtra
方法来执行定时任务,它接收 3 个参数,第 1 个是一个标志位,代表工作类型,第 2 个就是传入我们的定时时间,而第 3 个参数是一个 PendingIntent
对象,在这里我们通过定时开启一个广播,所以 PendingIntent
的代码如下:
Intent receiverIntent = new Intent(this, MyReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, receiverIntent, 0);
因为是要开启一个广播,所以这里我们要通过 getBroadcast
方法获取到广播,这里我们的广播命名为 MyReceiver
,它的代码如下所示:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, MyService.class);
context.startService(i);
}
}
不要忘了在 Manifest.xml
中对它进行注册,我们一旦开启了广播,就会调用 startService
方法开启 Service
,如此 Service
就会再次执行定时任务,再次打开广播,然后广播又会打开 Service
…如此循环反复,我们就成功建立起了一个定时任务。
我们在前面提到过,重复调用 startService
会重复调用 Service
中的 onStartCommand
方法,所以我们的定时任务逻辑应该放在 onStartCommand
中执行,所以完整的 MyService
代码如下所示:
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
Intent receiverIntent = new Intent(this, MyReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, receiverIntent, 0);
long time = SystemClock.elapsedRealtime() + 5000; // 延时 5s
AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在这里我们依然通过打印 Log
来进行验证,运行该程序,结果如下所示:
2019-02-23 10:41:56.013 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:01.048 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:06.085 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:11.120 3226-3226/com.marck.servicetest D/MyService: onStartCommand
2019-02-23 10:42:16.160 3226-3226/com.marck.servicetest D/MyService: onStartCommand
在通过 Activity
开启 Service
后,可以看到确实是每过 5s,我们就会执行一次 onStartCommand
方法。
空格空格空格
到这里,Service
的内容基本上就讲解完了,Service
是 Android
的基础和重要的组成部分,希望大家能够通过本篇文章有所收获,有问题可以在评论区下方给我留言!