前言
前面我们说过,从 Android 8.0 开始,系统为了优化电池和内存,会严格限制后台活动。应用一旦进入后台,Service
随时可能被系统回收。如果需要一个长时间运行的后台任务,就可以使用前台 Service。
前台 Service 会在系统状态栏中显示一个常驻通知,让用户清楚该应用正在后台活动。
那我们来看看如何创建一个前台 Service。
前台 Service 代码实现
MyService
中的代码如下:
kotlin
class MyService : Service() {
companion object {
private const val TAG = "MyService"
private const val CHANNEL_ID = "my_service_channel"
private const val NOTIFICATION_ID = 1
}
// 创建协程作用域
private val serviceScope = CoroutineScope(Dispatchers.Main)
// 持有我们的任务,以便后续可以取消它
private var timerJob: Job? = null
override fun onBind(intent: Intent?): IBinder? = null
/**
* 创建通知渠道
*/
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"前台服务渠道",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
/**
* 服务启动的回调
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 创建通知渠道
createNotificationChannel()
// 构建意图
val pendingIntent: PendingIntent =
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_IMMUTABLE // FLAG_IMMUTABLE 可防止其他应用篡改 PendingIntent
)
// 创建通知
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("服务运行中")
.setContentText("正在执行关键后台任务...")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.avatar)) // 我的头像
.setContentIntent(pendingIntent)
.build()
// 提升为前台服务
startForeground(NOTIFICATION_ID, notification)
// 启动协程来执行实际的耗时任务
timerJob = serviceScope.launch {
while (isActive) {
// 执行我任务:打印当前时间
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
val currentTime = sdf.format(Date())
Log.d(TAG, "服务正在运行, 当前时间: $currentTime")
delay(1000)
}
}
return START_STICKY // 服务被关闭后,系统会重新创建
}
override fun onDestroy() {
super.onDestroy()
// 服务销毁时,取消协程
timerJob?.cancel()
Log.d(TAG, "服务已销毁,协程任务已取消")
}
}
然后,在 MainActivity
中申请通知权限并启动服务。代码如下:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
// 权限被授予
startMyService()
} else {
// 权限被拒绝,向用户解释
Toast.makeText(this, "需要通知权限以显示服务状态", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.startServiceBtn.setOnClickListener {
checkPermissionAndStartService()
}
binding.stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止服务
}
}
/**
* 检查通知权限是否被授予并启动服务
*/
private fun checkPermissionAndStartService() {
// 动态请求通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
// 已有权限,直接启动服务
startMyService()
} else {
// 请求通知权限
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
} else {
startMyService()
}
}
/**
* 启动 MyService 服务
*/
private fun startMyService() {
val intent = Intent(this, MyService::class.java)
// 使用 ContextCompat.startForegroundService 来启动
ContextCompat.startForegroundService(this, intent)
}
}
清单文件配置
启动前台 Service 需要进行权限声明,在 Android 13+ 后发送通知也需要声明权限。另外,从 Android 14 开始,我们要为前台服务指定具体用途(类型),并且必须声明一个与服务类型匹配的、更具体的权限。
所以 AndroidManifest.xml
文件中的代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application ...>
<service
android:name=".MyService"
android:exported="false"
android:foregroundServiceType="dataSync">
</service>
</application>
</manifest>
因为是示例,所以前台服务的类型就指定为了 dataSync
(数据同步),比如数据的上传和下载、备份和恢复、导入或导出、本地文件处理等。
现在重新运行程序,并点击"Start Service"按钮,MyService
就会以前台 Service
的模式启动了。即使你退出应用,MyService
服务会拥有高的运行优先级,从而能在后台持续运行,被系统回收的概率极低。