Android가 포 그라운드 서비스를 종료합니다.


85

업데이트 : 문제에 대한 진정한 해결책을 찾지 못했습니다. 내가 생각 해낸 것은 연결이 끊어 질 때마다 이전 블루투스 장치에 자동으로 다시 연결하는 방법이었습니다. 이상적이지는 않지만 상당히 잘 작동하는 것 같습니다. 나는 이것에 관한 더 많은 제안을 듣고 싶습니다.

나는이 질문에서와 거의 같은 문제가 있습니다 : wake lock을 유지하는 동안 서비스가 종료되고 장치 (Asus Transformer)를 포함하여 startForeground호출 한 후 서비스가 중지되기까지의 시간 (30-45 분), 사용 wake lock, startForeground () 사용 및 화면이 꺼질 때 앱이 열려 있으면 문제가 발생하지 않는다는 사실.

내 앱은 다른 기기와 블루투스 연결을 유지하고 두 기기간에 데이터를 전송하므로 데이터를 수신하려면 항상 활성화되어 있어야합니다. 사용자는 마음대로 서비스를 시작하고 중지 할 수 있으며 실제로 이것이 서비스를 시작하거나 중지하기 위해 구현 한 유일한 방법입니다. 서비스가 다시 시작되면 다른 장치에 대한 블루투스 연결이 끊어집니다.

연결된 질문의 답변에 따르면 startForeground ()는 "서비스가 종료 될 가능성을 줄이지 만이를 방지하지는 않습니다". 나는 그 사실을 이해하지만,이 문제가없는 다른 앱 (예 : Tasker)의 많은 예를 보았습니다.

사용자가 중지 할 때까지 서비스를 실행하지 않으면 내 앱의 유용성이 크게 떨어집니다. 이것을 피할 방법이 있습니까 ???

서비스가 중지 될 때마다 내 logcat에 다음이 표시됩니다.

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

편집 : 또한 내가 연결된 다른 장치에서는 발생하지 않는 것 같습니다 : Cyanogen을 실행하는 HTC Legend

편집 : 다음은 출력입니다 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}

그리고 출력 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)

이는 서비스가 포 그라운드에서 실행되고 있음을 나타냅니다.


이 답변에 봐 - 당신을 위해 힘 작업은 stackoverflow.com/a/21157035/624109
Muzikant

답변:


218

좋아요 dokey. 나는 지옥을 겪었고이 문제로 돌아 왔습니다. 진행 방법은 다음과 같습니다. 버그가 있습니다. 이 게시물은 구현에서 버그를 분석하고 문제를 해결하는 방법을 설명합니다.

요약하자면 다음은 작동 방식입니다. 실행중인 서비스는 정기적으로 청소되고 30 분 정도마다 종료됩니다. 이보다 더 오래 살아남 으려는 서비스는 Service.startForeground를 호출해야합니다. Service.startForeground는 알림 표시 줄에 알림을 표시하여 사용자가 서비스가 영구적으로 실행되고 잠재적으로 배터리 수명을 소모하고 있음을 알 수 있도록합니다. 주어진 시간에 3 개의 서비스 프로세스 만이 자신을 포 그라운드 서비스로 지정할 수 있습니다. 포 그라운드 서비스가 3 개 이상인 경우 Android는 가장 오래된 서비스를 청소 및 종료 후보로 지정합니다.

안타깝게도 Android에는 다양한 서비스 바인딩 플래그 조합에 의해 트리거되는 포 그라운드 서비스의 우선 순위 지정과 관련하여 버그가 있습니다. 서비스를 포 그라운드 서비스로 올바르게 지정 했어도 프로세스의 서비스에 대한 연결이 특정 결합 플래그 조합으로 이루어진 경우 Android는 어쨌든 서비스를 종료 할 수 있습니다. 자세한 내용은 다음과 같습니다.

포 그라운드 서비스가 필요한 서비스는 거의 없습니다. 일반적으로 사용자가 켜고 끄거나 취소 할 수있는 일종의 인터넷 연결이 지속적으로 활성화되거나 장기적으로 실행되는 경우에만 포 그라운드 서비스가 필요합니다. 포 그라운드 상태가 필요한 서비스의 예 : UPNP 서버, 대용량 파일의 장기 실행 다운로드, Wi-Fi로 파일 시스템 동기화, 음악 재생.

가끔 폴링하거나 시스템 브로드 캐스트 수신기 또는 시스템 이벤트를 기다리는 경우 타이머에서 서비스를 깨우거나 브로드 캐스트 수신기에 대한 응답으로 서비스를 깨운 다음 ​​서비스가 완료되면 종료하는 것이 좋습니다. 이것이 바로 서비스를 위해 설계된 동작입니다. 단순히 살아남 아야한다면 계속 읽으십시오.

잘 알려진 요구 사항 (예 : Service.startForeground 호출)에 대한 확인란을 선택했으면 다음으로 살펴볼 곳은 Context.bindService 호출에서 사용하는 플래그입니다. 바인딩에 사용되는 플래그는 예기치 않은 다양한 방식으로 대상 서비스 프로세스의 우선 순위에 영향을줍니다. 특히 특정 바인딩 플래그를 사용하면 Android가 포 그라운드 서비스를 정규 서비스로 잘못 다운 그레이드 할 수 있습니다. 프로세스 우선 순위를 지정하는 데 사용되는 코드가 상당히 많이 변경되었습니다. 특히 API 14+에는 이전 바인딩 플래그를 사용할 때 버그를 유발할 수있는 개정이 있습니다. 4.2.1에는 확실한 버그가 있습니다.

이 모든 것에서 친구는 sysdump 유틸리티로, 활동 관리자가 서비스 프로세스에 할당 한 우선 순위를 파악하고 잘못된 우선 순위를 할당 한 사례를 파악하는 데 사용할 수 있습니다. 서비스를 시작하고 실행 한 후 호스트 컴퓨터의 명령 프롬프트에서 다음 명령을 실행하십시오.

adb 쉘 dumpsys 활동 프로세스> tmp.txt

내용을 확인하려면 메모장 (워드 패드 / 쓰기 아님)을 사용하십시오.

먼저 포 그라운드 상태에서 서비스를 성공적으로 실행했는지 확인하십시오. dumpsys 파일의 첫 번째 섹션에는 각 프로세스의 ActivityManager 속성에 대한 설명이 포함되어 있습니다. dumpsys 파일의 첫 번째 섹션에서 애플리케이션에 해당하는 다음과 같은 행을 찾으십시오.

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

다음 섹션에서 foregroundServices = true인지 확인하십시오. 숨겨진 빈 설정에 대해 걱정하지 마십시오. 프로세스의 활동 상태를 설명하며 서비스가 포함 된 프로세스와 특별히 관련이없는 것 같습니다. foregroundService가 true가 아니면 Service.startForeground를 호출하여 true로 만들어야합니다.

다음으로 살펴보아야 할 것은 "Process LRU list (sorted by oom_adj) :"라는 파일의 끝 부분에있는 섹션입니다. 이 목록의 항목을 사용하면 Android가 실제로 애플리케이션을 포 그라운드 서비스로 분류했는지 여부를 확인할 수 있습니다. 프로세스가이 목록의 맨 아래에있는 경우 요약 제거를위한 주요 후보입니다. 프로세스가 목록의 맨 위에 있으면 사실상 파괴 할 수 없습니다.

이 표의 한 줄을 살펴 보겠습니다.

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

이것은 모든 것을 올바르게 수행 한 포 그라운드 서비스의 예입니다. 여기서 핵심 필드는 "adj ="필드입니다. 이는 모든 작업이 완료된 후 ActivityManagerService에서 프로세스에 할당 한 우선 순위를 나타냅니다. "adj = prcp"(보이는 포 그라운드 서비스)를 원합니다. 또는 "adj = vis"(활동이있는 가시적 프로세스) 또는 "fore"(포 그라운드 활동이있는 프로세스). "adj = svc"(서비스 프로세스) 또는 "adj = svcb"(레거시 서비스?) 또는 "adj = bak"(빈 백그라운드 프로세스) 인 경우 프로세스가 종료 될 가능성이 높으며 종료됩니다. 메모리를 회수해야하는 부담이 없더라도 30 분마다 라인의 나머지 플래그는 대부분 Google 엔지니어를위한 진단 디버그 정보입니다. 종료에 대한 결정은 조정 필드를 기반으로합니다. 간단히 / FS는 포 그라운드 서비스를 나타냅니다. / FA는 활동이있는 포 그라운드 프로세스를 나타냅니다. / B는 백그라운드 서비스를 나타냅니다. 끝에있는 레이블은 프로세스에 우선 순위가 할당 된 일반 규칙을 나타냅니다. 일반적으로 adj = 필드와 일치해야합니다. 그러나 adj = 값은 다른 서비스 또는 활동과의 활성 바인딩에 대한 바인딩 플래그로 인해 경우에 따라 위쪽 또는 아래쪽으로 조정할 수 있습니다.

바인딩 플래그로 버그를 발견했다면 dumpsys 라인은 다음과 같습니다.

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

adj 필드의 값이 "adj = bak"(빈 백그라운드 프로세스)로 잘못 설정되어있는 방법에 유의하십시오. 이는 프로세스 청소를 위해 "이 무의미한 존재를 끝낼 수 있도록 지금 나를 종료하십시오"로 변환됩니다. 또한 줄 끝에있는 "포 그라운드 서비스 규칙이"adj "설정을 결정하는 데 사용되었음을 나타내는 (fg-service) 플래그를 확인합니다. fg-service 규칙이 사용 되었음에도 불구하고이 프로세스에는 adj 설정이 할당되었습니다. "박", 그리고 오래 살지 않을 것입니다. 분명히 말해서 이것은 버그입니다.

따라서 목표는 프로세스가 항상 "adj = prcp"(또는 그 이상)를 얻도록하는 것입니다. 그리고 그 목표를 달성하는 방법은 우선 순위 할당에서 버그를 피할 수있을 때까지 바인딩 플래그를 조정하는 것입니다.

내가 아는 버그는 다음과 같습니다. (1) Context.BIND_ABOVE_CLIENT를 사용하여 서비스 또는 활동이 서비스에 바인딩 된 적이있는 경우 해당 바인딩이 더 이상 활성화되지 않더라도 adj = 설정이 "bak"로 다운 그레이드 될 위험이 있습니다. 서비스간에 바인딩이있는 경우 특히 그렇습니다. 4.2.1 소스의 명확한 버그. (2) 서비스 대 서비스 바인딩에 BIND_ABOVE_CLIENT를 절대 사용하지 마십시오. 활동-서비스 연결에도 사용하지 마십시오. BIND_ABOVE_CLIENT 동작을 구현하는 데 사용되는 플래그는 연결 단위가 아닌 프로세스 단위로 설정된 것처럼 보이므로 활성 활동 대 서비스가없는 경우에도 서비스 대 서비스 바인딩으로 버그를 트리거합니다. 플래그 세트와 바인딩. 또한 서비스 간 바인딩을 사용하여 프로세스에 여러 서비스가있을 때 우선 순위를 설정하는 데 문제가있는 것 같습니다. 서비스 간 바인딩에 Context.BIND_WAIVE_PRIORITY (API 14)를 사용하는 것이 도움이되는 것 같습니다. Context.BIND_IMPORTANT는 활동에서 서비스로 바인딩 할 때 다소 좋은 생각 인 것 같습니다. 이렇게하면 활동이 일시 중지되거나 완료 될 때 명백한 해를 끼치 지 않고 활동이 포 그라운드에있을 때 프로세스 우선 순위가 한 단계 더 높아집니다.

그러나 전반적으로 전략은 sysdump가 프로세스가 올바른 우선 순위를 받았음을 나타낼 때까지 bindService 플래그를 조정하는 것입니다.

내 목적을 위해 Context.BIND_AUTO_CREATE | Activity-to-Service 바인딩의 경우 Context.BIND_IMPORTANT 및 Context.BIND_AUTO_CREATE | 서비스 간 바인딩에 대한 Context.BIND_WAIVE_PRIORITY는 올바른 일을하는 것 같습니다. 귀하의 마일리지가 다를 수 있습니다.

내 앱은 상당히 복잡합니다. 각각 독립적으로 포 그라운드 서비스 상태를 보유 할 수있는 두 개의 백그라운드 서비스와 포 그라운드 서비스 상태를 취할 수있는 세 번째 서비스입니다. 두 서비스는 조건부로 서로 바인딩됩니다. 세 번째는 항상 첫 번째에 바인딩됩니다. 또한 Activites는 별도의 프로세스로 실행됩니다 (애니메이션을 더 부드럽게 만듭니다). 동일한 프로세스에서 활동과 서비스를 실행하는 것은 아무런 차이가없는 것 같습니다.

청소 프로세스에 대한 규칙 구현 (및 sysdump 파일의 내용을 생성하는 데 사용되는 소스 코드)은 핵심 Android 파일에서 찾을 수 있습니다.

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

좋은 기회.

추신 : 다음은 Android 5.0 용 sysdump 문자열의 해석입니다. 나는 그들과 함께 일하지 않았으므로 당신이 원하는대로 만드십시오. 4는 'A'또는 'S', 5는 "IF"또는 "IB", 1은 가능한 한 낮게 (아마 3 개 미만의 포 그라운드 서비스 프로세스 만 활성 상태로 유지되기 때문에) 기본 구성).

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, 작은 질문이 있습니다. bindService()지속적으로 서비스를 실행해야하는 경우 정말로 전화 해야합니까? 전화 만하면 충분하지 startForeground()않나요? EventBus를 사용하여 서버 I와의 통신을 위해.
ar-g

처음에 서비스를 실행하기 위해 Activity에서 Context.bindService를 호출합니다. Service.startService 메서드는 시작된 서비스를 "포 그라운드"상태로 이동하기 위해 서비스의 코드에 의해 호출됩니다. EventBus 라이브러리가 서비스를 시작하기 위해 어느 시점에서 귀하를 대신하여 Context.bindService를 호출한다고 가정합니다. 서비스를 시작하는 다른 방법이 있다면 잘 모르겠습니다.
Robin Davies

3
그레이트 포스트! 찬성. 이 댓글에 추가하고 싶은 한 조각은 관련성이 있다고 생각합니다. 지속적으로 실행되는 서비스를 원한다면 Robin이 언급했듯이 어떻게 든 시작해야합니다. bindService ()가 아닌 액티비티 내에서 직접 startService (Intent service)를 호출 할 수 있으며 서비스가 시작되면 startForeground () 메서드를 호출 할 수 있습니다. 서비스 클래스의 onStartCommand ()에서 이것을 호출합니다. 내가 아는 한, 이것은 서비스가 바인딩되지 않도록해야하지만 보류중인 리소스 문제를 계속 실행해야합니다. 바라건대 이것은 누군가를 도울 것입니다.
Dave

훌륭한 일!! 이것에 업데이트를 추가하고 싶습니다. 먼저 adb의 출력 형식이 약간 변경되었습니다 (2016 년 1 월). 이 프로세스를 두 장치에서 테스트했습니다. LG Volt 4.4.2 및 Nexus 5x 6.0.1 두 장치 모두 여전히 버그가 있습니다. 나는 Context.BIND_ABOVE_CLIENT를 사용해서 만 문제를 재현 할 수 있습니다. 활동 종료 후 장치. 다른 모든 플래그는 두 Android 버전에서 모두 정상적으로 작동하는 것 같습니다.
user3259330

1
@Dave hey Dave, 정확히 그 방법을 사용하고 START_STICKY를 반환하지만 내 서비스는 항상 기기가 유휴 상태 일 때 한 시간 정도 후에 종료됩니다. 당신은에 갈 수 있는지의 어떤 아이디어가 있습니까
Ruchir Baronia

7

"더 이상 원하지 않습니다 ..."라고 말하면 해당 프로세스에는 현재 startForeground () 상태에있는 서비스가 활성화되어 있지 않습니다. 그에 대한 호출이 실제로 성공하고 있는지 확인하십시오. 알림이 게시되었는지, 그 시점에서 로그에 어떤 것에 대해 불평하는 메시지가 없는지 등을 확인하십시오. 또한 "adb shell dumpsys activity services"를 사용하여 서비스 상태를 확인하고 실제로 포 그라운드로 표시되었는지 확인합니다. 또한 올바르게 포 그라운드 인 경우 "adb shell dumpsys activity"의 출력에서 ​​해당 서비스로 인해 현재 포 그라운드 수준에있는 프로세스의 OOM adj를 보여주는 섹션에 표시됩니다.


도움을 주셔서 감사합니다! 언급 한 명령의 출력으로 내 질문을 편집했습니다. 서비스가 포 그라운드에서 실행되고 있음을 나타내는 것 같습니다.
howettl 2011-07-11

진단에 도움이 될 수있는 코드 섹션이 있습니까?
howettl

1
확실히 포 그라운드에서 죽여서는 안되며, 표준 플랫폼의 음악과 같은 것들은 그렇지 않다는 것을 확실히 알고 있습니다. 문제를 재현하기 위해 코드로 버그를 제출하는 것을 고려하십시오. 한 가지 살펴보아야 할 점은 어떤 지점에서든 포 그라운드로 들어갔다 나오면 죽을 수 있다는 것입니다.
hackbod

1
startForeground ()를 다시 호출하는 대신 notify ()를 호출하여 진행중인 알림을 업데이트하면 포 그라운드 상태에서 벗어날 수 있습니까? 중요한 경우 알림에서 FLAG_ALERT_ONLY_ONCE를 활성화했습니다.
howettl

2
알림 관리자를 통해 업데이트하지 마십시오. 서비스를 통해 게시하고 있으며 서비스를 통해 계속 업데이트해야합니다.
hackbod
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.