Context.startForegroundService ()가 Service.startForeground ()를 호출하지 않았습니다.
내가 사용하고 Service
안드로이드 O OS에 클래스.
Service
백그라운드에서 사용할 계획 입니다.
startService()
사용하도록 Android 권장 사항에 설명되어 있습니다 startForegroundService()
.
를 사용 startForegroundService()
하면 Service
던지기
Context.startForegroundService ()가 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 초 이내에 포 그라운드 알림을 엄격하게 제한해야하기 때문입니다. .
이것은 분명히 프레임 워크 문제이지만이 문제에 직면 한 모든 개발자가 최선을 다하는 것은 아닙니다.
startForeground
알림이 모두 있어야합니다onCreate
및onStartCommand
서비스가 이미 생성되고 어떻게 든 당신의 활동이 다시 시작하려고하는 경우, 때문에,onCreate
호출되지 않습니다.통지 ID는 0이 아니어야합니다. 그렇지 않으면 동일한 이유가 아니더라도 동일한 충돌이 발생합니다.
stopSelf
전에 호출해서는 안됩니다startForeground
.
위의 3 이상 으로이 문제는 약간 줄어들지 만 여전히 수정은 불가능합니다. 실제 수정 또는 해결 방법은 대상 SDK 버전을 25로 다운 그레이드하는 것입니다.
Google은 현재 진행중인 상황을 이해하지 못하고 이것이 잘못이라고 믿지 않기 때문에 Android P가 여전히이 문제를 겪을 가능성이 높습니다. 자세한 내용은 # 36 및 # 56 을 읽으십시오 .
전화 Context.startForegroundService(...)
를 걸고 전화 Context.stopService(...)
하기 전에 전화 를 걸면 앱이 중단됩니다 Service.startForeground(...)
.
여기 명확한 생식을 ForegroundServiceAPI26
Google 이슈 트래커 에서 버그를 열었습니다.
이것에 대한 몇 가지 버그가 열리고 닫히지 않습니다.
분명한 재현 단계를 가진 광산이 잘릴 것입니다.
Google 팀에서 제공 한 정보
이것은 프레임 워크 버그가 아닙니다. 의도적입니다. 앱에서 서비스 인스턴스를 시작하는 경우 startForegroundService()
, 그것은 해야한다 전경 상태로 해당 서비스 인스턴스를 전환하고 알림을 보여줍니다. 서비스 인스턴스가 startForeground()
호출 되기 전에 중지 된 경우 해당 약속이 이행되지 않습니다. 이는 앱의 버그입니다.
다시 # 31 , 다른 앱이 직접 시작할 수있는 서비스를 게시하는 것은 근본적으로 안전하지 않습니다. 해당 서비스의 모든 시작 동작을 요구하는 것으로 처리하면 약간 완화 할 수 startForeground()
있지만 분명히 마음에 들지 않을 수도 있습니다.
여기에 동일한 결과를 초래하는 몇 가지 시나리오가 있습니다.
명백한 의미 론적 문제는 단순히 무언가를 시작하는 것이 오류 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();
}
내가 이것에 너무 많은 시간을 낭비하면서 그냥 머리. startForeground(..)
에서 첫 번째 로 전화했지만이 예외가 계속 발생 했습니다 onCreate(..)
. 결국 나는을 사용하여 문제가 발생한 것을 발견했습니다 NOTIFICATION_ID = 0
. 다른 값을 사용하면이 문제가 해결됩니다.
이미 답변이 너무 많이 게시되었지만 사실은 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에 결함이있어서 사용해서는 안된다고 말하는 것입니다.
qs는 여전히 남아 있습니다 : 대신 무엇을 사용해야합니까? 다행히도 현재 전경 서비스를위한 더 나은 대안 인 JobScheduler와 JobService가 있습니다. 그 때문에 더 나은 옵션입니다.
작업이 실행되는 동안 시스템은 앱 대신 깨우기 잠금을 유지합니다. 따라서 작업 기간 동안 장치가 깨어있는 상태를 유지하기 위해 어떤 조치도 취할 필요가 없습니다.
즉, 더 이상 웨이크 록 처리에 신경 쓸 필요가 없으므로 포 그라운드 서비스와 다르지 않습니다. 구현에서 PoV JobScheduler는 귀하의 서비스가 아니며 시스템의 것입니다. 아마도 대기열을 올바르게 처리하고 Google은 자체 자식을 종료하지 않습니다. :)
Samsung은 Samsung Accessory Protocol (SAP)에서 startForegroundService에서 JobScheduler 및 JobService로 전환했습니다. 스마트 워치와 같은 장치가 전화와 같은 호스트와 대화해야 할 때 매우 유용합니다. 여기서 작업은 앱의 주요 스레드를 통해 사용자와 상호 작용해야합니다. 스케줄러가 작업을 기본 스레드에 게시하므로 가능합니다. 작업이 기본 스레드에서 실행 중이고 모든 무거운 물건을 다른 스레드 및 비동기 작업으로 오프로드한다는 것을 기억해야합니다.
이 서비스는 애플리케이션의 메인 스레드에서 실행되는 핸들러에서 각 수신 작업을 실행합니다. 즉, 실행 로직을 선택한 다른 스레드 / 핸들러 / AsyncTask로 오프로드해야합니다.
JobScheduler / JobService로 전환하는 유일한 함정은 이전 코드를 리팩터링해야한다는 것입니다. 지난 이틀 동안 새로운 삼성의 SAP 구현을 사용하기 위해 노력했습니다. 충돌 보고서를보고 충돌이 다시 발생하는지 알려 드리겠습니다. 이론적으로는 그렇게해서는 안되지만, 우리가 알지 못하는 세부 사항이 항상 있습니다.
업데이트 Play 스토어에서 더 이상 충돌이보고되지 않았습니다. 이는 JobScheduler / JobService에 이러한 문제가 없으며이 모델로 전환하는 것이 startForegroundService 문제를 한 번 그리고 영원히 없애는 올바른 방법입니다. Google / Android가이 내용을 읽고 결국에는 모든 사람을위한 공식 지침에 대해 언급 / 권고 / 제공하기를 바랍니다.
이 문제에 대한 해결 방법이 있습니다. 필자는 자체 응용 프로그램 (300K + DAU) 에서이 수정 프로그램을 확인하여 이러한 종류의 충돌을 최소 95 % 줄일 수는 있지만 여전히이 문제를 100 % 피할 수는 없습니다.
이 문제는 Google에서 문서화 된대로 서비스가 시작된 직후 startForeground ()를 호출 할 때도 발생합니다. 많은 시나리오에서 서비스 작성 및 초기화 프로세스 비용이 이미 5 초 이상이기 때문에 startForeground () 메소드를 언제 어디서 호출하더라도이 충돌을 피할 수 없습니다.
내 솔루션은 startForegroundService () 메소드 후 5 초 이내에 startForeground ()가 서비스를 작성하고 초기화하는 시간에 관계없이 실행되도록하는 것입니다. 자세한 솔루션은 다음과 같습니다.
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);
다음은 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(); } }
가장 중요한 부분 인 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); } } }
앱을 열지 않고 위젯에서 포 그라운드 서비스를 시작하려는 경우 (위젯 버튼 클릭)와 같이 보류중인 의도로 1 단계 코드 스 니펫을 실행하려는 경우 브로드 캐스트 리시버에서 코드 스 니펫을 랩핑 할 수 있습니다. 서비스 시작 명령 대신 브로드 캐스트 이벤트를 시작하십시오.
그게 다야 도움이 되길 바랍니다. 행운을 빕니다.
이 오류는 id 가 0으로 설정된 상태 에서 Service.startForeground (int id, Notification notification) 가 호출 될 때 Android 8 이상에서도 발생합니다 .
id int : NotificationManager.notify (int, Notification)에 따른이 알림의 식별자입니다. 0이 아니어야합니다 .
너무 많은 답변이지만 내 경우에는 효과가 없었습니다.
이런 식으로 서비스를 시작했습니다.
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"; 개인 정적 최종 정수 NOTIFICATION_ID = 555;
그래서 모든 것이 완벽했지만 8.1에서 여전히 충돌하므로 원인은 다음과 같습니다.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { stopForeground(true); } else { stopForeground(true); }
알림을 제거하고 중지 포 그라운드를 호출했지만 알림 제거 서비스가 백그라운드가되고 백그라운드 서비스가 백그라운드에서 안드로이드 O에서 실행될 수 없습니다. 푸시 수신 후 시작되었습니다.
마법의 단어는
stopSelf();
지금까지 서비스가 중단되는 이유는 위의 모든 단계를 수행하고 즐기십시오.
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)
하기 전에 워치 독을 추가 한 것으로 보입니다 .
나는 같은 문제에 직면하고 있으며 시간을 보낸 후 솔루션을 찾았습니다. 아래 코드를 시도해보십시오. 사용하는 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);
}
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);
나는이 문제를 연구 해 왔으며 이것이 지금까지 내가 발견 한 것입니다. 다음과 유사한 코드가있는 경우이 충돌이 발생할 수 있습니다.
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
기본 스레드 핸들러에 대한 서비스의 안드로이드 일정 때문에 생성하지만 bringDownServiceLocked
A의 호출 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
더 이상 사용되지 않으며 나는이 문제를 해결하기 위해 일시적인 해결 방법으로 그것을 사용할 수 있습니다.
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로 변경하십시오.
@ 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를 추가하고 알림에 색상을 추가하고 있습니다. 작동합니다.
여기를 방문하는 모든 사람들이 똑같은 고통을 겪고 있기 때문에 아무도 전에 시도하지 않은 솔루션을 공유하고 싶습니다 (어쨌든이 질문에서). 이 방법을 확인 하는 중단 된 중단 점에서도 작동 하는지 확인할 수 있습니다.
문제는 Service.startForeground(id, notification)
서비스 자체에서 전화 하는 것입니다. 안타깝게도 Android Framework 는 5 초 Service.startForeground(id, notification)
이내에 전화를 보장하지 않지만 Service.onCreate()
어쨌든 예외가 발생 하므로이 방법을 생각해 냈습니다.
- 호출하기 전에 서비스에서 바인더를 사용하여 서비스를 컨텍스트에 바인딩
Context.startForegroundService()
- 바인드에 성공
Context.startForegroundService()
하면 서비스 연결에서 호출하고 즉시 서비스 연결 내부에서 호출Service.startForeground()
하십시오. - 중요 사항 : 경우에 따라 호출에서 예외가 발생할 수 있으므로 try-catch
Context.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));
}
그것은 내가 개발 한 응용 프로그램에서 작동하는 것처럼 보이므로 시도해도 효과가 있습니다.
startForeground
in 호출 한 후에도 호출 하기 직전에 Service
호출하면 일부 장치에서 충돌이 발생합니다 . 따라서 추가 플래그로 서비스를 시작 하여이 문제를 해결했습니다.stopService
onCreate
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
boolean stopService = false;
if (intent != null) {
stopService = intent.getBooleanExtra("request_stop", false);
}
if (stopService) {
stopSelf();
return START_STICKY;
}
//Continue with the background task
return START_STICKY;
}
PS 서비스가 실제로 실행되고 있지 않은 경우 서비스가 먼저 시작되며 이는 오버 헤드입니다.
함수 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);
}
}
Service 또는 IntentService가 작성된 직후 startForeground 메소드를 호출하십시오. 이처럼 :
import android.app.Notification;
public class AuthenticationService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(1,new Notification());
}
}
startService(intent)
대신 서비스를 시작하고 바로 Context.startForeground()
호출 하는 문제를 해결했습니다 . 또한 부팅시 서비스를 시작하면 부팅 브로드 캐스트에서 서비스를 시작하는 활동을 시작할 수 있습니다. 영구적 인 솔루션은 아니지만 작동합니다.startForegound()
super.OnCreate()
데이터 업데이트 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)
}
좋아, 내가 이것에 주목 한 것은 다른 사람들에게도 도움이 될 수 있습니다. 이것은 내가보고있는 발생 문제를 해결하는 방법을 알아낼 수 있는지 테스트하기위한 것입니다. 간단히하기 위해 발표자에서 이것을 호출하는 메서드가 있다고 가정 해 봅시다.
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();
}
}
});
메인 스레드가 적시에 처리 할 수있을 때까지 기다려야한다고 생각하지만 충돌이 발생하지 않는 이유를 알지 못합니다. 나는 그것을 메인 스레드에 묶는 것이 이상적이지 않다는 것을 알고 있지만, 내 사용법이 백그라운드에서 호출하기 때문에 충돌이 아니라 완료 될 때까지 기다릴 지 걱정하지 않습니다.
나를 위해 매니페스트에 전경 서비스를 추가했습니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
그것이 아래 코멘트 작동하면 알려주세요
서비스
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
'Programing' 카테고리의 다른 글
파이썬 문자열에서 u 접두사는 무엇입니까? (0) | 2020.05.10 |
---|---|
C ++에서 <=> 연산자는 무엇입니까? (0) | 2020.05.10 |
npm UNMET PEER DEPENDENCY 경고는 어떻게 수정합니까? (0) | 2020.05.10 |
TypeError : method ()는 1 개의 위치 인수를 취하지 만 2가 주어졌습니다 (0) | 2020.05.10 |
수업 종류 확인 (0) | 2020.05.10 |