Skip to content

Android - Service

服务 (Service) 是一种应用程序组件 (application component),它可以在后台执行长时间运行的操作。它不提供用户界面 (UI)。另一个应用程序组件可以启动一个服务,即使用户切换到其他应用程序,它也会继续在后台运行。此外,组件可以绑定 (bind) 到一个服务,以便与其交互,甚至通过进程间通信 (IPC) 进行通信。

服务有三种不同的类型:

  • Foreground Services (前台服务):这些服务执行对用户可见的操作。例如,播放音频的应用。前台服务必须显示一个通知 (Notification)。即使用户不与应用交互,它们也会继续运行。
  • Background Services (后台服务):这些服务执行用户不直接注意到的操作。例如,压缩存储。在现代 Android 版本 (API 26+) 上,对运行后台服务有限制。建议应用使用 WorkManager 进行大多数后台任务。
  • Bound Services (绑定服务):当一个应用程序组件通过调用 bindService() 绑定到服务时,该服务就是绑定服务。绑定服务提供一个客户端-服务器接口,允许组件与服务交互、发送请求、获取结果,甚至通过 IPC 进行跨进程通信。绑定服务只在其绑定的应用程序组件存在时运行。多个组件可以同时绑定到服务,但当所有组件都解绑时,服务将被销毁 (destroyed)。

一个服务,无论被启动、被绑定,还是两者兼有,都有一个生命周期 (lifecycle),您可以实现其回调方法 (callback methods) 来监视其状态变化,并在适当的阶段执行工作。理解这些对于管理资源和确保平稳运行至关重要。

服务的生命周期取决于其创建方式——是被启动服务 (startService()) 还是绑定服务 (bindService())。

  • 对于启动服务:生命周期遵循 onCreate() -> onStartCommand() -> (服务运行) -> onDestroy()。
  • 对于绑定服务:生命周期遵循 onCreate() -> onBind() -> (客户端绑定) -> onUnbind() -> onDestroy()。

需要注意的是,一个服务可以同时被启动和被绑定。例如,音乐播放器服务可以被启动以便无限期播放音乐,同时也可以允许绑定以进行 UI 控制。

要创建服务,您需要扩展 Service 类或其子类之一(如 IntentService,用于在主线程外执行简单的后台任务,尽管对于新的开发,更推荐使用 JobIntentService 或 WorkManager)。主要的回调方法包括:

序号回调方法与描述
1onCreate()
系统只会在服务首次创建时调用此方法(在 onStartCommand() 或 onBind() 之前)。使用此方法进行一次性设置。如果服务已在运行,则不会调用此方法。
2onStartCommand(Intent intent, int flags, int startId)
当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统会调用此方法。一旦此方法执行完毕,服务就已启动,可以在后台无限期运行(尽管在较新的 Android 版本上存在后台执行限制,长任务通常需要前台服务)。工作完成后,您必须通过调用 stopSelf() 或 stopService() 来停止服务。如果您只想提供绑定功能,则无需实现此方法。至关重要的是,在此方法中,必须在新线程 (thread) 中执行长时间运行的任务,以避免阻塞主线程 (main thread)。
3onBind(Intent intent)
当另一个组件通过调用 bindService() 希望与服务绑定时,系统会调用此方法。您必须通过返回一个 IBinder 来提供客户端用于与服务通信的接口 (interface)。您必须始终实现此方法;但是,如果您不想允许绑定,则应返回 null。通信可以通过扩展 Binder 类、使用 Messenger 或使用 AIDL 进行进程间通信 (IPC) 来实现。
4onUnbind(Intent intent)
当所有客户端都已从服务发布的特定接口断开连接时(即所有客户端都已调用 unbindService()),系统会调用此方法。从此方法返回 true 可使 onRebind() 在新客户端稍后绑定时被调用。
5onRebind(Intent intent)
在服务之前已被通知所有客户端已在 onUnbind(Intent) 中断开连接后(并且 onUnbind 返回 true),当新客户端连接到服务时,系统会调用此方法。
6onDestroy()
当服务不再使用并正在被销毁时,系统会调用此方法。这是服务接收到的最后一次调用。实现此方法以清理任何资源,如线程、注册的监听器或接收器。当调用 stopSelf() 或 stopService() 时,或者当所有绑定的客户端解绑时(如果服务没有同时被启动),此方法会被调用。

以下骨架服务 (skeleton service) 展示了生命周期方法。在实际应用中,您会添加特定于您的服务目的的逻辑。

package com.example.myserviceapp;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyExampleService extends Service {
private static final String TAG = "MyExampleService";
// Binder given to clients (for bound services)
private final IBinder binder = new LocalBinder();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
MyExampleService getService() {
// Return this instance of MyExampleService so clients can call public methods
return MyExampleService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Service onCreate");
// Initialization code here (e.g., create threads, setup resources)
// If this service needs to run as a foreground service, call startForeground() here or in onStartCommand().
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Service onStartCommand, Start ID: " + startId + ": " + intent);
// Perform long-running tasks here on a new thread.
// For example, starting a download or playing music.
// new Thread(() -> { /* your long task */ }).start();
// Return value indicates how system should handle service if it's killed:
// START_NOT_STICKY: If the system kills the service after onStartCommand() returns, do not recreate the service, unless there are pending intents to deliver.
// START_STICKY: If the system kills the service after onStartCommand() returns, recreate the service and call onStartCommand(), but do not redeliver the last intent. The intent CULd be null.
// START_REDELIVER_INTENT: If the system kills the service after onStartCommand() returns, recreate the service and call onStartCommand() with the last intent that was delivered to the service.
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Service onBind");
return binder; // Return your IBinder implementation
// return null; // If you don't want to allow binding
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "Service onUnbind");
// Return true to allow onRebind to be called later
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.d(TAG, "Service onRebind");
super.onRebind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service onDestroy");
// Clean up resources here (e.g., stop threads, unregister listeners)
}
// Example public method that clients can call if this is a bound service
public long getCurrentTimestamp() {
return System.currentTimeMillis();
}
}

示例:创建和使用启动服务 (Started Service)

Section titled “示例:创建和使用启动服务 (Started Service)”

此示例演示了如何创建一个简单的启动服务,该服务记录消息,以及 Activity 如何启动和停止它。现代 Android 开发通常使用 Kotlin,但为了与原始教程风格保持一致,此示例将使用 Java,并使用 androidx 库。

步骤描述
1使用 Empty Activity 模板创建一个新的 Android Studio 项目。命名为 MyServiceApp,包名为 com.example.myserviceapp。
2修改 MainActivity.java 以包含用于启动和停止服务的按钮和方法。
3在 com.example.myserviceapp 包下创建一个新的 Java 类 MySimpleStartedService.java,继承自 Service。实现 onStartCommand 和 onDestroy 方法。
4在 AndroidManifest.xml 文件中使用 <service> 标签声明服务。
5更新 res/layout/activity_main.xml 以包含用于启动和停止服务的两个按钮。
6在模拟器或设备上运行应用程序,并在 Logcat 中观察服务发送的消息。

MainActivity.java 内容:

package com.example.myserviceapp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate() event");
Button startButton = findViewById(R.id.buttonStartService);
Button stopButton = findViewById(R.id.buttonStopService);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startMyService();
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopMyService();
}
});
}
public void startMyService() {
Intent serviceIntent = new Intent(this, MySimpleStartedService.class);
// You can pass data to the service via the Intent
serviceIntent.putExtra("inputExtra", "Service Started from MainActivity");
startService(serviceIntent);
Log.d(TAG, "startService() called");
}
public void stopMyService() {
Intent serviceIntent = new Intent(this, MySimpleStartedService.class);
stopService(serviceIntent);
Log.d(TAG, "stopService() called");
}
}

MySimpleStartedService.java 内容:

package com.example.myserviceapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
public class MySimpleStartedService extends Service {
private static final String TAG = "MySimpleStartedService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Service onCreate");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Service onBind - not used for started service");
return null; // This service is not designed to be bound
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Service onStartCommand. Start ID: " + startId);
if (intent != null) {
String input = intent.getStringExtra("inputExtra");
Log.d(TAG, "Received: " + input);
}
// Simulate long-running task in a background thread
// IMPORTANT: Real long-running tasks MUST be done in a separate thread
// to avoid Application Not Responding (ANR) errors.
new Thread(() -> {
for (int i = 0; i < 5; i++) {
Log.d(TAG, "Service is working in background... count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Log.d(TAG, "Background task finished.");
// If the service is no longer needed after this task, stop it.
// stopSelf(startId); // or stopSelf();
}).start();
Toast.makeText(this, "Service Started", Toast.LENGTH_SHORT).show();
// For tasks that should run until explicitly stopped, even if app is killed.
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service onDestroy");
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_SHORT).show();
}
}

AndroidManifest.xml 内容 (位于 <application> 标签内):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myserviceapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyServiceApp">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MySimpleStartedService"
android:enabled="true"
android:exported="false" />
<!--
android:exported="false" means the service can only be started or bound by components of the same application.
Set to "true" if you want other apps to interact with it (requires appropriate permissions and intent filters).
For foreground services, you'll need to add the FOREGROUND_SERVICE permission for Android 9 (API level 28) and higher.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-->
</application>
</manifest>

res/layout/activity_main.xml 内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Service Example"
android:textSize="24sp"
android:layout_marginBottom="32dp"/>
<Button
android:id="@+id/buttonStartService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/buttonStopService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Stop Service" />
</LinearLayout>

运行应用程序时,点击“Start Service”会启动 MySimpleStartedService。您将在 Logcat 中看到来自 MainActivity 和 MySimpleStartedService 的日志。服务将显示一个 Toast 并继续运行其模拟的后台任务。点击“Stop Service”将调用服务的 onDestroy() 方法,服务将被终止,同时也会显示一个 Toast。

现代 Android 开发的重要注意事项

Section titled “现代 Android 开发的重要注意事项”
  • 后台执行限制:从 Android 8.0 (API 级别 26) 开始,系统对应用在后台运行时可以执行的操作施加了限制。对于大多数长时间运行的任务,前台服务 (Foreground Services) 或 WorkManager 是推荐的解决方案。
  • 前台服务 (Foreground Services):如果您的服务执行的操作需要用户主动感知(例如,音乐播放、导航),它必须是一个前台服务。这需要显示一个常驻通知 (persistent notification)。您需要调用 startForeground(int id, Notification notification)。
  • WorkManager:对于可延迟的、有保证的后台工作,即使应用退出或设备重启也应运行,WorkManager 是推荐的解决方案。它处理与不同 Android 版本的兼容性,并尊重电池优化模式 (battery optimization modes)。示例:同步数据、上传日志。
  • 线程处理 (Threading):切勿在服务(或任何其他应用程序组件)的主线程 (main thread) 中执行长时间运行或阻塞的操作。这可能导致应用程序无响应 (ANR - Application Not Responding) 错误。始终将此类任务分载 (offload) 到后台线程(例如,使用 new Thread()、ExecutorService、Kotlin Coroutines 或 RxJava)。IntentService 曾自动处理此问题,但对于更复杂的场景或更新的 API,需要显式的线程管理或 WorkManager。

有关使用 AIDL 进行服务间进程通信 (IPC) 等更高级主题,或 WorkManager 的详细用法,请参阅官方 Android 开发者文档。