O Foreground Service é um componente especial que permite que os aplicativos Android façam um trabalho útil mesmo quando estão em segundo plano. Por exemplo, os Serviços de Primeiro Plano estão envolvidos quando você ouve seu podcast favorito em qualquer lugar, rastreia sua localização enquanto pedala e transmite seu jogo na Twitch.
Neste artigo, explicarei como o Foreground Service funciona e mostrarei como adicionar um ao seu aplicativo Android.
Serviço vs Serviço em Primeiro Plano
Como o Serviço de Primeiro Plano funciona em segundo plano, o prefixo “primeiro plano” pode ser confuso. Vamos entender por que isso realmente faz sentido.
Começa com uma classe de Serviço regular. De acordo com a documentação oficial:
Um serviço é um componente de aplicativo que pode executar operações de longa duração em segundo plano.
Documentação do Android.
Os serviços existem desde os primeiros dias do Android. Inicialmente, eles poderiam de fato realizar operações de longa duração em segundo plano. No entanto, com o tempo, o Google impôs cada vez mais restrições ao trabalho em segundo plano em aplicativos Android. O objetivo dessas restrições era prolongar a vida útil da bateria dos usuários, melhorar o desempenho e preservar a privacidade (por exemplo, alguns aplicativos abusavam de serviços em segundo plano para rastrear a localização do usuário). O Google teve sucesso em seus esforços, mas, ao longo do caminho, o trabalho em segundo plano no Android tornou-se tão restrito que um serviço iniciado regularmente tornou-se efetivamente inútil neste contexto. Hoje em dia, mesmo que você inicie um serviço em seu aplicativo, ele ficará ocioso poucos minutos depois que o usuário enviar seu aplicativo para segundo plano (às vezes até mais rápido).
Portanto, o serviço regular não pode suportar casos de uso importantes para trabalho em segundo plano: reprodução de mídia, rastreamento de localização, streaming, etc. Entre no serviço em primeiro plano.
O Foreground Service é um serviço e, assim como um serviço normal, o Foreground Service é executado em segundo plano. No entanto, quando um processo inicia um serviço em primeiro plano, o sistema operacional Android trata esse processo como se tivesse um componente em primeiro plano (por exemplo, atividade em primeiro plano), portanto, não aplica restrições de segundo plano a ele. Isso permite que o Foreground Service permaneça funcional em situações em que um serviço regular estaria inativo por muito tempo (ou, até mesmo, todo o processo seria eliminado ).
Resumindo, o prefixo “primeiro plano” no contexto de um serviço em primeiro plano refere-se ao mecanismo dentro do sistema operacional Android que fornece mais recursos para processos em “primeiro plano” em oposição a processos em “segundo plano”. Em essência, o Foreground Service é apenas um serviço VIP que recebe tratamento especial do sistema operacional.
Declarando serviço de primeiro plano no AndroidManifest
Assim como um serviço normal, o serviço de primeiro plano deve ser declarado em seu AndroidManifest.xml
arquivo:
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync"
/>
Observe o foregroundServiceType
atributo. Será obrigatório no Android 14 .
Ao contrário do serviço normal, o serviço em primeiro plano requer permissões adicionais:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
A primeira permissão, FOREGROUND_SERVICE
, é concedida ao aplicativo no momento da instalação e deve ser autoexplicativa.
A segunda permissão, POST_NOTIFICATIONS
, é uma permissão de tempo de execução que foi adicionada na API 33. Como você verá em breve, ao implementar um serviço em primeiro plano, o sistema força você a criar uma nova notificação correspondente a esse serviço que será mostrada ao usuário . A ideia é garantir que o usuário esteja ciente de que existe um serviço em primeiro plano em execução em segundo plano. Bem, para ser mais preciso, essa era a ideia no passado, mas com a API 33 o Google mudou de ideia. A partir da API 33, se quiser que o usuário veja a notificação, você deverá obter explicitamente a aprovação dele solicitando essa permissão. Se o usuário não conceder a permissão, você ainda poderá iniciar seu serviço em primeiro plano, mas a notificação não será mostrada.
Implementando serviço em primeiro plano
Este é o código-fonte de um serviço em primeiro plano. Este não faz nada de útil, mas, para sua conveniência, adicionei comentários explicando onde você deve inserir sua própria lógica:
class ForegroundService: Service() {
private lateinit var notificationManager: NotificationManager
// onStartCommand can be called multiple times, so we keep track of "started" state manually
private var isStarted = false
override fun onCreate() {
super.onCreate()
// initialize dependencies here (e.g. perform dependency injection)
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
override fun onDestroy() {
super.onDestroy()
isStarted = false
}
override fun onBind(intent: Intent?): IBinder? {
throw UnsupportedOperationException() // bound Service is a different story
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!isStarted) {
makeForeground()
// place here any logic that should run just once when the Service is started
isStarted = true
}
// process the command here (e.g. retrieve extras from the Intent and act accordingly)
val demoString = intent?.getStringExtra(EXTRA_DEMO) ?: ""
return START_STICKY // makes sense for a Foreground Service, or even START_REDELIVER_INTENT
}
private fun makeForeground() {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// before calling startForeground, we must create a notification and a corresponding
// notification channel
createServiceNotificationChannel()
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText("Foreground Service demonstration")
.setSmallIcon(R.drawable.ic_your_app_logo)
.setContentIntent(pendingIntent)
.build()
startForeground(ONGOING_NOTIFICATION_ID, notification)
}
private fun createServiceNotificationChannel() {
if (Build.VERSION.SDK_INT < 26) {
return // notification channels were added in API 26
}
val channel = NotificationChannel(
CHANNEL_ID,
"Foreground Service channel",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
notificationManager.createNotificationChannel(channel)
}
companion object {
private const val ONGOING_NOTIFICATION_ID = 101
private const val CHANNEL_ID = "1001"
private const val EXTRA_DEMO = "EXTRA_DEMO"
fun startService(context: Context, demoString: String) {
val intent = Intent(context, ForegroundService::class.java)
intent.putExtra(EXTRA_DEMO, demoString)
if (Build.VERSION.SDK_INT < 26) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
}
fun stopService(context: Context) {
val intent = Intent(context, ForegroundService::class.java)
context.stopService(intent)
}
}
}
Observe as verificações de nível de API que adicionei para desenvolvedores que precisam oferecer suporte a versões mais antigas da API. Se o seu aplicativo minSdkVersion
tiver mais de 26 anos, você poderá remover todos eles.
Iniciando e interrompendo o serviço em primeiro plano
Você pode iniciar o procedimento acima ForegroundService
a partir de outros componentes invocando este método:
ForegroundService.startService(context, "some string you want to pass into the service")
A maioria dos Foreground Services não triviais aceitará parâmetros de configuração adicionais de seus chamadores. Para acomodar esse requisito, basta adicionar esses parâmetros à assinatura do método acima e, em seguida, inseri-los na intenção (de forma análoga a demoString
).
Para parar ForegroundService
, invoque este método:
ForegroundService.stopService(context)
Se precisar interromper o serviço internamente, você também pode usar stopSelf()
o método.