Service 是什么?
Service 是 Android 四大组件之一,专门用于在后台执行长时间运行的操作,不需要用户界面。它是你的 App 的"幕后工作者"。
一、Service 的核心用途
- 执行耗时操作: 网络下载、大文件处理、复杂计算(避免在主线程做这些导致 ANR)。
- 播放媒体: 音乐播放器、电台 App 的核心,音乐在后台播放时 Activity 可以关闭。
- 文件上传/下载: 即使 App 退到后台,上传下载任务也能继续。
- 与远程进程通信: 通过 AIDL 实现跨 App 的功能调用(如天气服务提供数据给多个 App)。
- 定时任务: (结合 AlarmManager 或 WorkManager) 在特定时间或间隔执行操作。
- 后台位置更新: (需谨慎处理权限和电量) 持续获取位置信息。
二、如何使用 Service?两种启动模式
Service 的生命周期和行为取决于你如何启动它:
-
Started Service (启动服务)
-
作用: 执行一个单次操作 ,操作完成后服务应该自己停止。例如:下载一个文件、上传一张图片、播放一首歌(虽然播放器通常用绑定模式)。
-
如何启动: 在 Activity 或其他组件中调用
startService(Intent)
。 -
特点:
- 启动它的组件(如 Activity)销毁了,服务依然可以独立运行在后台。
- 服务执行的任务不直接返回结果给启动者。通常通过广播 (Broadcast)、通知 (Notification)、更新文件/数据库等方式告知结果。
- 服务必须自己调用
stopSelf()
或在其他组件调用stopService(Intent)
来停止,否则会一直运行(耗电!)。
-
生命周期方法调用顺序:
onCreate()
->onStartCommand(Intent, int, int)
-> (服务运行...) ->stopSelf()
/stopService()
->onDestroy()
onStartCommand()
是核心,每次调用startService()
都会触发它(即使服务已在运行),Intent 携带启动参数。
示例代码 (启动一个下载服务):
scala// 在 Activity 中 Intent downloadIntent = new Intent(this, DownloadService.class); downloadIntent.putExtra("url", "https://example.com/bigfile.zip"); startService(downloadIntent); // 启动服务 // 在 DownloadService 中 public class DownloadService extends Service { @Override public void onCreate() { super.onCreate(); // 初始化工作,如创建通知、线程池等 (只调用一次) } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 每次 startService() 都会调用这里 String url = intent.getStringExtra("url"); // 启动线程或 AsyncTask 执行下载 (记住:不能在主线程做耗时操作!) new DownloadTask().execute(url); // 告诉系统如果服务被意外杀死后如何处理 return START_NOT_STICKY; // 常用:不重新创建服务 } private class DownloadTask extends AsyncTask<String, Void, Void> { @Override protected Void doInBackground(String... urls) { // 执行实际的下载逻辑... // 下载完成或失败后,记得调用 stopSelf() 或发送广播/通知 stopSelf(); // 任务完成,停止服务 return null; } } @Override public void onDestroy() { super.onDestroy(); // 清理资源,如关闭线程、移除通知 } @Nullable @Override public IBinder onBind(Intent intent) { return null; // 纯启动服务不需要实现绑定 } }
-
-
Bound Service (绑定服务)
-
作用: 提供客户端-服务器 接口,让其他组件(如 Activity)绑定 到服务上,进行交互 (调用方法、获取数据、监听状态)。例如:音乐播放器服务提供
play()
,pause()
,getPosition()
等方法给 Activity 调用;股票 App 服务提供实时股价数据流。 -
如何绑定: 在 Activity 或其他组件中调用
bindService(Intent, ServiceConnection, int)
。 -
特点:
- 多个组件可以同时绑定到同一个服务。
- 组件与服务建立了一条通信通道(通常是 Binder 对象),组件可以直接调用服务暴露的方法。
- 当所有绑定者都解绑 (
unbindService()
) 后,系统通常会销毁 该服务(除非它同时被startService()
启动了)。 - 绑定服务的组件(如 Activity)销毁时,会自动解绑。
-
生命周期方法调用顺序:
onCreate()
->onBind(Intent)
-> (服务运行,客户端通过返回的 IBinder 通信) ->onUnbind(Intent)
->onDestroy()
onBind()
是关键,它返回一个IBinder
对象,作为客户端与服务通信的桥梁。
实现绑定服务的三种方式:
-
-
1. 扩展 Binder 类 (最简单,同一进程内):
- 在服务内部创建一个继承
Binder
的类,提供公共方法让客户端调用。 onBind()
返回这个 Binder 实例。- 客户端在
ServiceConnection
的onServiceConnected()
中获取这个 Binder 并强转成服务内部类类型,即可调用方法。
scala// LocalService.java (服务端) public class LocalService extends Service { private final IBinder binder = new LocalBinder(); // 自定义Binder public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; // 返回服务实例本身 } } @Override public IBinder onBind(Intent intent) { return binder; } // 公共方法供客户端调用 public int getRandomNumber() { return new Random().nextInt(100); } } // MainActivity.java (客户端) public class MainActivity extends Activity { LocalService mService; boolean mBound = false; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获取Binder并转换 LocalService.LocalBinder binder = (LocalService.LocalBinder) service; mService = binder.getService(); // 拿到服务实例 mBound = true; // 现在可以调用 mService.getRandomNumber() 了! } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... Intent intent = new Intent(this, LocalService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); // 绑定服务 } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(connection); // 解绑服务 mBound = false; } } }
- 在服务内部创建一个继承
-
2. 使用 Messenger (跨进程较简单):
- 服务定义一个
Handler
来处理客户端通过Message
对象发送的请求。 onBind()
返回一个Messenger
(基于Handler
创建)。- 客户端拿到
Messenger
后,可以发送Message
对象给服务端。服务端也可以回复消息。 - 比 AIDL 简单,但通信是单向或请求-响应式的,不如 AIDL 灵活。
- 服务定义一个
-
服务端实现
scala
// MessengerService.java
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
public static final int MSG_SAY_HELLO = 1;
public static final int MSG_REPLY = 2;
// 服务端的 Handler,处理客户端发来的消息
private static class ServiceHandler extends Handler {
private final WeakReference<Context> mContextRef;
private Messenger mClientMessenger; // 客户端的 Messenger(用于双向通信)
ServiceHandler(Context context) {
mContextRef = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.d(TAG, "收到客户端消息: " + msg.getData().getString("data"));
// 如果是双向通信,可以从 msg.replyTo 获取客户端的 Messenger
mClientMessenger = msg.replyTo;
if (mClientMessenger != null) {
try {
// 向客户端回复消息
Message replyMsg = Message.obtain(null, MSG_REPLY);
Bundle bundle = new Bundle();
bundle.putString("reply", "你好客户端,我是服务端");
replyMsg.setData(bundle);
mClientMessenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
super.handleMessage(msg);
}
}
}
// 服务端的 Messenger(将 Handler 绑定到 Messenger)
private final Messenger mMessenger = new Messenger(new ServiceHandler(this));
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mMessenger.getBinder(); // 返回 Messenger 的 Binder 对象
}
}
- 客户端实现
java
// MainActivity.java
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private Messenger mServiceMessenger; // 服务端的 Messenger
private boolean mBound; // 是否已绑定服务
// 客户端的 Handler,用于接收服务端回复(双向通信时才需要)
private static class ClientHandler extends Handler {
private final WeakReference<MainActivity> mActivityRef;
ClientHandler(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivityRef.get();
if (activity != null && msg.what == MessengerService.MSG_REPLY) {
String reply = msg.getData().getString("reply");
Log.d(TAG, "收到服务端回复: " + reply);
Toast.makeText(activity, reply, Toast.LENGTH_SHORT).show();
}
}
}
// 客户端的 Messenger(用于双向通信)
private final Messenger mClientMessenger = new Messenger(new ClientHandler(this));
// ServiceConnection 用于绑定/解绑服务
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "服务已连接");
// 通过服务端返回的 IBinder 创建 Messenger
mServiceMessenger = new Messenger(service);
mBound = true;
// 向服务端发送消息(单向通信示例)
sendMessageToService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "服务已断开");
mServiceMessenger = null;
mBound = false;
}
};
private void sendMessageToService() {
if (!mBound) return;
try {
// 创建消息
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO);
Bundle data = new Bundle();
data.putString("data", "你好服务端,我是客户端");
msg.setData(data);
// 如果是双向通信,设置 replyTo
msg.replyTo = mClientMessenger;
// 发送消息
mServiceMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_bind).setOnClickListener(v -> bindService());
findViewById(R.id.btn_unbind).setOnClickListener(v -> unbindService());
}
private void bindService() {
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
private void unbindService() {
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService();
}
}
-
3. 使用 AIDL (Android Interface Definition Language - 跨进程复杂交互):
markdown- 定义 AIDL 接口文件 (.aidl),描述服务公开的方法。 - 编译时 Android SDK 工具会生成对应的 Java 接口和 Stub 类。 - 服务继承 `Stub` 类并实现 AIDL 接口定义的方法,在 `onBind()` 中返回这个 `Stub` 实例。 - 客户端绑定后,将 `onServiceConnected()` 返回的 `IBinder` 转换成 AIDL 接口类型,即可调用远程方法。 - 这是实现**跨进程通信 (IPC)** 最强大但也最复杂的方式。系统服务(如 `LocationManagerService`)大多通过 AIDL 暴露接口。
三、关键概念与注意事项
-
生命周期: 深刻理解
Started
和Bound
两种模式下的生命周期回调 (onCreate
,onStartCommand
,onBind
,onUnbind
,onDestroy
) 及其触发条件。服务不会自动停止! 启动服务需手动停,绑定服务需所有客户端解绑。 -
主线程警告: Service 的所有生命周期方法默认都在主线程 (UI 线程) 执行 !绝对不能在
onStartCommand()
或onBind()
里直接执行耗时操作 ,否则会导致 ANR (Application Not Responding)。必须使用工作线程 (Thread, HandlerThread, ExecutorService, AsyncTask - 已废弃, 推荐 Kotlin 协程/RxJava) 或IntentService
(见下文)。 -
前台服务 (Foreground Service):
- 为什么需要? Android 8.0 (Oreo) 后,普通后台服务受到严格限制,容易被系统杀死。需要长时间运行且用户可感知的服务(如音乐播放、导航、文件下载)应提升为前台服务。
- 怎么做? 在服务中调用
startForeground(int id, Notification notification)
。必须提供一个持续显示的通知 (Notification),告知用户服务正在运行。这会给服务更高的优先级,减少被杀死的概率。 - 完成后调用
stopForeground(boolean removeNotification)
可降回后台(服务本身不会停止)。
-
IntentService:
- 系统提供的
Service
子类,专门用于处理异步的、一次性的启动请求。 - 优点: 内部自带工作线程,请求会排队处理 (
onHandleIntent(Intent)
在工作线程执行),所有请求处理完后自动调用stopSelf()
。 - 缺点: 不能绑定,不能直接处理并发任务(排队串行执行),Android 8.0 后同样受后台限制影响,适合简单任务。Android 11 标记为废弃 ,官方推荐使用
JobIntentService
(也废弃) 或更现代的WorkManager
。
- 系统提供的
-
Android 8.0+ 后台限制:
-
背景:为了优化电池续航和系统性能。
-
核心规则: 当 App 进入后台 (用户不再与其交互,如按 Home 键或切换到其他 App)后,它有几分钟的时间窗口 可以创建和运行服务。时间窗口结束后,系统会停止所有后台服务。
-
应对策略:
- 前台服务: 需要长时间运行且用户可感知的任务。
- JobScheduler / WorkManager: 安排稍后执行或满足条件(如充电、有网络)时执行的后台任务。这是 Google 强烈推荐的现代后台任务处理方式。WorkManager 兼容旧版本,能处理网络、存储空间、电量等约束。
- Foreground Service + 通知: 最常用手段。
- 豁免情况: 处理高优先级 FCM 消息、活动识别等特定场景。
-
-
服务保活误区:
- 不要尝试用
startForeground()
但隐藏通知(会被系统检测并杀死)。 - 不要循环调用
startService()
或AlarmManager
频繁唤醒(浪费电,用户体验差,新系统会限制)。 - 正确思路: 接受服务会被杀死的现实。使用前台服务让用户知情;使用 WorkManager 保证任务最终会被执行;服务重启时 (
onStartCommand
的flags
) 考虑恢复状态;重要数据及时持久化(数据库/文件)。
- 不要尝试用
四、总结
-
Service 是 Android 的幕后工作者,用于后台执行长时间任务。
-
两种启动模式:
Started Service
(startService
): 执行单次任务,启动者与服务无直接通信,需手动停止。Bound Service
(bindService
): 提供交互接口,客户端与服务直接通信,所有客户端解绑后服务通常停止。实现方式有Binder
(同进程)、Messenger
(跨进程简单)、AIDL
(跨进程强大)。
-
关键要点:
- 服务生命周期方法在主线程运行,耗时操作必须开工作线程。
- Android 8.0+ 后台服务受限 ,需要长时间运行的服务务必使用前台服务并显示通知。
IntentService
是简化启动服务的工具(已废弃,了解即可)。- 现代后台任务首选
WorkManager
。 - 避免无效的服务保活,设计上要接受服务可能被杀死。