Serviço de primeiro plano sendo eliminado pelo Android


86

Atualização : Não encontrei uma solução verdadeira para o problema. O que eu descobri foi um método de reconectar automaticamente a um dispositivo bluetooth anterior sempre que a conexão for perdida. Não é ideal, mas parece funcionar muito bem. Eu adoraria ouvir mais sugestões sobre isso.

Estou tendo o mesmo problema desta pergunta: serviço sendo interrompido enquanto mantém o wake lock e depois de chamar startForeground incluindo o dispositivo (Asus Transformer), o período de tempo antes do serviço ser interrompido (30-45 minutos), o uso de wake lock, o uso de startForeground () e o fato de que o problema não ocorre se o aplicativo for aberto quando a tela desligar.

Meu aplicativo mantém uma conexão bluetooth com outro dispositivo e envia dados entre os dois, portanto, deve estar ativo o tempo todo para ouvir os dados. O usuário pode iniciar e interromper o serviço à vontade e, de fato, esta é a única maneira que implementei para iniciar ou interromper o serviço. Assim que o serviço for reiniciado, a conexão bluetooth com o outro dispositivo será perdida.

De acordo com a resposta da pergunta vinculada, startForeground () "reduz a probabilidade de um serviço ser encerrado, mas não o impede". Eu entendo que seja o caso, porém tenho visto muitos exemplos de outros aplicativos que não têm esse problema (Tasker, por exemplo).

A utilidade do meu aplicativo será bastante reduzida sem a capacidade do serviço ser executado até ser interrompido pelo usuário. Existe alguma maneira de evitar isso ??

Vejo isso no meu logcat sempre que o serviço é interrompido:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

EDITAR: também devo observar que isso não parece ocorrer no outro dispositivo ao qual estou conectado: HTC Legend executando o Cyanogen

EDIT: Aqui está o resultado de adb shell dumpsys activity services:

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

E a saída de adb shell dumpsys activity:

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

Eles parecem mostrar que o serviço está sendo executado em primeiro plano.


Dê uma olhada nesta resposta - pode funcionar para você stackoverflow.com/a/21157035/624109
Muzikant

Respostas:


222

Entendido. Eu já passei pelo inferno e voltei com esse problema. Veja como proceder. Existem bugs. Esta postagem descreve como analisar bugs na implementação e contornar os problemas.

Para resumir, é assim que as coisas devem funcionar. Os serviços em execução serão eliminados rotineiramente e encerrados a cada 30 minutos ou mais. Os serviços que desejam permanecer ativos por mais tempo devem chamar Service.startForeground, que coloca uma notificação na barra de notificação, para que os usuários saibam que seu serviço está funcionando permanentemente e potencialmente consumindo bateria. Apenas 3 processos de serviço podem se autodenominar como serviços de primeiro plano a qualquer momento. Se houver mais de três serviços em primeiro plano, o Android nomeará o serviço mais antigo como candidato para eliminação e encerramento.

Infelizmente, existem bugs no Android com relação à priorização de serviços de primeiro plano, que são acionados por várias combinações de sinalizadores de vinculação de serviço. Mesmo que você tenha nomeado corretamente seu serviço como um serviço de primeiro plano, o Android pode encerrar seu serviço de qualquer maneira, se alguma conexão aos serviços em seu processo tiver sido feita com certas combinações de sinalizadores de ligação. Os detalhes são fornecidos abaixo.

Observe que muito poucos serviços precisam ser serviços de primeiro plano. Geralmente, você só precisa ser um serviço de primeiro plano se tiver uma conexão à Internet constantemente ativa ou de longa duração de algum tipo que possa ser ligada e desligada ou cancelada pelos usuários. Exemplos de serviços que precisam do status de primeiro plano: servidores UPNP, downloads de longa duração de arquivos muito grandes, sincronização de sistemas de arquivos por wi-fi e reprodução de música.

Se você está apenas pesquisando ocasionalmente, ou esperando em receptores de transmissão do sistema ou eventos do sistema, seria melhor ativar seu serviço em um cronômetro, ou em resposta a receptores de transmissão, e então deixar seu serviço morrer quando concluído. Esse é o comportamento projetado para serviços. Se você simplesmente precisa permanecer vivo, continue lendo.

Depois de marcar as caixas em requisitos conhecidos (por exemplo, chamar Service.startForeground), o próximo lugar a observar é nos sinalizadores que você usa nas chamadas Context.bindService. Os sinalizadores usados ​​para vincular afetam a prioridade do processo do serviço de destino de várias maneiras inesperadas. Mais especificamente, o uso de certos sinalizadores de ligação pode fazer com que o Android faça downgrade incorretamente de seu serviço de primeiro plano para um serviço regular. O código usado para atribuir a prioridade do processo foi agitado fortemente. Notavelmente, há revisões na API 14+ que podem causar bugs ao usar sinalizadores de ligação mais antigos; e existem bugs definitivos em 4.2.1.

Seu amigo em tudo isso é o utilitário sysdump, que pode ser usado para descobrir qual prioridade o gerente de atividades atribuiu ao seu processo de serviço e identificar casos em que atribuiu uma prioridade incorreta. Coloque seu serviço em funcionamento e emita o seguinte comando em um prompt de comando em seu computador host:

processos de atividade do adb shell dumpsys> tmp.txt

Use o bloco de notas (não o wordpad / escrita) para examinar o conteúdo.

Primeiro, verifique se você conseguiu executar o serviço em primeiro plano. A primeira seção do arquivo dumpsys contém uma descrição das propriedades ActivityManager para cada processo. Procure uma linha como a seguinte que corresponda ao seu aplicativo na primeira seção do arquivo dumpsys:

APLICATIVO UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Verifique se foregroundServices = true na seção a seguir. Não se preocupe com as configurações ocultas e vazias; eles descrevem o estado das Atividades no processo e não parecem ser particularmente relevantes para processos com serviços neles. Se foregroundService não for verdadeiro, você precisará chamar Service.startForeground para torná-lo verdadeiro.

A próxima coisa que você precisa examinar é a seção próxima ao final do arquivo intitulada "Lista de LRU do processo (classificada por oom_adj):". As entradas nesta lista permitem que você determine se o Android realmente classificou seu aplicativo como um serviço de primeiro plano. Se o seu processo está no final desta lista, é um candidato principal para o extermínio sumário. Se o seu processo estiver próximo ao topo da lista, ele é virtualmente indestrutível.

Vejamos uma linha nesta tabela:

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Este é um exemplo de serviço de primeiro plano que fez tudo certo. O campo-chave aqui é o campo "adj =". Isso indica a prioridade que seu processo foi atribuído pelo ActivityManagerService depois que tudo foi dito feito. Você deseja que seja "adj = prcp" (serviço visível em primeiro plano); ou "adj = vis" (processo visível com uma atividade) ou "anterior" (processo com uma atividade em primeiro plano). Se for "adj = svc" (processo de serviço), ou "adj = svcb" (serviço legado?), Ou "adj = bak" (processo em segundo plano vazio), então seu processo é um provável candidato a encerramento e será encerrado não menos frequentemente do que a cada 30 minutos, mesmo se não houver qualquer pressão para recuperar a memória. Os sinalizadores restantes na linha são principalmente informações de diagnóstico de depuração para engenheiros do Google. As decisões de rescisão são feitas com base nos campos adj. Resumidamente, / FS indica um serviço de primeiro plano; / FA indica um processo em primeiro plano com uma atividade. / B indica um serviço em segundo plano. O rótulo no final indica a regra geral sob a qual o processo recebeu uma prioridade. Normalmente, deve corresponder ao campo adj =; mas o valor adj = pode ser ajustado para cima ou para baixo em alguns casos devido a sinalizadores de ligação em ligações ativas com outros serviços ou atividades.

Se você tropeçou em um bug com sinalizadores de ligação, a linha dumpsys será semelhante a esta:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Observe como o valor do campo adj está incorretamente definido como "adj = bak" (processo em segundo plano vazio), o que se traduz aproximadamente em "por favor, termine-me agora para que eu possa encerrar esta existência inútil" para fins de eliminação do processo. Observe também o sinalizador (fg-service) no final da linha, que indica que "regras de serviço forground foram usadas para determinar a configuração" adj ". Apesar do fato de as regras fg-service terem sido usadas, este processo recebeu uma configuração adj "bak", e não viverá por muito tempo. Simplificando, isso é um bug.

Portanto, o objetivo é garantir que seu processo sempre obtenha "adj = prcp" (ou melhor). E o método para atingir esse objetivo é ajustar os sinalizadores de ligação até conseguir evitar bugs na atribuição de prioridade.

Aqui estão os bugs que conheço. (1) Se QUALQUER serviço ou atividade já se vinculou ao serviço usando Context.BIND_ABOVE_CLIENT, você corre o risco de que a configuração adj = seja rebaixada para "bak", mesmo que a vinculação não esteja mais ativa. Isso é particularmente verdadeiro se você também tiver ligações entre serviços. Um bug claro nas fontes 4.2.1. (2) Definitivamente, nunca use BIND_ABOVE_CLIENT para uma vinculação serviço a serviço. Não o use para conexões de atividade para serviço. O sinalizador usado para implementar o comportamento BIND_ABOVE_CLIENT parece ser definido por processo, em vez de por conexão, então ele aciona bugs com ligações serviço a serviço, mesmo se não houver uma atividade ativa a serviço vinculação com o conjunto de sinalizadores. Também parece haver problemas com o estabelecimento de prioridade quando há vários serviços no processo, com ligações de serviço a serviço. Usando Context.BIND_WAIVE_PRIORITY (API 14) em ligações serviço a serviço parece ajudar. Context.BIND_IMPORTANT parece ser uma ideia mais ou menos boa ao vincular de uma Activity a um serviço. Fazer isso aumenta a prioridade do processo um degrau acima quando a atividade está em primeiro plano, sem causar nenhum dano aparente quando a atividade é pausada ou concluída.

Mas, no geral, a estratégia é ajustar seus sinalizadores de bindService até que sysdump indique que seu processo recebeu a prioridade correta.

Para meus propósitos, usando Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT para ligações de atividade para serviço e Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY para ligações serviço a serviço parece fazer a coisa certa. Sua milhagem pode ser diferente.

Meu aplicativo é bastante complexo: dois serviços de segundo plano, cada um dos quais pode conter independentemente estados de serviço de primeiro plano, mais um terceiro que também pode assumir o estado de serviço de primeiro plano; dois dos serviços se ligam condicionalmente; o terceiro liga-se ao primeiro, sempre. Além disso, as atividades são executadas em um processo separado (torna a animação mais suave). Executar as Atividades e Serviços no mesmo processo não pareceu fazer diferença.

A implementação das regras para processos de eliminação (e o código-fonte usado para gerar o conteúdo dos arquivos sysdump) podem ser encontrados no arquivo android principal

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.

Boa chance.

PS: Esta é a interpretação das strings sysdump para Android 5.0. Não trabalhei com eles, então faça deles o que quiser. Eu acredito que você deseja que 4 seja 'A' ou 'S' e 5 seja "IF" ou "IB" e 1 seja o mais baixo possível (provavelmente abaixo de 3, uma vez que apenas 3 processos de serviço em primeiro plano são mantidos ativos na configuração padrão).

Example:
   Proc # : prcp  F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)

Format:
   Proc # {1}: {2}  {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}

1: Order in list: lower is less likely to get trimmed.

2: Not sure.

3:
    B: Process.THREAD_GROUP_BG_NONINTERACTIVE
    F: Process.THREAD_GROUP_DEFAULT

4:
    A: Foreground Activity
    S: Foreground Service
    ' ': Other.

5:
    -1: procState = "N ";
        ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
    ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
    ActivityManager.PROCESS_STATE_TOP: procState = "T ";
    ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
    ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
    ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
    ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
    ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
    ActivityManager.PROCESS_STATE_HOME: procState = "HO";
    ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
    ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";

{6}: trimMemoryLevel

{8} Process ID.
{9} process name
{10} appUid 

4
@Robin Davies, tenho uma pequena pergunta. Eu realmente preciso ligar bindService()se eu precisar executar o serviço constantemente? Não é suficiente apenas ligar startForeground()para o serviço? Para comunicação com o servidor I usando EventBus.
ar-g

Você chama Context.bindService de uma Activity para que o serviço seja executado em primeiro lugar. O método Service.startService é chamado pelo código no serviço para mover um serviço que foi iniciado para o estado "primeiro plano". Eu presumiria que a biblioteca EventBus está chamando Context.bindService em seu nome em algum ponto para iniciar o serviço. Se houver outra maneira de iniciar um serviço, não estou familiarizado com ela.
Robin Davies

3
Ótimo post! Votado. Gostaria de acrescentar uma peça a este comentário, que considero relevante. Se você deseja um serviço em execução constante, como Robin mencionou, você precisa iniciá-lo de alguma forma. É possível chamar startService (Intent service) diretamente dentro de sua atividade em vez de bindService () e, depois que seu serviço for iniciado, você poderá chamar o método startForeground (). Eu chamo isso no onStartCommand () da classe de serviço. Pelo que eu sei, isso deve fazer com que seu serviço não seja vinculado, mas mantê-lo executando problemas de recursos pendentes. Espero que isso ajude alguém.
Dave de

Ótimo trabalho!! Eu gostaria de adicionar uma atualização para isso. Primeiro, o formato de saída do adb mudou ligeiramente (janeiro de 2016). Eu testei esse processo em dois dispositivos LG Volt 4.4.2 e Nexus 5x 6.0.1, ambos os dispositivos ainda sofrem com o bug. Só posso reproduzir o problema usando Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) Usar o flag troubled causa morte instantânea na maioria das vezes nos mais antigos dispositivo após sair da atividade. Todos os outros sinalizadores parecem funcionar bem em ambas as versões do Android.
user3259330

1
@Dave ei Dave, eu uso exatamente esse método, além de retornar START_STICKY, mas meu serviço sempre morre após cerca de uma hora quando o dispositivo está ocioso. Você tem alguma ideia do que poderia estar acontecendo
Ruchir Baronia

7

Se estiver dizendo "não quero mais ...", então esse processo não tem um serviço ativo nele que esteja atualmente no estado startForeground (). Certifique-se de que sua chamada para está realmente sendo bem-sucedida - se você está vendo a notificação postada, não há mensagens no log naquele ponto reclamando de nada, etc. Use também "serviços de atividade de shell dumpsys adb" para olhar estado do seu serviço e certifique-se de que está realmente marcado como primeiro plano. Além disso, se estiver corretamente em primeiro plano, na saída de "adb shell dumpsys activity", você verá na seção que mostra o ajuste OOM dos processos que seu processo está atualmente no primeiro plano devido a esse serviço.


Obrigado por ajudar! Eu editei minha pergunta com a saída dos comandos que você mencionou. Eles parecem indicar que o serviço está sendo executado em primeiro plano.
Howettl

Existe uma seção de código que eu poderia postar que pode ajudar no diagnóstico?
Howettl

1
Definitivamente, não deve ser eliminado enquanto estiver em primeiro plano, e tenho certeza de que coisas como Música na plataforma padrão não são. Considere registrar um bug com código para reproduzir o problema. Uma coisa a observar é se você está entrando e saindo do primeiro plano em qualquer ponto que possa permitir que ele seja morto.
hackbod

1
É possível que atualizar a notificação em andamento chamando notificar () em vez de chamar startForeground () novamente possa tirá-la do status de primeiro plano? Eu também tenho FLAG_ALERT_ONLY_ONCE ativado na notificação, se isso importa.
Howettl

2
Definitivamente, não o atualize por meio do gerenciador de notificações. Você está postando isso por meio do serviço e deve continuar a atualizá-lo por meio do serviço.
hackbod
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.