Context.startForegroundService ()가 Service.startForeground ()를 호출하지 않았습니다.


258

내가 사용하고 Service안드로이드 O OS에 클래스.

Service백그라운드에서 사용할 계획 입니다.

안드로이드 문서의 상태가

앱이 API 레벨 26 이상을 대상으로하는 경우 앱 자체가 포 그라운드에 있지 않으면 시스템에서 백그라운드 서비스 사용 또는 생성에 대한 제한을 적용합니다. 앱이 포 그라운드 서비스를 작성해야하는 경우 앱은을 호출해야합니다 startForegroundService().

당신이 사용하는 경우 startForegroundService()의는 Service다음과 같은 오류가 발생합니다.

Context.startForegroundService() did not then call
Service.startForeground() 

이게 뭐가 문제 야?


IOW, 최소한의 재현 가능한 예 를 제공하십시오 . 여기에는 전체 Java 스택 추적과 충돌을 유발하는 코드가 포함됩니다.
CommonsWare

3
버그는 여전히 API 26 및 27 (27.0.3)에 있습니다. 영향을받는 Android 버전은 8.0 및 8.1입니다. startForeground ()를 onCreate () 및 onStartCommand ()에 모두 추가하여 충돌 수를 줄일 수 있지만 일부 사용자에게는 충돌이 여전히 발생합니다. atm을 고치는 유일한 방법은 build.gradle의 targetSdkVersion 25입니다.
Alex


1
이 해결 방법을 지금 사용하고 있습니다. 매력 theandroiddeveloper.com/blog/…
Prasad Pawar

1
나도 같은 문제가있어. 이 문제를 해결했습니다. 나는이 주제에 내 구현 공유 stackoverflow.com/questions/55894636/...
Beyazid

답변:


99

Android 8.0 동작 변경 에 대한 Google 문서에서 :

시스템은 앱이 백그라운드에있는 동안에도 앱이 Context.startForegroundService ()를 호출 할 수 있도록합니다. 그러나 앱은 서비스가 생성 된 후 5 초 이내에 해당 서비스의 startForeground () 메서드를 호출해야합니다.

해결 방법 : 전화 startForeground()onCreate()대한 Service당신이 사용하는Context.startForegroundService()

참조 : 배경 실행 제한 안드로이드 8.0 (오레오)


19
onStartCommand메서드 에서이 작업을 수행 했지만 여전히이 오류가 발생합니다. 나는 startForegroundService(intent)내 전화 했다 MainActivity. 서비스가 너무 느리게 시작되었을 수 있습니다. 서비스가 즉시 시작될 것이라고 약속하기 전에 5 초 제한이 없어야한다고 생각합니다.
Kimi Chiu

29
5 초 주기로는 충분하지 않습니다.이 예외는 디버그 세션에서 매우 자주 발생합니다. 또한 릴리스 모드에서 때때로 발생해야한다고 생각합니다. 치명적인 예외가되어 앱을 중단시킵니다! Thread.setDefaultUncaughtExceptionHandler ()로 잡으려고했지만 거기에 가져 와서 안드로이드를 무시해도 응용 프로그램이 중지됩니다. 이것은이 예외가 handleMessage ()에서 트리거되고 메인 루프가 효과적으로 종료되므로 해결 방법을 찾기 때문입니다.
Southerton

브로드 캐스트 수신기에서 Context.startForegroundService () 메서드를 호출했습니다. 이 상황에서 어떻게 대처할까요? 방송 수신기에서 onCreate ()를 사용할 수 없기 때문에
Anand Savjani

6
@southerton 서비스를 닫고 다시 시작하면 전화하지 않고 갈 수 있기 때문에 onStartCommand()대신에 전화해야한다고 생각합니다 ...onCreateonStartCommand()onCreate
android developer

1
여기서 Google 팀의 응답을 확인할 수 있습니다. issuetracker.google.com/issues/76112072#comment56
Prags

82

나는 ContextCompat.startForegroundService(this, intent)서비스를 시작하기 위해 전화했다

서비스 중 onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}

47
나도. 그러나 여전히이 오류가 계속 발생합니다. 안드로이드가 onCreate5 초 안에 전화를 보장 할 수는 없습니다 . 그래서 그들은 우리가 규칙을 따르도록 강요하기 전에 그것을 재 설계해야합니다.
Kimi Chiu

5
onStartCommand ()에서 startForeground를 호출하고 때로는이 오류가 발생했습니다. 나는 그것을 onCreate로 옮겼고 이후로는 보지 못했습니다 (손가락을 엇갈리게).
Tyler

4
이렇게하면 "APP_NAME이 (가) 실행 중입니다. 정보를 닫거나 보려면을 (를) 누르십시오"라는 로컬 알림이 Oreo에 생성됩니다. 해당 알림 표시를 중지하는 방법은 무엇입니까?
Artist404

6
아니요, Android 팀은 이것이 의도 된 동작이라고 주장했습니다. 방금 앱을 다시 디자인했습니다. 이건 말도 안돼 이러한 "의도 된 동작"으로 인해 항상 앱을 다시 디자인해야합니다. 세번째입니다.
Kimi Chiu

12
이 문제의 주요 원인은 서비스가 포 그라운드로 승격되기 전에 서비스가 중지 되었기 때문입니다. 그러나 서비스가 파괴 된 후에도 주장은 멈추지 않았습니다. StopService을 호출 한 후 추가하여이를 재현 해 볼 수 있습니다 startForegroundService.
Kimi Chiu

55

이 문제가 발생하는 이유는 Android 프레임 워크가 5 초 이내에 서비스 시작을 보장 할 수는 없지만 프레임 워크가 서비스를 시작하려고 시도하지 않았는지 확인하지 않고 5 초 이내에 포 그라운드 알림을 엄격하게 제한해야하기 때문입니다. .

이것은 분명히 프레임 워크 문제이지만이 문제에 직면 한 모든 개발자가 최선을 다하는 것은 아닙니다.

  1. startForeground알림이 모두 있어야합니다 onCreateonStartCommand서비스가 이미 생성되고 어떻게 든 당신의 활동이 다시 시작하려고하는 경우, 때문에, onCreate호출되지 않습니다.

  2. 통지 ID는 0이 아니어야합니다. 그렇지 않으면 동일한 이유가 아니더라도 동일한 충돌이 발생합니다.

  3. stopSelf전에 호출해서는 안됩니다 startForeground.

위의 3 이상 으로이 문제는 약간 줄어들지 만 여전히 수정은 불가능합니다. 실제 수정 또는 해결 방법은 대상 SDK 버전을 25로 다운 그레이드하는 것입니다.

Google은 현재 진행중인 상황을 이해하지 못하고 이것이 잘못이라고 생각하지 않기 때문에 Android P가 여전히이 문제를 겪을 가능성이 높습니다. 자세한 내용은 36 번56 번 을 읽으십시오 .


9
왜 onCreate와 onStartCommand를 사용해야합니까? 그냥 onStartCommand에 넣을 수 있습니까?
rcell

stopSelf는 startForeground => 전에 또는 그 직후에 호출하면 안됩니다. "startForeground"를 취소하는 것 같습니다.
Frank

1
일부 테스트를 실행했으며 서비스가 이미 생성 된 경우 onCreate가 호출되지 않더라도 startForeground를 다시 호출 할 필요가 없습니다. 따라서 onCreate 메소드에서만 호출 할 수 있으며 onStartCommand에서는 호출 할 수 없습니다. 아마도 단일 서비스 인스턴스가 첫 번째 호출이 끝날 때까지 포 그라운드에있는 것으로 간주합니다.
Lxu

에 대한 초기 호출의 알림 ID로 0을 사용하고 startForeground있습니다. 1로 변경하면 나에게 문제가 해결됩니다 (희망적으로)
mr5

그래서 해결책은 무엇입니까?
IgorGanapolsky

35

이미 너무 많은 답변이 게시되었지만 사실은 startForegroundService를 앱 수준에서 수정할 수 없으므로 사용을 중지해야한다는 사실입니다. Context # startForegroundService ()가 호출 된 후 5 초 이내에 Service # startForeground () API를 사용하도록 Google에서 권장하는 것은 앱이 항상 할 수있는 것이 아닙니다.

Android는 많은 프로세스를 동시에 실행하며 Looper가 5 초 이내에 startForeground ()를 호출해야하는 대상 서비스를 호출한다는 보장은 없습니다. 대상 서비스가 5 초 내에 전화를받지 못하면 운이 나빠 사용자에게 ANR 상황이 발생합니다. 스택 추적에 다음과 같은 내용이 표시됩니다.

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

내가 이해하는 것처럼 Looper는 여기에서 대기열을 분석하고 "abuser"를 발견하여 간단히 죽였습니다. 시스템은 현재 행복하고 건강하지만 개발자와 사용자는 그렇지 않지만 Google은 시스템에 대한 책임을 제한하기 때문에 왜 후자에 관심을 가져야합니까? 분명히 그들은하지 않습니다. 그들이 더 나아질 수 있을까? 물론, 예를 들어 "응용 프로그램이 사용 중입니다"대화 상자를 통해 사용자에게 앱 대기 또는 종료에 대한 결정을 요청했지만 왜 귀찮게하지 않습니까? 가장 중요한 것은 이제 시스템이 건강하다는 것입니다.

내 관찰에 따르면, 이것은 비교적 드물게 발생하며, 1K 사용자의 경우 한 달에 약 1 건의 충돌이 발생합니다. 재현 할 수 없으며 재현하더라도 영구적으로 고칠 수있는 방법이 없습니다.

이 스레드에서 "start"대신 "bind"를 사용하도록 제안한 다음 서비스가 준비되면 onServiceConnected를 처리하지만 다시 startForegroundService 호출을 전혀 사용하지 않음을 의미합니다.

Google 측의 옳고 정직한 행동은 모든 사람들에게 startForegourndServcie에 결함이있어서 사용해서는 안된다고 말하는 것입니다.

문제는 여전히 남아 있습니다 : 대신 무엇을 사용해야합니까? 다행히도 현재 전경 서비스를위한 더 나은 대안 인 JobScheduler와 JobService가 있습니다. 그 때문에 더 나은 옵션입니다.

작업이 실행되는 동안 시스템은 앱 대신 깨우기 잠금을 유지합니다. 따라서 작업 기간 동안 장치가 깨어있는 상태를 유지하기 위해 어떤 조치도 취할 필요가 없습니다.

즉, 더 이상 웨이크 록 처리에 신경 쓸 필요가 없으므로 포 그라운드 서비스와 다르지 않습니다. 구현 관점에서 볼 때 JobScheduler는 귀하의 서비스가 아니며 시스템의 시스템 일 것입니다. 아마 대기열을 올바르게 처리하고 Google은 자체 자식을 종료하지 않습니다. :)

Samsung은 Samsung Accessory Protocol (SAP)에서 startForegroundService에서 JobScheduler 및 JobService로 전환했습니다. 스마트 워치와 같은 장치가 전화와 같은 호스트와 대화해야 할 때 매우 유용합니다. 여기서 작업은 앱의 주요 스레드를 통해 사용자와 상호 작용해야합니다. 스케줄러가 작업을 기본 스레드에 게시하므로 가능합니다. 작업이 기본 스레드에서 실행 중이고 모든 무거운 물건을 다른 스레드 및 비동기 작업으로 오프로드한다는 것을 기억해야합니다.

이 서비스는 애플리케이션의 메인 스레드에서 실행되는 핸들러에서 각 수신 작업을 실행합니다. 즉, 실행 로직을 선택한 다른 스레드 / 핸들러 / AsyncTask로 오프로드해야합니다.

JobScheduler / JobService로 전환하는 유일한 함정은 이전 코드를 리팩터링해야한다는 것입니다. 지난 이틀 동안 새로운 삼성의 SAP 구현을 사용하기 위해 노력했습니다. 충돌 보고서를보고 충돌이 다시 발생하는지 알려 드리겠습니다. 이론적으로는 그렇게해서는 안되지만, 우리가 알지 못하는 세부 사항이 항상 있습니다.

업데이트 Play 스토어에서 더 이상 충돌이보고되지 않았습니다. 이는 JobScheduler / JobService에 그러한 문제가 없으며이 모델로 전환하는 것이 startForegroundService 문제를 한 번 그리고 영원히 없애는 올바른 접근법입니다. Google / Android가이 내용을 읽고 결국에는 모든 사람을위한 공식 지침에 대해 언급 / 권고 / 제공하기를 바랍니다.

업데이트 2

SAP를 사용 하고 SAP V2가 JobService 설명을 어떻게 활용하는지 묻는 사람들 은 다음과 같습니다.

사용자 지정 코드에서 SAP를 초기화해야합니다 (Kotlin).

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

이제 내부에서 무슨 일이 일어나고 있는지 삼성 코드를 디 컴파일해야합니다. SAAgentV2에서 requestAgent 구현과 다음 행을 살펴보십시오.

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

지금 SAAdapter 클래스로 이동하여 다음 호출을 사용하여 작업을 스케줄하는 onServiceConnectionRequested 함수를 찾으십시오.

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService는 Android에 의한 JobService의 구현 일 뿐이며 작업 스케줄링을 수행하는 것입니다.

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

보시다시피, 마지막 줄은 Android의 JobScheduler를 사용하여이 시스템 서비스를 받고 작업을 예약합니다.

requestAgent 호출에서 우리는 중요한 이벤트가 발생할 때 제어를받는 콜백 함수 인 mAgentCallback을 전달했습니다. 이것은 내 앱에서 콜백이 정의되는 방식입니다.

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs는 삼성 스마트 워치에서 오는 모든 요청을 처리하기 위해 구현 한 클래스입니다. 그것은 전체 코드가 아니며 골격 만입니다.

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

보시다시피 MessageJobs에는 MessageSocket 클래스가 필요하며 장치에서 오는 모든 메시지를 구현하고 처리해야합니다.

결론적으로, 그것은 간단하지 않으며 내부 및 코딩에 대한 파기를 필요로하지만 작동하며 가장 중요하게 충돌하지 않습니다.


좋은 대답이지만 큰 문제 JobIntentService가 있으며, IntentService아래 Oreo 로 즉시 실행 되지만 Oreo 이상에서 작업을 예약하므로 JobIntentService즉시 시작되지 않습니다. 더 많은 정보
CopsOnRoad

1
@CopsOnRoad 그것은 매우 유용하며 제 경우에는 즉시 시작됩니다. 필자가 작성한 것처럼 전화와 스마트 워치 간의 실시간 상호 작용에 사용됩니다. 결코, 사용자는이 둘 사이에 데이터를 보내기 위해 15 분을 기다릴 수 없습니다. 매우 잘 작동하며 충돌하지 않습니다.
Oleg Gryb

1
시원하게 들리면 샘플 코드를 공유 할 수 있다면 답변을 가치있게 만들 것입니다 .Android Job...를 처음 접했고 시작 하는 데 시간 이 걸리며의 고급 버전입니다 AlarmManager.
CopsOnRoad

2
아마 나는 시간이 허락는 언제 : 나는 사용자 고유의 코드 및 디 컴파일 일부 독점 삼성 libs와에서 그것 - 몇 해제해야합니다
올렉 Gryb

1
@batmaci-JobService는 SAP에서 내부적으로 사용합니다. 자세한 내용은 OP의 업데이트 2를 참조하십시오.
Oleg Gryb

32

전화 Context.startForegroundService(...)를 걸고 전화 Context.stopService(...)하기 전에 전화 를 걸면 앱이 중단됩니다 Service.startForeground(...).

여기 명확한 생식을 ForegroundServiceAPI26

Google 이슈 트래커 에서 버그를 열었습니다.

이것에 대한 몇 가지 버그가 열리고 닫히지 않습니다.

분명한 재현 단계를 가진 광산이 잘릴 것입니다.

Google 팀에서 제공 한 정보

구글 이슈 트래커 코멘트 36

이것은 프레임 워크 버그가 아닙니다. 의도적입니다. 앱에서 서비스 인스턴스를 시작하는 경우 startForegroundService(), 그것은 해야한다 전경 상태로 해당 서비스 인스턴스를 전환하고 알림을 보여줍니다. 서비스 인스턴스가 startForeground()호출 되기 전에 중지 된 경우 해당 약속이 이행되지 않습니다. 이는 앱의 버그입니다.

다시 # 31 , 다른 앱이 직접 시작할 수있는 서비스를 게시하는 것은 근본적으로 안전하지 않습니다. 해당 서비스의 모든 시작 동작을 요구하는 것으로 처리하면 약간 완화 할 수 startForeground()있지만 분명히 마음에 들지 않을 수도 있습니다.

구글 이슈 트래커 코멘트 56

여기에 동일한 결과를 초래하는 몇 가지 시나리오가 있습니다.

명백한 의미 론적 문제는 단순히 무언가를 시작하는 것이 오류 startForegroundService()이지만 실제로는 그것을 통해 전경으로 전환하는 것을 무시합니다.startForeground() 것은 의미 론적 문제입니다. 의도적으로 앱 버그로 취급됩니다. 포 그라운드로 전환하기 전에 서비스를 중지하면 앱 오류가 발생합니다. 이것이 OP의 핵심이며이 문제가 "의도 한대로 작동"으로 표시되어있는 이유입니다.

그러나이 문제의 가짜 탐지에 대한 질문도 있습니다. 그의는 있다 그것의 존재가이 특정 버그 추적기 문제는 별도로 추적하지만, 진짜 문제로 치료를 받고. 우리는 불만에 귀를 기울이지 않습니다.


2
내가 본 최고의 업데이트는 issuetracker.google.com/issues/76112072#comment56 입니다. Context.startForegroundService에 대한 문제가되는 호출을 완전히 피하는 Context.bindService를 사용하도록 코드를 다시 작성했습니다. github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/
swooby

그렇습니다. 내가 쓴 것처럼 bind는 작동해야하지만, JobServive로 이미 전환했으며이 역시 너무 깨지지 않는 한 아무 것도 바꾸지 않을 것입니다. ')
Oleg Gryb

20

나는 며칠 동안 이것에 대해 연구하고 해결책을 얻었습니다. 이제 Android O에서 아래와 같이 배경 제한을 설정할 수 있습니다

서비스 클래스를 호출하는 서비스

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

서비스 클래스는

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}

3
최소 수천 명의 사용자가있는 일부 앱에서 사용해 보셨습니까? 일부 사용자는 여전히 충돌 문제가 있습니다.
Alex

아니요 아니요 정확한 코드를 사용하고 있으며 클라이언트가 충돌하는 것을 보지 못했습니다.
Ahmad Arslan

얼마나 많은 사용자가 있습니까? 매일 10k 명의 사용자가있는 응용 프로그램에서 ~ 10-30 +가 충돌하는 것을 보았습니다 (오류가 있음). 물론 targetSdkVersion이 27 인 경우에는 Android 8.0 및 Android 8.1을 사용하는 사용자에게만 발생합니다. targetSdkVersion을 25로 설정 한 직후에 모든 문제가 0으로 중단됩니다.
Alex

단지 2200 사용자 : - /하지만 어떤 충돌하지 않았다
아마드 아르 슬란

1
@Jeeva, 실제로는 아닙니다. 기압, 내가 가장 좋은 방법은 실험의 많은 후 .... 희망이가 완료됩니다 좋아 compileSdkVersion 27. 외모와 targetSdkVersion을 25를 사용 developer.android.com/topic/libraries/architecture/... 2018년 8월 전에 때문에 안드로이드 - developers.googleblog .com / 2017 / 12 /…
Alex

16

장치가 깨어있을 때 비교적 자주 업데이트 되는 위젯이 있으며 며칠 만에 수천 건의 충돌이 발생했습니다.

이슈 트리거

장치에 많은 부하가 있다고 생각하지 않을 때 Pixel 3 XL에서도 문제를 발견했습니다. 그리고 모든 코드 경로는로 다뤄졌습니다 startForeground(). 그러나 많은 경우에 제 서비스가 업무를 빠르게 처리한다는 것을 깨달았습니다.내 앱의 트리거는 시스템이 실제로 알림을 표시하기 전에 서비스가 완료되었다고 생각합니다.

해결 방법 / 솔루션

모든 충돌을 제거 할 수있었습니다. 내가 한 일은에 대한 통화를 삭제하는 것이 었습니다 stopSelf(). (알림이 표시 될 때까지 중지를 지연시키는 것에 대해 생각하고 있었지만 알림이 필요하지 않은 경우 사용자에게 알림이 표시되는 것을 원하지 않습니다.) 서비스가 1 분 동안 또는 유휴 상태 인 경우 예외를 발생시키지 않고 정상적으로 파괴합니다.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}

11

내가 이것에 너무 많은 시간을 낭비하면서 그냥 머리. startForeground(..)에서 첫 번째 로 전화했지만이 예외가 계속 발생 했습니다 onCreate(..). 결국 나는을 사용하여 문제가 발생한 것을 발견했습니다 NOTIFICATION_ID = 0. 다른 값을 사용하면이 문제가 해결됩니다.


내 경우에는 내가 ID 1을 사용했는데, 덕분에 문제가 해결되었다
아민 Pinjari

4
ID 101을 사용하고 있는데 가끔이 오류가 발생합니다.
CopsOnRoad

9

이 문제에 대한 해결 방법이 있습니다. 필자는 자체 응용 프로그램 (300K + DAU) 에서이 수정 프로그램을 확인하여 이러한 종류의 충돌을 최소 95 % 줄일 수는 있지만 여전히이 문제를 100 % 피할 수는 없습니다.

이 문제는 Google에서 문서화 된대로 서비스가 시작된 직후 startForeground ()를 호출 할 때도 발생합니다. 많은 시나리오에서 서비스 작성 및 초기화 프로세스에 이미 5 초 이상의 비용이 들기 때문에 startForeground () 메소드를 언제 어디서 호출하더라도이 충돌을 피할 수 없습니다.

내 해결책은 startForegroundService () 메소드 후 5 초 이내에 startForeground ()가 서비스를 생성하고 초기화하는 시간에 관계없이 실행되도록하는 것입니다. 자세한 솔루션은 다음과 같습니다.

  1. 처음에는 startForegroundService를 사용하지 말고 auto_create 플래그와 함께 bindService ()를 사용하십시오. 서비스 초기화를 기다립니다. 다음은 코드입니다. 샘플 서비스는 MusicService입니다.

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. 다음은 MusicBinder 구현입니다.

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. 가장 중요한 부분 인 MusicService 구현, forceForeground () 메소드는 startForegroundService () 직후에 startForeground () 메소드가 호출되도록합니다.

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. 앱을 열지 않고 위젯에서 포 그라운드 서비스를 시작하려는 경우 (위젯 버튼 클릭)와 같이 보류중인 의도로 1 단계 코드 스 니펫을 실행하려는 경우 브로드 캐스트 리시버에서 코드 스 니펫을 랩핑 할 수 있습니다. 서비스 시작 명령 대신 브로드 캐스트 이벤트를 시작하십시오.

그게 다야 도움이 되길 바랍니다. 행운을 빕니다.



7

여기를 방문하는 모든 사람들이 똑같은 고통을 겪고 있기 때문에 아무도 전에 시도하지 않은 솔루션을 공유하고 싶습니다 (어쨌든이 질문에서). 중단 된 중단 점에서도 작동하는지 확인할 수 있습니다.이 방법을 확인 하는지 확인할 수 있습니다.

문제는 Service.startForeground(id, notification)서비스 자체에서 전화 하는 것입니다. 안타깝게도 Android Framework 는 5 초 Service.startForeground(id, notification)이내에 전화를 보장하지 않지만 Service.onCreate()어쨌든 예외가 발생 하므로이 방법을 생각해 냈습니다.

  1. 호출하기 전에 서비스에서 바인더를 사용하여 서비스를 컨텍스트에 바인딩 Context.startForegroundService()
  2. 바인드에 성공 Context.startForegroundService() 하면 서비스 연결에서 호출하고 즉시 서비스 연결 내부에서 호출 Service.startForeground() 하십시오.
  3. 중요 사항 : 경우에 따라 호출에서 예외가 발생할 수 있으므로 try-catchContext.bindService() 내 에서 메소드를 호출하십시오 .이 경우 직접 호출에 의존해야하며 실패하지 않기를 바랍니다. 예를 들어 브로드 캐스트 리시버 컨텍스트가 될 수 있지만이 경우 응용 프로그램 컨텍스트를 가져 오면 예외가 발생하지 않지만 컨텍스트를 직접 사용하면됩니다.Context.startForegroundService()

서비스를 바인딩 한 후 "startForeground"호출을 트리거하기 전에 중단 점을 기다리는 경우에도 작동합니다. 3-4 초 동안 대기해도 예외는 발생하지 않지만 5 초 후에는 예외가 발생합니다. (기기가 5 초 안에 두 줄의 코드를 실행할 수 없다면 휴지통에 버릴 차례입니다.)

따라서 서비스 연결을 만드는 것으로 시작하십시오.

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

서비스 내에서 서비스 인스턴스를 리턴하는 바인더를 작성하십시오.

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

그런 다음 해당 컨텍스트 에서 서비스를 바인딩하십시오 . 성공하면 ServiceConnection.onServiceConnected()사용중인 서비스 연결에서 메소드를 호출 합니다. 그런 다음 위에 표시된 코드에서 논리를 처리하십시오. 예제 코드는 다음과 같습니다.

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

그것은 내가 개발 한 응용 프로그램에서 작동하는 것처럼 보이므로 시도해도 효과가 있습니다.


5

Android O API 문제 26

서비스를 즉시 중지하면 (실제로 서비스가 실제로 실행되지 않고 (말씀 / 이해) ANR 간격에 도달 한 경우 stopSelf 전에 startForeground를 호출해야합니다.

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

이 접근법을 시도했지만 여전히 오류가 발생합니다.

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

오류가 해결 될 때까지 이것을 사용하고 있습니다.

mContext.startService(playIntent);

3
더 큰 것이 아니라 Util.SDK_INT> = 26이어야 함
Andris

4

나는 같은 문제에 직면하고 있으며 시간을 보낸 후에 솔루션을 찾았습니다. 아래 코드를 시도해보십시오. 사용하는 Service경우이 코드를 onCreate Intent Service에 넣으십시오. 다른 경우를 사용 하여이 코드를 onHandleIntent에 넣으십시오.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

4

나는이 문제를 연구 해 왔으며 이것이 지금까지 내가 발견 한 것입니다. 다음과 유사한 코드가있는 경우이 충돌이 발생할 수 있습니다.

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

다음 코드 블록에서 예외가 발생합니다.

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

이 방법은 이전 실행 onCreate()MyForegroundService기본 스레드 핸들러에 대한 서비스의 안드로이드 일정 때문에 생성하지만 bringDownServiceLockedA의 호출 BinderThread경쟁 조건입니다 느릅 나무. MyForegroundService전화를 걸 기회 가 없었 음을 의미합니다startForeground 충돌의 원인이되는합니다.

이 문제를 해결하려면 bringDownServiceLocked이전 onCreate()에 호출되지 않았 는지 확인해야합니다 MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

브로드 캐스트를 사용하여 우리는 방송이 분실되지 않는지 확인 stopReceiver즉시이 등록 된 같은 의도 정지를 수신 onCreate()MyForegroundService. 지금까지 우리는 이미 전화했다startForeground(...) . 또한 다음 번에 stopReceiver에 알림이 표시되지 않도록 해당 고정 브로드 캐스트를 제거해야합니다.

주의 방법이 있음을 sendStickyBroadcast더 이상 사용되지 않으며 나는이 문제를 해결하기 위해 일시적인 해결 방법으로 그것을 사용할 수 있습니다.


서비스를 중지하려면 context.stopService (serviceIntent) 대신 호출해야합니다.
makovkastar

4

너무 많은 답변이지만 내 경우에는 효과가 없었습니다.

이런 식으로 서비스를 시작했습니다.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

그리고 onStartCommand의 서비스에서

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

NOTIFICATION_ID를 0이 아닌 값으로 설정하는 것을 잊지 마십시오

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

그래서 모든 것이 완벽했지만 8.1에서 여전히 충돌하므로 원인은 다음과 같습니다.

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

알림을 제거하여 중지 포 그라운드를 호출했지만 알림 제거 서비스가 백그라운드가되고 백그라운드 서비스가 백그라운드에서 안드로이드 O에서 실행될 수 없습니다. 푸시 수신 후 시작되었습니다.

마법의 단어는

   stopSelf();

지금까지 서비스가 중단되는 이유는 위의 모든 단계를 수행하고 즐기십시오.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } 다른 경우에는이게 무엇입니까? 두 경우 모두 동일한 작업을 수행합니다. stopForeground (true);
user3135923 2016 년

나는 당신이 stopSelf (); stopForeground (true) 대신 else 블록 내부;
Justin Stanley

1
예, @JustinStanley는 stopSelf ()를 사용합니다.
모래

startForeground호출 해서는 안 onCreate됩니까?
Aba

Notification 클래스가 아닌 NotificationManager를 사용하고 있습니다. 이것을 어떻게 구현할 수 있습니까?
Prajwal W

3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

startService (Intent)와 비슷하지만 서비스가 시작되면 startForeground (int, android.app.Notification)를 호출 할 것이라는 암시 적 약속이 있습니다. 이를 위해 ANR 간격과 비슷한 시간이 서비스에 제공됩니다. 그렇지 않으면 시스템에서 서비스를 자동으로 중지하고 앱 ANR을 선언합니다.

일반적인 startService (Intent)와 달리이 메소드는 서비스를 호스팅하는 앱이 포 그라운드 상태인지 여부에 관계없이 언제든지 사용할 수 있습니다.

Service.startForeground(int, android.app.Notification)onCreate () 에서 on 을 호출해야 호출 할 수 있습니다 Context.startService(Intent).Service.startForeground(int, android.app.Notification) 자신을.

Context.startForegroundService()추가 Service.startForeground(int, android.app.Notification)하기 전에 워치 독을 추가 한 것으로 보입니다 .


3

onCreate () 메서드 내에서 StartForgroundServices를 호출하지 마십시오 . 작업자 스레드를 만든 후 onStartCommand () 에서 StartForground 서비스를 호출해야합니다. 그렇지 않으면 ANR이 항상 발생 하므로 onStartCommand () 의 메인 스레드에 복잡한 로그인을 작성하지 마십시오 ;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

편집 : 주의! startForeground 함수는 첫 번째 인수로 0을 사용할 수 없으며 예외가 발생합니다! 이 예제에는 잘못된 함수 호출이 포함되어 있습니다. 0은 0이거나 Max (Int32)보다 클 수있는 자신의 const로 변경하십시오.


2

startForegroundin 호출 한 후에도 호출 하기 직전에 Service호출하면 일부 장치에서 충돌이 발생합니다 . 따라서 추가 플래그로 서비스를 시작 하여이 문제를 해결했습니다.stopServiceonCreate

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

onStartCommand에 검사가 추가되어 중지 시작되었는지 확인했습니다.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PS 서비스가 실행되고 있지 않으면 먼저 서비스를 시작합니다. 이는 오버 헤드입니다.


2

target sdk 28 이상을 사용하는 경우 android 9 장치에 대한 권한을 벨로우즈로 추가해야합니다. 그렇지 않으면 예외가 항상 발생합니다.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

1

함수 PendingIntent를 호출하기 전에 null을 확인 하거나 확인 하지 않습니다 context.startForegroundService(service_intent).

이것은 나를 위해 작동

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

이것은 실제로 null 인 경우 startForegroundService 대신 startService로 서비스를 시작합니다. if 문은 중첩되어야하며, 그렇지 않으면 앱이 특정 시점에서 이후 버전에서 서비스를 사용하려고 할 때 충돌이 발생합니다.
a54studio

1

Service 또는 IntentService가 작성된 직후 startForeground 메소드를 호출하십시오. 이처럼 :

import android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}

치명적인 예외 : 기본 android.app.RemoteServiceException : startForeground에 대한 잘못된 알림 : java.lang.RuntimeException : 서비스 알림에 대한 잘못된 채널 : 알림 (channel = null pri = 0 contentView = null vibrate = null sound = null 기본값 = x0 flags = 0x40 android.os.Looper.loop (Looper.)의 android.os.Handler.dispatchMessage (Handler.java:106)의 android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821)의 color = 0x00000000 vis = PRIVATE) android.app.ActivityThread.main (ActivityThread.java:6626)의 java : 164)
Gerry

1

좋아, 내가 이것에 주목 한 것은 다른 사람들에게도 도움이 될 수 있습니다. 이것은 내가보고있는 발생 문제를 해결하는 방법을 알아낼 수 있는지 테스트하기위한 것입니다. 간단히하기 위해 발표자에서 이것을 호출하는 메소드가 있다고 가정 해 봅시다.

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

이것은 같은 오류로 충돌합니다. 방법이 완료 될 때까지 서비스가 시작되지 않으므로 아니오onCreate() 서비스에 .

따라서 메인 스레드에서 UI를 업데이트하더라도 해당 메소드를 보유한 후에도 해당 메소드를 유지할 수있는 항목이 있으면 정시에 시작되지 않고 두려운 전경 오류가 발생합니다. 내 경우에는 대기열에로드하고 각각을 호출 startForegroundService했지만 일부 로직은 백그라운드에서 각각 관련되었습니다. 따라서 로직이 연속해서 호출 된 이후 해당 메소드를 완료하는 데 너무 오래 걸렸다면 충돌 시간이 발생합니다. 오래된startService 방금 그것을 무시하고 길을 갔고 매번 전화 했으므로 다음 라운드는 끝날 것입니다.

백그라운드에서 스레드에서 서비스를 호출하면 시작시 완전히 바인딩되어 즉시 실행되지 않아 실험을 시작했을 수 있습니다. 이것이 즉시 시작되지 않더라도 충돌하지는 않습니다.

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

나는 이것이 메인 스레드가 적시에 그것을 처리 할 수있을 때까지 기다려야한다고 생각하지만 왜 충돌하지 않는지 알지 않을 것입니다. 나는 그것을 메인 스레드에 묶는 것이 이상적이지 않다는 것을 알고 있지만, 내 사용법이 백그라운드에서 호출하기 때문에 충돌이 아니라 완료 될 때까지 기다릴 지 걱정하지 않습니다.


백그라운드에서 또는 활동중인 서비스를 시작하고 있습니까?
Mateen Chaudhry

배경. 문제는 안드로이드가 할당 된 시간 내에 서비스가 시작될 것이라고 보장 할 수 없다는 것입니다. 하나는 안드로이드가 아니라 원하는 경우 무시하기 위해 오류를 던질 것이라고 생각할 것입니다. 치명적이어야합니다. 이것은 우리 앱에서 도움이되었지만 완전히 수정하지는 못했습니다.
a54studio

1

@ humazed 답변에 코드를 추가하고 있습니다. 따라서 초기 알림이 없습니다. 해결 방법 일 수도 있지만 나에게 효과적입니다.

@Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

작은 아이콘에 transparentColor를 추가하고 알림에 색상을 추가하고 있습니다. 작동합니다.


0

startService(intent)대신 서비스를 시작하고 바로 Context.startForeground()호출 하는 문제를 해결했습니다 . 또한 부팅시 서비스를 시작하면 부팅 브로드 캐스트에서 서비스를 시작하는 활동을 시작할 수 있습니다. 영구적 인 솔루션은 아니지만 작동합니다.startForegound()super.OnCreate()


일부 Xiaomi 장치에서는 백그라운드에서 활동을 시작할 수 없습니다
Mateen Chaudhry

0

데이터 업데이트 onStartCommand(...)

onBind (...)

onBind(...)시작하는 더 나은 라이프 사이클 이벤트 startForeground대를 onCreate(...)하기 때문에 onBind(...)에서 패스 Intent에서 중요한 데이터가 포함될 수 Bundle를 초기화 할 필요가 Service. 그러나 처음으로 생성되거나 이후에 onStartCommand(...)호출 될 때 호출 될 필요는 없습니다 Service.

onStartCommand (...)

startForeground이미 작성된 후에 onStartCommand(...)업데이트하려면 in 이 중요 Service합니다.

ContextCompat.startForegroundService(...)A는 후라고 Service만들어졌습니다 onBind(...)onCreate(...)호출되지 않습니다. 따라서 업데이트 된 데이터는 에서 데이터를 업데이트 onStartCommand(...)하기 Intent Bundle위해 를 통해 전달 될 수 있습니다 Service.

견본

이 패턴을 사용 PlayerNotificationManager하여 Coinverse cryptocurrency 뉴스 앱 을 구현하고 있습니다.

활동 / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

-1

서비스

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

시험 장치

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

결과

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.