1. Service 是什么?
服务(Service) 是 Android 的四大组件之一,是 Android 中实现程序后台运行的解决方案,适合去执行那些不需要和用户交互而且还要求长期运行的任务。
Service 的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service 仍然能够保持正常运行。
Service 并不是运行在一个独立的进程当中的 ,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
Service 并不会自动开启线程,所有代码都是默认运行在主线程中 。我们需要在服务内部手动创建子线程,并在这里执行具体的任务。
2. Service 的基本用法
2.1 定义一个 Service
如下图,如同创建 Activity 一样创建一个 Service:
有两个属性:
- Exported:表示是否将这个 Service 暴露给外部其他程序访问。
- Enabled:表示是否启用这个 Service。
创建之后的 MyService 继承 Service:
java
// Android API 34
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
onBind()
是 Service 类中唯一的抽象方法:
java
// Service.java
@Nullable
public abstract IBinder onBind(Intent intent);
定义一个 Service 不仅实现 onBind()
方法,一般还会实现 onCreate()
、onStartCommend()
和 onDestroy()
:
java
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) {
// TODO: Return the communication channel to the service.
return mBinder;
}
}
onCreate()
:服务第一次创建时调用。onStartCommend()
:每次启动服务时调用。服务一旦启动就立即去执行某个操作的代码逻辑现在这里。onDestroy()
:服务销毁时调用。回收不再使用的资源。
Android 四大组件在 AndroidManifest.xml 中进行注册才能生效:
java
// AndroidManifest.xml
<service
android:name=".MyService"
android:enabled="true"
android:exported="true" />
2.2 startService() 显式开启服务
主要通过 Intent 去完成服务的启动和停止。
开启 Service 的两种方式:显示开启 和绑定开启
在 Activity 中可以直接调用 startService()
和 stopService()
这两个方法显示开启和停止服务:
java
if (id == R.id.start_service) {
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);// 通过 startService() 方法启动 MyService 服务
} else if (id == R.id.stop_service) {
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);// 由 Activity 来决定服务何时停止。若想服务自己停止,在 MyService 调用stopSelf()方法
}
点击开启服务按钮开启服务之后,在手机设置中正在运行的服务中会看见我们开启的服务,也可以通过 log 看看服务是否开启,停止服务同理:
这里,完全是 Activity 决定服务何时停止的,如果没有点击按钮,Service 会一直处于运行状态。
如果想让 Service 自己停下来,只需要在 MyService 任何一个位置调用 stopSelf()
。
显示开启服务流程:
2.3 绑定开启
通过 startService()
方法显示开启 Service 后,调用者(Activity)就和 service 没有关联了。
这里一个很重要的问题:Activity 无法拿到 Service引用。
举个例子,如何在 MyService 中提供一个下载功能,并且在 Activity 中可以决定何时开始下载,以及查看下载进度呢?
通过 bindService()
方法绑定开 启服务,可以借助 onBind()
方法,创建一个专门的 Binder 对象来对下载功能进行管理。
新建 DownloadBinder 继承自 Binder,并在其内部提供开始下载和查看进度方法:
java
public class MyService extends Service {
private DownloadBinder mDownloadBinder = new DownloadBinder();
public class DownloadBinder extends Binder{
public void startDownload(){
Log.d("MyService", "Start");
}
public int getProgress(){
Log.d("MyService","getProgress");
return 0;
}
public void stopDownload(){
Log.d("MyService", "Stop");
}
}
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) {
// TODO: Return the communication channel to the service.
return mDownloadBinder;
}
}
通过 startService()
开启 Service 时,我们重写了 onBind()
,直接返回的是null,此时该方法并没有调用。
第31~34行,当通过 bindService()
开启 Service 时,需要返回 IBiner 的引用给绑定者使用。
返回的是 DownloadBinder 对象的引用,该对象持有了 MyService 引用。绑定者又是在哪里接收 IBinder 的引用呢?
在 Activity 中定义的 ServiceConnection 匿名内部类:
创建一个 ServiceConnection 的匿名类,重写了 onServiceConnected()
和onServiceDisconnected()
,这两个方法分别在 Activity 和 Service 成功绑定以及连接断开时时调用:
java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.start_service) {
...
} else if (id == R.id.stop_service) {
...
} else if (id == R.id.bind_service) {
Intent bindIntent = new Intent(this, MyService.class);
// service 和 activity 绑定后自动创建服务 BIND_AUTO_CREATE,这会使得 MyService 中 OnCreate() 方法得到执行
bindService(bindIntent,connection,BIND_AUTO_CREATE);
} else if (id == R.id.unbind_service) {
unbindService(connection);
}
}
private ServiceConnection connection = new ServiceConnection() {
// service 和 activity 绑定时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service 就是从 onBind() 方法返回的
// 向下转型得到 DownloadBinder 的实例
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Service 被销毁时调用,内存不足等。
}
};
}
有了 ServiceConnection 引用,接着就需要和 Service 建立联系,建立联系的过程即是绑定开启 Service 的过程:
java
private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
从上面可以看出,绑定开启的流程:
- 新建 DownloadBinder 类持有 Service 对象。
- 在 Service 的
onBind()
方法中返回 IBinder 引用(mDownloadBinder),该引用持有 Service 引用。 - Activity 调用
bindService()
方法成功后,会调用 ServiceConnection 的onServiceConnected()
方法,Service 和 ServiceConnection 也会建立联系,onServiceConnected()
方法会返回 IBinder 引用(mDownloadBinder)。 - Activity 通过 IBinder 引用(mDownloadBinder)拿到 Service 引用,进而操作 Service。
以上回答了上面的问题:绑定者(Activity)如何拿到 Service 引用。
那么如何进行解绑呢?
手动调用 unbindService(serviceConnection)
方法进行解绑,解绑时也需要传入 ServiceConnection 引用,不然无法确定解绑哪个 Service。
手动调用该方法即可解绑 Service。 当 Activity 通过 bindService()
绑定开启 Service 后,若是 Activity 销毁了,那么相应的 Service 也会被销毁掉。
值得注意的是:
- 若是
startService()
开启 Service,则无法用unbindService()
方法关闭 Service。 - 若是
bindService()
开启 Service,则无法用stopService()
关闭 Service。 - 既调用了
startService()
方法,又调用了bindService()
方法的 Service ,这种情况下要同时调用stopService()
和unbindService()
方法,onDestroy()
方法才会执行。
绑定开启服务的流程:
3. 前台服务
系统内存不足时可能会回收服务。若想要服务一直保持运行状态,可以考虑使用前台服务,避免服务被回收。
前台服务会一直有一个正在运行的图标在系统的状态栏显示,类似于通知的效果。相当于 Service 以这种形式在前台运行。
Android 9.0开始,需进行权限声明:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
java
// MyService.java
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
NotificationChannel notificationChannel = new NotificationChannel("my_service","前台Service通知 ", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);
}
Intent intent = new Intent(this, MyServiceActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(this,"my_service")
.setContentTitle("This is content title")
.setContentText("真正的平静,不是避开车马喧嚣,而是在心中修篱种菊。尽管如流往事,每一天都涛声依旧,只要我们消除执念,便可寂静安然。愿每个人,在纷呈世相中不会迷失荒径,可以端坐磐石上,醉倒落花前。")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.a)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.a))
.setContentIntent(pi)
.build();
startForeground(3,notification);
}
第5行,NotificationManager 类用于向用户展示通知,对通知进行管理。
第7行,NotificationChannel 类用于构建一个通知渠道,并使用 NotificationManager 的 createNotificationChannel()
方法进行创建。
通知渠道(Notification Channels),也被称为通知类别,是Android 8.0(API 级别 26)引入的一个功能,它为开发者提供了更好的管理通知的方式,也使用户能够有更好地控制他们想要接收的通知类型。
NotificationChannel 类和 createNotificationChannel() 方法都是 Android 8.0 系统中新增的 API,因此我们在使用的时候进行版本判断。
创建一个通知渠道至少需要渠道ID 、渠道名称 以及重要等级这三个参数:
- 渠道ID:可以随便定义,只要保证全局唯一性就可以。
- 渠道名称:给用户看的,需清楚地表达这个渠道的用途。
- 重要等级 :主要有
IMPORTANCE_HIGH
、IMPORTANCE_DEFAULT
、IMPORTANCE_LOW
、IMPORTANCE_MIN
。用户可以随时手动更改某个通知渠道的重要等级。
第12行,使用 NotificationCompat Builder
对 Notification 进行构造。由于通知 API 的不稳定性,使用 NotificationCompat 类对通知相关操作进行兼容实现。
NotificationCompat.Builder(this,"my_service")
第二个参数为渠道ID,需要与创建时的 ID 保持一致。
第11行,PendingIntent 可以在某个合适的时机执行某个动作,可以理解为延迟执行的Intent。通过第18行设置,在点击状态栏中的通知时会进行跳转。
运行程序,点击 START SERVICE 按钮,就会在状态栏中看到相关通知:
若没有出现,可能通知权限还未开启: