Service安卓是什么?

99ANYc3cd6
预计阅读时长 44 分钟
位置: 首页 安卓 正文

目录

  1. 什么是 Service?
  2. Service 的两种主要类型
  3. Service 的生命周期
  4. 如何创建和使用一个 Service?
  5. Service 的启动与绑定详解
  6. 前台服务
  7. 后台服务限制
  8. 最佳实践与注意事项

什么是 Service?

在 Android 中,Service(服务) 是一个在后台长时间运行、没有用户界面的组件,它不执行 UI 操作,非常适合用于执行那些不需要用户交互且需要长时间运行的任务。

service 安卓
(图片来源网络,侵删)

核心特点:

  • 无 UI 界面:它不是一个 Activity,你无法直接在 Service 中放置按钮或文本框。
  • 后台运行:即使用户离开了你的应用,Service 默认仍然可以在后台继续运行。
  • 依赖应用进程:默认情况下,Service 运行在它所属应用的主线程中,这一点非常重要,也是很多初学者容易犯错的地方。

典型应用场景:

  • 音乐播放器:在后台播放音乐,即使用户切换到其他应用。
  • 下载文件:在后台下载大文件,并在完成后通知用户。
  • 网络请求:执行轮询或与服务器保持长连接。
  • 定位服务:在后台持续获取用户位置信息。
  • 数据同步:定期将设备数据同步到云端。

Service 的两种主要类型

Service 主要有两种使用方式,也可以结合使用:

A. 启动服务

  • 用途:执行一个一次性的任务,下载一个文件、播放一首歌。
  • 工作方式:其他组件(如 Activity)通过 startService() 方法启动 Service,一旦启动,Service 就会独立于启动它的组件而运行,即使启动它的组件被销毁了,Service 也会继续运行,直到它自己调用 stopSelf() 或其他组件调用 stopService()
  • 通信:启动的 Service 和启动者之间没有直接的绑定关系,如果需要通信,通常通过 BroadcastReceiver(广播)Binder(需要额外绑定)来实现。

B. 绑定服务

  • 用途:提供一个客户端-服务器模式的接口,允许其他组件(如 Activity)与 Service 进行交互,比如调用 Service 中的方法、获取数据。
  • 工作方式:其他组件通过 bindService() 方法绑定到 Service,绑定后,组件和 Service 就建立了一个连接,当所有绑定者都解绑(调用 unbindService())后,Service 也不是通过 startService() 启动的,那么系统会自动销毁这个 Service。
  • 通信:通过 Binder 机制进行通信,Service 可以暴露一个 Binder 对象给客户端,客户端通过这个 Binder 对象直接调用 Service 的公共方法。

Service 的生命周期

理解生命周期是掌握 Service 的关键,它的生命周期比 Activity 更复杂,因为它结合了启动和绑定两种模式。

service 安卓
(图片来源网络,侵删)

以下是 Service 的生命周期方法:

  • onCreate(): Service 被创建时调用。只调用一次,在这里进行一些初始化工作,比如初始化线程、注册监听器等。
  • onStartCommand(Intent intent, int flags, int startId): 当组件通过 startService() 启动 Service 时调用,每次启动都会调用此方法,Service 会一直运行,直到调用 stopSelf()stopService(),你可以在这里处理传入的 Intent 数据。
  • onBind(Intent intent): 当组件通过 bindService() 绑定 Service 时调用。必须实现此方法,Service 不允许绑定,应返回 null,返回的 IBinder 对象是客户端用来与 Service 通信的关键。
  • onUnbind(Intent intent): 当所有客户端都解绑时调用,默认返回 false
  • onRebind(Intent intent): 当新客户端绑定到已经解绑的 Service 时调用(仅在 onUnbind 返回 true 时有效)。
  • onDestroy(): Service 被销毁时调用。只调用一次,在这里进行资源清理,如停止线程、注销监听器等。

生命周期流程图:

// 启动服务
startService()  -->  onCreate()  -->  onStartCommand()  -->  Service Running
     ^                                                                  |
     |                                                                  |
     +------------------ stopService() / stopSelf() ---------------------+
// 绑定服务
bindService()  -->  onCreate()  -->  onBind()  -->  Client Connected
     |                                                                     |
     |                                                                     |
     +------------------ unbindService() ---------------------------------+
                                                                     |
                                                                     |
// 启动并绑定
startService() --> onCreate() --> onStartCommand() --> Service Running
      |                                                        |
bindService()                                                onBind() --> Client Connected
      |                                                        |
      +------------------ unbindService() ---------------------+
      |
      +------------------ stopService() / stopSelf() --------------> onDestroy()

如何创建和使用一个 Service?(代码示例)

步骤 1:创建 Service 类

javakotlin 目录下创建一个继承自 Service 的类。

Java 示例 (MyService.java):

service 安卓
(图片来源网络,侵删)
public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "Service is created.");
        // 初始化工作
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "Service is started.");
        // 在这里执行后台任务
        // 启动一个线程
        doBackgroundWork();
        return START_STICKY; // 重要:返回启动模式
    }
    @Override
    public IBinder onBind(Intent intent) {
        // 如果是绑定服务,返回一个Binder对象
        // 如果不是,返回null
        return null;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyService", "Service is destroyed.");
        // 清理工作
    }
    private void doBackgroundWork() {
        // 模拟耗时任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 注意:不能在子线程中更新UI!
                Log.d("MyService", "Background work is running...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d("MyService", "Background work finished.");
            }
        }).start();
    }
}

Kotlin 示例 (MyService.kt):

class MyService : Service() {
    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "Service is created.")
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("MyService", "Service is started.")
        doBackgroundWork()
        return START_STICKY // 重要:返回启动模式
    }
    override fun onBind(intent: Intent): IBinder? {
        // 如果是绑定服务,返回一个Binder对象
        return null
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyService", "Service is destroyed.")
    }
    private fun doBackgroundWork() {
        // 使用协程来处理耗时任务(Kotlin推荐)
        CoroutineScope(Dispatchers.IO).launch {
            Log.d("MyService", "Background work is running...")
            delay(5000)
            Log.d("MyService", "Background work finished.")
        }
    }
}

步骤 2:在 AndroidManifest.xml 中注册 Service

<manifest ...>
    <application ...>
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="false" />
    </application>
</manifest>
  • android:name: Service 的全类名。
  • android:exported: false 表示此 Service 只能被应用内部组件访问,不能被其他应用调用。

步骤 3:在 Activity 中启动 Service

启动服务 (startService)

// 在 Activity 中
Intent intent = new Intent(this, MyService.class);
startService(intent); // 启动或重启服务
// 在 Activity 中
val intent = Intent(this, MyService::class.java)
startService(intent)

停止服务

stopService(intent); // 停止服务
// 或者在Service内部调用
// stopSelf();
stopService(intent)
// 或者在Service内部调用
// stopSelf()

Service 的启动与绑定详解

启动模式 (onStartCommand 的返回值)

onStartCommand 方法的返回值告诉系统当 Service 被系统杀死后应该如何重启它。

  • START_NOT_STICKY: Service 被杀死,不会自动重启,适用于那些可以轻松恢复状态的任务。
  • START_STICKY: Service 被杀死,系统会尝试重新创建这个 Service,并调用 onStartCommand,但不会传入之前的 Intent,适用于播放器等需要持续运行的服务。
  • START_REDELIVER_INTENT: Service 被杀死,系统会尝试重新创建这个 Service,并调用 onStartCommand会传入最后被杀掉时收到的 Intent,适用于下载任务,需要确保任务能完成。

绑定服务与 Binder 通信

为了让 Activity 能调用 Service 里的方法,我们需要实现 Binder

修改 Service,创建一个 Binder 类

// MyService.kt
class MyService : Service() {
    // 定义一个 Binder 类,用于暴露 Service 的公共方法
    private val binder = LocalBinder()
    inner class LocalBinder : Binder() {
        // 返回当前 Service 实例,这样客户端就可以访问公共方法
        fun getService(): MyService = this@MyService
    }
    override fun onBind(intent: Intent): IBinder {
        return binder
    }
    // 一个公共方法,供 Activity 调用
    fun getCurrentTime(): String {
        return SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
    }
}

在 Activity 中绑定并使用 Service

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private var myService: MyService? = null
    private var isBound = false
    // Service 连接的回调
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // 获取 Service 实例
            val binder = service as MyService.LocalBinder
            myService = binder.getService()
            isBound = true
            Log.d("MainActivity", "Service connected.")
        }
        override fun onServiceDisconnected(arg0: ComponentName) {
            isBound = false
            Log.d("MainActivity", "Service disconnected.")
        }
    }
    override fun onStart() {
        super.onStart()
        Intent(this, MyService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }
    override fun onStop() {
        super.onStop()
        if (isBound) {
            unbindService(connection)
            isBound = false
            Log.d("MainActivity", "Service unbound.")
        }
    }
    fun getTimeFromService(view: View) {
        if (isBound) {
            val time = myService?.getCurrentTime()
            Toast.makeText(this, "Current time from service: $time", Toast.LENGTH_SHORT).show()
        }
    }
}

前台服务

什么是前台服务? 前台服务会通知用户(通过一个状态栏通知),即使用户不与应用交互,它也不容易被系统杀死,这是为了防止一些关键的后台任务被系统为了省电而终止。

为什么需要? 从 Android 8.0 (Oreo) 开始,对后台服务的限制非常严格,如果你需要在后台执行一个用户感知的、重要的任务(如导航、播放音乐),就必须使用前台服务。

如何实现?

// 在 Service 中
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // 创建一个通知渠道 (Android 8.0+ 需要)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channelId = "my_channel_id"
        val channelName = "My Channel"
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel(channelId, channelName, importance)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
    // 创建通知
    val notification: Notification = NotificationCompat.Builder(this, "my_channel_id")
        .setContentTitle("Foreground Service")
        .setContentText("This is a foreground service running.")
        .setSmallIcon(R.drawable.ic_notification) // 必须设置一个图标
        .build()
    // 将 Service 提升为前台服务
    startForeground(1, notification) // 1 是通知的 ID
    // 执行你的任务...
    return START_STICKY
}

注意:

  • 必须提供一个 Notification,否则会抛出 ForegroundServiceStartNotAllowedException 异常。
  • 必须设置一个小的图标 (setSmallIcon)。

后台服务限制

  • Android 8.0 (Oreo, API 26): 引入了后台执行限制,如果应用的目标 SDK 版本是 26 或更高,应用在后台时无法长时间运行后台服务,如果超过限制时间,系统会抛出 AnrException 或停止 Service。

    • 解决方案:使用 JobScheduler, WorkManager前台服务
  • Android 9.0 (Pie, API 28): 对后台服务的访问权限进一步收紧,即使应用处于前台,也只能访问有限的设备信息。

  • Android 10 (Q, API 29): 引入了应用待机存储分区,长时间未使用的应用会被限制,其后台服务访问存储的能力也会受到限制。

  • Android 11 (API 30): 后台应用无法启动前台服务,除非它拥有特定的权限(如 FOREGROUND_SERVICE)。

  • Android 12 (API 31): 引入了更严格的启动前台服务限制,应用在启动前台服务前,必须先请求 FOREGROUND_SERVICE 权限,并指定一个服务类型(如 location, camera)。

对于简单的后台任务,强烈推荐使用 WorkManager,它能够更好地处理电池和系统限制,只有当任务对用户可见(如播放音乐、导航)时,才应使用前台服务。


最佳实践与注意事项

  1. 不要阻塞主线程:Service 默认运行在主线程,所有耗时操作(网络、IO、计算)都必须在子线程、线程池或使用协程/Kotlin 协程来处理,否则会触发 ANR (Application Not Responding) 错误。
  2. 使用 WorkManager 替代后台任务:对于可以延迟、可重试的后台任务,优先使用 WorkManager,它更可靠、更省电,并且能很好地适配不同版本的 Android 系统。
  3. 明确使用场景:不要滥用 Service,只在真正需要后台运行且用户需要感知(前台服务)或需要实时通信(绑定服务)时才使用它。
  4. 及时清理资源:在 onDestroy() 中务必停止所有线程、注销广播接收器、关闭数据库连接等,防止内存泄漏。
  5. 处理配置更改:当屏幕旋转时,Activity 会被销毁并重建,Service 正在与该 Activity 通信,需要确保 Service 的引用不会因为 Activity 的销毁而失效,通常的做法是在 onDestroy() 中解绑 Service。
  6. 考虑使用 IntentServiceIntentServiceService 的一个子类,它内部有一个工作线程,会自动处理请求队列,并在所有任务完成后自动停止,它简化了单次后台任务的实现,但在现代 Android 开发中,推荐直接使用 Service + 协程或 WorkManager

特性 描述
核心作用 在后台执行无 UI 的长时间任务。
启动服务 startService() 启动,独立运行,适合一次性任务。
绑定服务 bindService() 绑定,提供接口交互,适合需要双向通信的场景。
生命周期 比复杂,结合了启动和绑定的流程。onCreate()onStartCommand()onBind()onDestroy() 是关键。
线程 默认运行在主线程!耗时操作必须手动处理。
前台服务 通过通知提升优先级,不易被杀死,用于音乐、导航等关键任务。
现代替代方案 WorkManager 是处理后台任务的官方推荐方式,更可靠、更省电。

希望这份详细的指南能帮助你彻底理解 Android Service!

-- 展开阅读全文 --
头像
安卓版PPSSPP在哪下载?好用吗?
« 上一篇 今天
手机自定义铃声怎么删?
下一篇 » 59分钟前

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]