Context.startForegroundService ()가 Service.startForeground ()를 호출하지 않았습니다. OS에 클래스. Service백그라운드에서 사용할 계획 입니다. 안드로이드 문서의 상태가

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

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

안드로이드 문서의 상태가

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

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

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

이게 뭐가 문제 야?



답변

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

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

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

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


답변

나는 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);
        }
}


답변

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

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

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

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

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

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

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


답변

이미 너무 많은 답변이 게시되었지만 사실은 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 클래스가 필요하며 장치에서 오는 모든 메시지를 구현하고 처리해야합니다.

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


답변

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

여기 명확한 생식을 ForegroundServiceAPI26

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

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

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

Google 팀에서 제공 한 정보

구글 이슈 트래커 코멘트 36

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

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

구글 이슈 트래커 코멘트 56

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

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

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


답변

나는 며칠 동안 이것에 대해 연구하고 해결책을 얻었습니다. 이제 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
    }
}


답변

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

이슈 트리거

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

해결 방법 / 솔루션

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

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