Context.startForegroundService () ไม่ได้เรียก Service.startForeground ()


258

ฉันใช้Serviceคลาสบนระบบปฏิบัติการ Android O

ฉันวางแผนที่จะใช้Serviceในพื้นหลัง

Android เอกสารระบุว่า

หากแอปของคุณกำหนดเป้าหมาย API ระดับ 26 ขึ้นไประบบจะกำหนดข้อ จำกัด ในการใช้หรือสร้างบริการพื้นหลังยกเว้นว่าแอพนั้นอยู่ในเบื้องหน้า หากแอปต้องการที่จะสร้างบริการเบื้องหน้า, app 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 คือ targetSdkVersion 25 ใน build.gradle ของคุณ
อเล็กซ์


1
ฉันกำลังใช้งานรอบนี้ ทำงานได้อย่างมีเสน่ห์theandroiddeveloper.com/blog/…
Prasad Pawar

1
ผมมีปัญหาเดียวกัน. ฉันแก้ไขปัญหานี้ ฉันแบ่งปันการใช้งานของฉันในหัวข้อนี้stackoverflow.com/questions/55894636/…
Beyazid

คำตอบ:


99

จากเอกสารของ Google ในAndroid 8.0 การเปลี่ยนแปลงพฤติกรรม :

ระบบอนุญาตให้แอปโทร Context.startForegroundService () แม้ในขณะที่แอปอยู่ในพื้นหลัง อย่างไรก็ตามแอปจะต้องเรียกใช้เมธอด startForeground () ของบริการนั้นภายในห้าวินาทีหลังจากสร้างบริการ

วิธีการแก้ปัญหา: เรียกstartForeground()ในonCreate()สำหรับServiceที่คุณใช้Context.startForegroundService()

ดูเพิ่มเติม: ข้อ จำกัด เบื้องหลังการดำเนินการสำหรับ Android 8.0 (Oreo)


19
ฉันทำสิ่งนี้ในonStartCommandวิธีการ แต่ฉันยังคงได้รับข้อผิดพลาดนี้ ฉันเรียกว่าในของฉันstartForegroundService(intent) MainActivityอาจเริ่มให้บริการช้าเกินไป ฉันคิดว่าไม่ควรมีขีด จำกัด ห้าวินาทีก่อนที่พวกเขาจะสามารถสัญญาได้ว่าบริการจะเริ่มต้นทันที
Kimi Chiu

29
ระยะเวลา 5 วินาทีไม่เพียงพอข้อยกเว้นนี้เกิดขึ้นบ่อยครั้งในเซสชันดีบัก ฉันสงสัยว่ามันจะเกิดขึ้นในโหมดการเปิดตัวเป็นครั้งคราว และการถูกยกเว้นโดย FATAL มันทำให้แอพล่ม! ฉันพยายามที่จะจับมันด้วย Thread.setDefaultUncaughtExceptionHandler () แต่ถึงกระนั้นก็ยังได้รับมันและไม่สนใจ Android ค้างโปรแกรม นั่นเป็นเพราะข้อยกเว้นนี้จะถูกเรียกจาก handleMessage () และการวนรอบหลักสิ้นสุดลงอย่างมีประสิทธิภาพ ... กำลังมองหาวิธีแก้ปัญหา
southerton

ฉันเรียกว่า Context.startForegroundService () วิธีการในตัวรับสัญญาณออกอากาศ ดังนั้นวิธีจัดการในสถานการณ์นี้ เพราะ onCreate () ไม่สามารถใช้ได้ในเครื่องรับกระจายสัญญาณ
Anand Savjani

6
@ ใต้ฉันคิดว่าคุณควรจะเรียกมันonStartCommand()แทนonCreateเพราะถ้าคุณปิดบริการและเริ่มต้นอีกครั้งมันอาจจะไปonStartCommand()โดยไม่ต้องโทรonCreate...
นักพัฒนา Android

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
ฉันด้วย. แต่ฉันยังคงได้รับข้อผิดพลาดนี้เป็นครั้งคราว บางที Android ไม่สามารถรับประกันได้ว่าจะโทรonCreateภายใน 5 วินาที ดังนั้นพวกเขาควรออกแบบใหม่ก่อนที่จะบังคับให้เราทำตามกฎ
Kimi Chiu

5
ฉันเรียก startForeground ใน onStartCommand () และบางครั้งฉันก็ได้รับข้อผิดพลาดนี้ ฉันย้ายมันไปยังสร้างสร้างและฉันไม่ได้เห็นมันตั้งแต่ (ข้ามนิ้ว)
ไทเลอร์

4
สิ่งนี้จะสร้างการแจ้งเตือนท้องถิ่นใน Oreo ว่า "APP_NAME กำลังทำงานอยู่แตะเพื่อปิดหรือดูข้อมูล" จะหยุดแสดงการแจ้งเตือนนั้นได้อย่างไร?
Artist404

6
ไม่ทีม Android อ้างว่านี่เป็นพฤติกรรมที่ตั้งใจ ดังนั้นฉันเพิ่งออกแบบแอปของฉันใหม่ นี่มันไร้สาระ จำเป็นต้องออกแบบแอปใหม่ทุกครั้งเนื่องจาก "พฤติกรรมที่ตั้งใจ" เหล่านี้ มันเป็นครั้งที่สาม.
Kimi Chiu

12
สาเหตุหลักของปัญหานี้คือบริการหยุดทำงานก่อนที่จะได้รับการเลื่อนไปยังส่วนหน้า แต่การยืนยันไม่ได้หยุดหลังจากบริการถูกทำลาย คุณสามารถลองทำซ้ำนี้โดยการเพิ่มหลังจากที่โทรStopService startForegroundService
Kimi Chiu

55

เหตุใดปัญหานี้เกิดขึ้นเนื่องจากเฟรมเวิร์ก Android ไม่สามารถรับประกันบริการของคุณเริ่มต้นได้ภายใน 5 วินาที แต่เฟรมเวิร์กอื่น ๆ จะมีข้อ จำกัด ที่เข้มงวดในการแจ้งเตือนเบื้องหน้าจะต้องเริ่มทำงานภายใน 5 วินาทีโดยไม่ตรวจสอบว่า .

นี่เป็นปัญหากรอบแน่นอน แต่ไม่ใช่นักพัฒนาทั้งหมดที่เผชิญกับปัญหานี้ทำดีที่สุด:

  1. startForegroundการแจ้งเตือนจะต้องอยู่ในทั้งสองonCreateและonStartCommandเนื่องจากหากบริการของคุณถูกสร้างขึ้นแล้วและกิจกรรมของคุณพยายามเริ่มใหม่อีกครั้งonCreateจะไม่ถูกเรียก

  2. รหัสการแจ้งเตือนต้องไม่เป็น 0 มิฉะนั้นจะเกิดข้อผิดพลาดเดียวกันแม้ว่าจะไม่ใช่เหตุผลเดียวกัน

  3. stopSelfstartForegroundจะต้องไม่ถูกเรียกก่อน

ด้วยทั้งหมด 3 ปัญหาข้างต้นนี้สามารถลดลงได้เล็กน้อย แต่ก็ยังไม่ใช่การแก้ไขการแก้ไขจริงหรือสมมุติว่าวิธีแก้ปัญหาคือลดระดับรุ่น sdk เป้าหมายของคุณเป็น 25

และโปรดทราบว่า Android P ส่วนใหญ่จะยังคงมีปัญหานี้อยู่เพราะ Google ปฏิเสธที่จะเข้าใจสิ่งที่เกิดขึ้นและไม่เชื่อว่านี่เป็นความผิดของพวกเขาอ่าน# 36และ# 56สำหรับข้อมูลเพิ่มเติม


9
ทำไมทั้ง onCreate และ onStartCommand คุณสามารถใส่มันลงใน onStartCommand ได้ไหม?
rcell

stopSelf ต้องไม่ถูกเรียกใช้ก่อน startForeground => หรือโดยตรงหลังเพราะดูเหมือนว่าจะยกเลิก "startForeground" เมื่อคุณทำ
แฟรงค์

1
ฉันทำการทดสอบบางอย่างและถึงแม้ว่า onCreate จะไม่ถูกเรียกใช้ถ้าบริการถูกสร้างขึ้นแล้วคุณไม่จำเป็นต้องเรียก startForeground อีกครั้ง ดังนั้นคุณสามารถเรียกมันได้เฉพาะบนเมธอด onCreate และไม่ได้อยู่ใน onStartCommand อาจเป็นเพราะพวกเขาพิจารณาตัวอย่างของการบริการที่อยู่เบื้องหน้าหลังจากมีการโทรครั้งแรกไปจนสิ้นสุด
Lxu

ฉันใช้ 0 startForegroundเป็นรหัสประกาศเริ่มต้นสำหรับการโทร การเปลี่ยนเป็น 1 ช่วยแก้ปัญหาสำหรับฉัน (หวังว่า)
mr5

แล้วทางออกคืออะไร?
IgorGanapolsky

35

ฉันรู้ว่ามีการเผยแพร่คำตอบมากเกินไปแล้วอย่างไรก็ตามความจริงก็คือ - startForegroundService ไม่สามารถแก้ไขได้ในระดับแอปและคุณควรหยุดใช้ การแนะนำของ Google ให้ใช้ Service # startForeground () API ภายใน 5 วินาทีหลังจาก Context # startForegroundService () ถูกเรียกไม่ใช่สิ่งที่แอปสามารถทำได้เสมอ

Android รันกระบวนการหลายอย่างพร้อมกันและไม่มีการรับประกันใด ๆ ว่า Looper จะโทรหาบริการเป้าหมายของคุณที่ควรจะเรียก startForeground () ภายใน 5 วินาที หากบริการเป้าหมายของคุณไม่ได้รับสายภายใน 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 ได้วิเคราะห์คิวที่นี่พบ "ผู้ทำร้าย" และฆ่ามันง่ายๆ ระบบมีความสุขและมีสุขภาพดีในขณะนี้ในขณะที่นักพัฒนาและผู้ใช้ไม่ได้ แต่เนื่องจาก Google จำกัด ความรับผิดชอบของพวกเขากับระบบทำไมพวกเขาควรสนใจเกี่ยวกับสองหลัง เห็นได้ชัดว่าพวกเขาทำไม่ได้ พวกเขาจะทำให้ดีขึ้นได้อย่างไร แน่นอนว่าพวกเขาสามารถให้บริการกล่องโต้ตอบ "แอปพลิเคชันไม่ว่าง" ได้ขอให้ผู้ใช้ตัดสินใจเกี่ยวกับการรอหรือฆ่าแอป แต่ทำไมต้องกังวลมันไม่ใช่ความรับผิดชอบของพวกเขา สิ่งสำคัญคือระบบมีสุขภาพดีในขณะนี้

จากการสังเกตของฉันสิ่งนี้เกิดขึ้นได้ค่อนข้างน้อยในกรณีของฉันมีผู้ใช้ 1K ประมาณ 1 ครั้งในหนึ่งเดือน การทำซ้ำมันเป็นไปไม่ได้และถึงแม้ว่ามันจะทำซ้ำมีอะไรที่คุณสามารถทำได้เพื่อแก้ไขอย่างถาวร

มีข้อเสนอแนะที่ดีในชุดข้อความนี้เพื่อใช้ "ผูก" แทน "เริ่มต้น" จากนั้นเมื่อบริการพร้อมให้ดำเนินการกับ ServiceConnected แต่อีกครั้งหมายความว่าไม่ได้ใช้การเรียกใช้ startForegroundService เลย

ฉันคิดว่าการกระทำที่ถูกต้องและซื่อสัตย์จากฝ่าย Google จะบอกทุกคนว่า startFourngourndServcie มีข้อบกพร่องและไม่ควรใช้

คำถามยังคงอยู่: จะใช้อะไรแทน โชคดีสำหรับเราที่มี JobScheduler และ JobService ตอนนี้ซึ่งเป็นทางเลือกที่ดีกว่าสำหรับบริการเบื้องหน้า มันเป็นตัวเลือกที่ดีกว่าเพราะสิ่งนั้น:

ในขณะที่งานกำลังทำงานอยู่ระบบจะเก็บ wakelock แทนแอพของคุณ ด้วยเหตุผลนี้คุณไม่จำเป็นต้องดำเนินการใด ๆ เพื่อรับประกันว่าอุปกรณ์จะยังคงทำงานตลอดระยะเวลาของงาน

หมายความว่าคุณไม่จำเป็นต้องกังวลเกี่ยวกับการจัดการ wakelocks อีกต่อไปและนั่นเป็นสาเหตุที่ไม่แตกต่างจากบริการเบื้องหน้า จากมุมมองการนำไปใช้งาน JobScheduler ไม่ใช่บริการของคุณมันเป็นระบบหนึ่งซึ่งสันนิษฐานว่ามันจะจัดการกับคิวที่ถูกต้องและ Google จะไม่ยกเลิกลูกของตัวเอง :)

ซัมซุงเปลี่ยนจาก startForegroundService เป็น JobScheduler และ JobService ใน Samsung Accessory Protocol (SAP) มันมีประโยชน์มากเมื่ออุปกรณ์เช่น smartwatches ต้องพูดคุยกับโฮสต์เช่นโทรศัพท์ซึ่งงานไม่จำเป็นต้องโต้ตอบกับผู้ใช้ผ่านทางเธรดหลักของแอป เนื่องจากงานถูกโพสต์โดยตัวกำหนดตารางเวลาไปที่เธรดหลักจึงเป็นไปได้ คุณควรจำไว้ว่างานกำลังทำงานอยู่ในเธรดหลักและถ่ายของที่มีน้ำหนักมากไปยังเธรดและงานอื่น ๆ ของ async

บริการนี้ดำเนินการงานที่เข้ามาแต่ละครั้งบนตัวจัดการที่ทำงานบนเธรดหลักของแอปพลิเคชันของคุณ ซึ่งหมายความว่าคุณต้องถ่ายตรรกะการดำเนินการของคุณไปยังเธรด / ตัวจัดการ / AsyncTask อื่นที่คุณเลือก

ข้อผิดพลาดเพียงอย่างเดียวของการเปลี่ยนไปใช้ JobScheduler / JobService คือคุณจะต้องปรับรหัสเก่าใหม่และไม่สนุก ฉันใช้เวลาสองวันในการทำเช่นนั้นเพื่อใช้การติดตั้ง SAP ของ Samsung ใหม่ ฉันจะดูรายงานข้อขัดข้องของฉันและแจ้งให้คุณทราบหากพบข้อขัดข้องอีกครั้ง ในทางทฤษฎีมันไม่ควรเกิดขึ้น แต่มีรายละเอียดเสมอที่เราอาจไม่ได้ตระหนักถึง

อัปเดต ไม่มีรายงานปัญหาอีกต่อไปโดย Play Store หมายความว่า JobScheduler / JobService ไม่มีปัญหาดังกล่าวและการสลับไปใช้โมเดลนี้เป็นแนวทางที่ถูกต้องในการกำจัดปัญหา startForegroundService ทันทีและตลอดไป ฉันหวังว่า Google / Android จะอ่านมันและในที่สุดก็จะแสดงความคิดเห็น / แนะนำ / ให้คำแนะนำอย่างเป็นทางการสำหรับทุกคน

อัพเดท 2

สำหรับผู้ที่ใช้ SAPและถามว่า SAP V2 ใช้คำอธิบาย JobService ได้อย่างไรด้านล่าง

ในรหัสที่กำหนดเองคุณจะต้องเริ่มต้น SAP (มันคือ Kotlin):

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

ตอนนี้คุณต้องถอดรหัสโค้ดของ Samsung เพื่อดูว่าเกิดอะไรขึ้นภายใน ใน 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'd 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'd 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 เช่นเดียวกับที่คุณต้องใช้และประมวลผลข้อความทั้งหมดที่มาจากอุปกรณ์ของคุณ

บรรทัดล่างมันไม่ง่ายอย่างนั้นและต้องมีการขุดเพื่อ internals และรหัส แต่มันใช้งานได้และที่สำคัญที่สุด - มันไม่ผิดพลาด


คำตอบที่ดี แต่มีปัญหาสำคัญJobIntentServiceวิ่งทันทีIntentServiceด้านล่างของ Oreo แต่จัดตารางงานบนและสูงกว่า Oreo ดังนั้นจึงJobIntentServiceไม่เริ่มทันที ข้อมูลเพิ่มเติม
CopsOnRoad

1
@CopsOnRoad มันมีประโยชน์มากและเริ่มต้นทันทีในกรณีของฉันตามที่ฉันเขียนมันใช้สำหรับการโต้ตอบแบบเรียลไทม์ระหว่างโทรศัพท์และสมาร์ทวอทช์ ไม่มีทางผู้ใช้จะรอ 15 นาทีในการส่งข้อมูลระหว่างสองสิ่งนี้ ทำงานได้ดีมากและไม่เกิดปัญหา
Oleg Gryb

1
เสียงเย็นจะทำให้คำตอบที่คุ้มค่ามากหากคุณสามารถแบ่งปันโค้ดตัวอย่างบางฉันใหม่เพื่อ Android, และพบว่าใช้เวลาในการเริ่มต้นและมันเป็นรุ่นล่วงหน้าJob... AlarmManager
CopsOnRoad

2
อาจจะเป็นเมื่อฉันอนุญาตให้เวลา: ฉันจะต้องยกเลิกการจับคู่จากรหัสเฉพาะที่กำหนดเองและยกเลิกการรวบรวม libs ที่เป็นกรรมสิทธิ์ของ Samsung
Oleg Gryb

1
@batmaci - JobService ถูกใช้โดย SAP ภายใน ดูการอัพเดต 2 ใน OP สำหรับรายละเอียด
Oleg Gryb

32

แอปของคุณจะมีปัญหาถ้าคุณโทรContext.startForegroundService(...)แล้วโทรContext.stopService(...)ก่อนService.startForeground(...)ถูกเรียก

ฉันมีคำติชมที่ชัดเจนที่นี่ForegroundServiceAPI26

ฉันได้เปิดข้อบกพร่องในเรื่องนี้ที่: ปัญหาการติดตามของ Google

ข้อบกพร่องบางอย่างเกี่ยวกับเรื่องนี้ได้รับการเปิดและปิดจะไม่แก้ไข

หวังว่าฉันกับขั้นตอนการทำซ้ำที่ชัดเจนจะทำให้การตัด

ข้อมูลที่จัดทำโดยทีมงาน google

Google ออกติดตามความคิดเห็นที่ 36

นี่ไม่ใช่ข้อผิดพลาดของเฟรมเวิร์ก มันตั้งใจ หากแอปเริ่มต้นอินสแตนซ์บริการด้วยแอปstartForegroundService()นั้นจะต้องเปลี่ยนอินสแตนซ์ของบริการนั้นเป็นสถานะพื้นหน้าและแสดงการแจ้งเตือน ถ้าอินสแตนซ์ของบริการถูกหยุดก่อนstartForeground()ถูกเรียกใช้สัญญานั้นจะไม่ได้ผล: นี่เป็นข้อบกพร่องในแอป

เช่น # 31การเผยแพร่บริการที่แอปอื่นสามารถเริ่มต้นได้โดยตรงนั้นไม่ปลอดภัยโดยพื้นฐาน คุณสามารถบรรเทาได้นิดหน่อยโดยปฏิบัติต่อทั้งหมดดำเนินการเริ่มต้นของบริการตามที่ต้องการstartForeground()แต่แน่นอนว่าอาจไม่ใช่สิ่งที่คุณมีอยู่ในใจ

Google ออกติดตามความคิดเห็น 56

มีสองสถานการณ์ที่แตกต่างกันซึ่งนำไปสู่ผลลัพธ์เดียวกันที่นี่

ปัญหาความหมายทันทีว่าเป็นข้อผิดพลาดที่จะเตะบางสิ่งบางอย่างออกด้วยstartForegroundService()แต่ไม่สนใจที่จะเปลี่ยนเป็นพื้นหน้าผ่านstartForeground()เป็นเพียงแค่: ปัญหาความหมาย นั่นถือเป็นข้อบกพร่องของแอปโดยเจตนา การหยุดบริการก่อนที่จะเปลี่ยนเป็นพื้นหน้าเป็นข้อผิดพลาดของแอพ นั่นเป็นจุดเริ่มต้นของ OP และเป็นสาเหตุที่ทำให้ปัญหานี้ถูกทำเครื่องหมายว่า "ทำงานตามที่ตั้งใจไว้"

อย่างไรก็ตามยังมีคำถามเกี่ยวกับการตรวจจับของปัญหานี้ นั่นคือการได้รับการปฏิบัติเหมือนเป็นปัญหาของแท้ถึงแม้ว่ามันจะถูกติดตามแยกต่างหากจากปัญหาการติดตามข้อผิดพลาดนี้โดยเฉพาะ เราไม่ได้หูหนวกกับคำร้องเรียน


2
การปรับปรุงที่ดีที่สุดที่ฉันได้เห็นคือissuetracker.google.com/issues/76112072#comment56 ฉันเขียนรหัสของฉันอีกครั้งเพื่อใช้ Context.bindService ซึ่งหลีกเลี่ยงการโทรที่เป็นปัญหาไปยัง Context.startForegroundService คุณสามารถดูรหัสตัวอย่างของฉันได้ที่github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby

ใช่อย่างที่ฉันเขียนไว้ควรใช้งานได้ แต่ฉันได้เปลี่ยนไปใช้ 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
คุณลองในบางแอปที่มีผู้ใช้อย่างน้อยหลายพันคนหรือไม่ ฉันพยายามผู้ใช้บางคนยังคงมีปัญหาความผิดพลาด
อเล็กซ์

ไม่ไม่ฉันใช้รหัสที่ถูกต้องและฉันไม่เห็นลูกค้าได้รับความผิดพลาด
Ahmad Arslan

คุณมีผู้ใช้งานเท่าไหร่? ฉันเห็น ~ 10-30+ ล่มทุกวัน (พร้อมข้อผิดพลาด) ในแอปที่มีผู้ใช้ 10 พันคนต่อวัน แน่นอนมันเกิดขึ้นเฉพาะกับผู้ใช้ที่มี android 8.0 และ android 8.1 ในกรณี targetSdkVersion คือ 27 ปัญหาทั้งหมดหายไปเป็น 0 ข้อขัดข้องทันทีหลังจากที่ฉันตั้ง targetSdkVersion เป็น 25
Alex

ผู้ใช้ 2200 คนเท่านั้น: - / แต่ไม่ได้รับความผิดพลาดใด ๆ
Ahmad Arslan

1
@Jeeva ไม่ได้จริงๆ ตู้เอทีเอ็มฉันใช้ targetSdkVersion 25 กับ compileSdkVersion 27 ดูเหมือนว่าจะเป็นวิธีที่ดีที่สุดหลังจากการทดลองมากมาย .... หวังว่าพวกเขาจะเสร็จสิ้นdeveloper.android.com/topic/lไลบรารี/architecture/… ก่อนเดือนสิงหาคม 2018 เพราะandroid-developers.googleblog .com / 2017/12 / …
อเล็กซ์

16

ฉันมีวิดเจ็ตซึ่งอัพเดตบ่อยครั้งเมื่ออุปกรณ์ตื่นและฉันเห็นปัญหาหลายพันรายการในเวลาเพียงไม่กี่วัน

ทริกเกอร์ปัญหา

ฉันยังสังเกตเห็นปัญหาแม้ใน Pixel 3 XL ของฉันเมื่อฉันไม่คิดว่าอุปกรณ์จะมีภาระมาก และเส้นทางรหัสใด ๆ startForeground()และทั้งหมดถูกปกคลุมไปด้วย แต่แล้วฉันก็ตระหนักว่าในหลาย ๆ กรณีบริการของฉันทำให้งานเสร็จเร็วมาก ฉันเชื่อว่าทริกเกอร์สำหรับแอปของฉันคือบริการเสร็จก่อนที่ระบบจะได้รับการแจ้งเตือน

วิธีแก้ปัญหา / วิธีแก้ปัญหา

ฉันสามารถกำจัดข้อขัดข้องทั้งหมดได้ stopSelf()อะไรที่ฉันไม่ได้ที่จะเอาการเรียกร้องให้ (ฉันกำลังคิดที่จะชะลอการหยุดจนกว่าฉันจะค่อนข้างแน่ใจว่าการแจ้งเตือนถูกแสดง แต่ฉันไม่ต้องการให้ผู้ใช้เห็นการแจ้งเตือนหากไม่จำเป็น) เมื่อบริการไม่ได้ใช้งานเป็นเวลาหนึ่งนาทีหรือระบบ ทำลายมันตามปกติโดยไม่ทิ้งข้อยกเว้นใด ๆ

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

11

แค่หัวขึ้นเพราะฉันเสียเวลาไปหลายชั่วโมงเกินไป ผมเก็บไว้ได้รับข้อยกเว้นนี้แม้ว่าฉันถูกเรียกว่าเป็นสิ่งแรกในstartForeground(..) ในท้ายที่สุดผมพบว่าปัญหาที่เกิดจากการใช้onCreate(..) NOTIFICATION_ID = 0ใช้ค่าอื่น ๆ ดูเหมือนว่าจะแก้ไขปัญหานี้


ในกรณีของฉันฉันใช้ ID 1 ขอบคุณปัญหาได้รับการแก้ไขแล้ว
Amin Pinjari

4
ฉันใช้ ID 101 ฉันยังคงได้รับข้อผิดพลาดนี้ในบางครั้ง
CopsOnRoad

9

ฉันมีวิธีแก้ไขปัญหานี้แล้ว ฉันได้ตรวจสอบการแก้ไขนี้ในแอพของฉันเอง (300K + DAU) ซึ่งสามารถลดความผิดพลาดประเภทนี้ได้อย่างน้อย 95% แต่ก็ไม่สามารถหลีกเลี่ยงปัญหานี้ได้ 100%

ปัญหานี้เกิดขึ้นแม้ว่าคุณจะแน่ใจว่าได้โทรหา startForeground () หลังจากเริ่มให้บริการตามเอกสารของ Google แล้ว อาจเป็นเพราะการสร้างบริการและกระบวนการเริ่มต้นมีค่าใช้จ่ายมากกว่า 5 วินาทีในหลาย ๆ สถานการณ์ไม่ว่าเมื่อใดและที่ใดที่คุณเรียกใช้เมธอด startForeground () ความผิดพลาดนี้ไม่สามารถหลีกเลี่ยงได้

โซลูชันของฉันคือทำให้แน่ใจว่า startForeground () จะถูกดำเนินการภายใน 5 วินาทีหลังจากเมธอด startForegroundService () ไม่ว่าบริการของคุณจะต้องถูกสร้างและเริ่มต้นนานเท่าใด นี่คือวิธีการแก้ปัญหาอย่างละเอียด

  1. ห้ามใช้ startForegroundService ตั้งแต่แรกใช้ bindService () พร้อมกับแฟล็ก auto_create มันจะรอการเริ่มต้นบริการ นี่คือรหัสบริการตัวอย่างของฉันคือ 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 () จะทำให้มั่นใจได้ว่าเมธอด startForeground () นั้นถูกเรียกใช้หลังจาก startForegroundService ():

    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 ด้วยความตั้งใจที่รอดำเนินการเช่นถ้าคุณต้องการเริ่มบริการเบื้องหน้าในวิดเจ็ต (คลิกที่ปุ่มวิดเจ็ต) โดยไม่ต้องเปิดแอปของคุณคุณสามารถใส่ข้อมูลโค้ดในเครื่องรับ และดำเนินการเหตุการณ์ออกอากาศแทนคำสั่งเริ่มบริการ

นั้นคือทั้งหมด. หวังว่ามันจะช่วย โชคดี.


8

ข้อผิดพลาดนี้ยังเกิดขึ้นบน Android 8+ เมื่อService.startForeground (int id, การแจ้งเตือนการแจ้งเตือน)ถูกเรียกในขณะที่idถูกตั้งค่าเป็น 0

id int: ตัวระบุสำหรับการแจ้งเตือนนี้ตาม NotificationManager.notify (int, การแจ้งเตือน); จะต้องไม่เป็น 0


3
อย่าใช้หมายเลขที่สูงขึ้นสำหรับรหัสการแจ้งเตือนด้วย ลองใช้ ID หนึ่งหลัก stackoverflow.com/a/12228555/2527204
Marlon

7

เนื่องจากทุกคนที่มาที่นี่กำลังทุกข์ทรมานในสิ่งเดียวกันฉันต้องการแบ่งปันวิธีแก้ปัญหาของฉันที่ไม่มีใครเคยลองมาก่อน (ในคำถามนี้ต่อไป) ฉันรับรองได้ว่ามันใช้งานได้แม้กระทั่งในจุดหยุดที่หยุดซึ่งยืนยันวิธีนี้

ปัญหาคือการโทรService.startForeground(id, notification)จากบริการของตัวเองใช่มั้ย น่าเสียดายที่ Android Framework ไม่รับประกันว่าจะโทรService.startForeground(id, notification)ภายในService.onCreate()5 วินาที แต่มีข้อยกเว้นอยู่ดีดังนั้นฉันจึงคิดวิธีนี้

  1. ผูกบริการกับบริบทด้วยเครื่องผูกจากบริการก่อนการโทร Context.startForegroundService()
  2. หากการผูกสำเร็จโทรContext.startForegroundService() จากการเชื่อมต่อบริการและโทรService.startForeground() ภายในการเชื่อมต่อบริการทันที
  3. หมายเหตุสำคัญ:เรียกใช้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));
}

ดูเหมือนว่าจะทำงานกับแอปพลิเคชันที่ฉันพัฒนาดังนั้นจึงควรทำงานเมื่อคุณลองเช่นกัน


5

มีปัญหากับ Android O API 26

ถ้าคุณหยุดให้บริการทันที (เพื่อให้บริการของคุณไม่ได้ทำงานจริง ๆ (ถ้อยคำ / ความเข้าใจ) และคุณอยู่ในช่วง ANR คุณยังต้องโทรหา startForeground ก่อน stopSelf

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

พยายามใช้วิธีนี้ แต่ก็ยังสร้างข้อผิดพลาด: -

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

ฉันกำลังใช้สิ่งนี้จนกว่าข้อผิดพลาดจะได้รับการแก้ไข

mContext.startService(playIntent);

3
ควรใช้ประโยชน์ SDK_INT> = 26 ไม่ใช่แค่ใหญ่กว่า
Andris

4

ฉันกำลังเผชิญกับปัญหาเดียวกันและหลังจากใช้เวลาพบ solutons คุณสามารถลองรหัสด้านล่าง หากการใช้งานของคุณ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เพราะตาราง Android สร้างบริการบนตัวจัดการหัวข้อหลัก แต่bringDownServiceLockedจะเรียกว่าบน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(...)นอกจากนี้เรายังต้องลบการออกอากาศที่ติดหนึบเพื่อป้องกันไม่ให้ตัวรับสัญญาณถูกแจ้งเตือนในครั้งต่อไป

โปรดทราบว่าวิธีการ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 ไม่ใช่ศูนย์

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

ฉันได้เรียกหยุดเบื้องหน้าด้วยการลบ notificaton แต่เมื่อบริการลบการแจ้งเตือนกลายเป็นบริการพื้นหลังและพื้นหลังไม่สามารถทำงานใน android O จากพื้นหลัง เริ่มต้นหลังจากได้รับการผลักดัน

ดังนั้นคำวิเศษคือ

   stopSelf();

จนถึงขณะนี้ด้วยเหตุผลใดก็ตามที่บริการของคุณหยุดทำตามขั้นตอนข้างต้นและเพลิดเพลินไปกับมัน


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (จริง); } else {stopForeground (จริง); } ถ้าเป็นอย่างอื่นจะทำอะไร ทั้งสองกรณีที่คุณทำเช่นเดียวกัน stopForeground (true);
user3135923

ฉันเชื่อว่าคุณตั้งใจจะหยุด stopSelf (); ภายในบล็อกอื่นแทน stopForeground (จริง);
จัสตินสแตนเลย์

1
ใช่ @JustinStanley ใช้ stopSelf ();
ทราย

ไม่ควรstartForegroundจะเรียกว่าในonCreate?
Aba

ฉันกำลังใช้ NotificationManager และไม่ใช่คลาสการแจ้งเตือน ฉันจะใช้สิ่งนี้ได้อย่างไร
Prajwal W

3

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

คล้ายกับ startService (Intent) แต่ด้วยสัญญาโดยนัยว่า Service จะเรียก startForeground (int, android.app.Notification) เมื่อเริ่มทำงาน บริการจะได้รับจำนวนเวลาเทียบได้กับช่วงเวลา ANR ในการทำเช่นนี้มิฉะนั้นระบบจะหยุดให้บริการโดยอัตโนมัติและประกาศแอป ANR

แตกต่างจาก startService ทั่วไป (Intent) วิธีนี้สามารถใช้ได้ตลอดเวลาไม่ว่าแอพที่ให้บริการโฮสต์จะอยู่ในสถานะเบื้องหน้าหรือไม่

ให้แน่ใจว่าคุณเรียกService.startForeground(int, android.app.Notification)ใน onCreate () เพื่อให้คุณมั่นใจได้ว่ามันจะเป็น called..if คุณมีเงื่อนไขใด ๆ ที่อาจป้องกันไม่ให้คุณทำอย่างนั้นแล้วคุณจะดีกว่าการใช้ปกติContext.startService(Intent)และเรียกService.startForeground(int, android.app.Notification)ตัวเอง

ดูเหมือนว่าContext.startForegroundService()สุนัขเฝ้าบ้านเพิ่มเพื่อให้แน่ใจว่าคุณเรียกว่าService.startForeground(int, android.app.Notification)ก่อนที่มันจะถูกทำลาย ...


3

กรุณาอย่าโทร StartForgroundServices ใด ๆ ภายในวิธีการonCreate ()คุณต้องโทรหาบริการ StartForground ใน onStartCommand ()หลังจากทำเธรดผู้ปฏิบัติงานมิฉะนั้นคุณจะได้รับ 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 เป็น const ของคุณเองซึ่งไม่สามารถเป็น 0 หรือมากกว่า Max (Int32)


2

แม้หลังจากการเรียกstartForegroundใน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
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

ป.ล. ถ้าบริการไม่ทำงานก็จะเริ่มบริการก่อนซึ่งเป็นค่าใช้จ่าย


2

คุณต้องเพิ่มการอนุญาตเป็นร้องสำหรับอุปกรณ์ Android 9 เมื่อใช้เป้าหมาย sdk 28 หรือใหม่กว่ามิฉะนั้นจะเกิดข้อยกเว้น:

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

1

ฉันแค่ตรวจสอบPendingIntentโมฆะหรือไม่ก่อนที่จะเรียก 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);
        }
}

นี่เป็นเพียงการเริ่มต้นบริการเพื่อเริ่มบริการแทน startForegroundService หากเป็นโมฆะ คำสั่ง if ต้องซ้อนกันมิฉะนั้นแอปจะพยายามใช้บริการในเวอร์ชันต่อ ๆ มาในบางจุด
a54studio

1

เพียงเรียกวิธีการ startForeground ทันทีหลังจากสร้าง Service หรือ IntentService แบบนี้:

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

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

1
FATAL EXCEPTION: หลัก android.app.RemoteServiceException: การแจ้งเตือนที่ไม่ดีสำหรับ startForeground: java.lang.RuntimeException: ช่องที่ไม่ถูกต้องสำหรับการแจ้งเตือนบริการ: การแจ้งเตือน (channel = pri pri = 0 contentView = การสั่นของ null = ค่า null null = 0x40 ค่าสถานะ color = 0x00000000 vis = เอกชน) ที่ android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) ที่ android.os.Handler.dispatchMessage (Handler.java:106) ที่ android.os.Looper.loop (Looper java: 164) ที่ android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry

1

ตกลงสิ่งที่ฉันสังเกตเห็นเกี่ยวกับเรื่องนี้ที่อาจช่วยคนอื่นไม่กี่คน นี่คือการทดสอบอย่างเคร่งครัดเพื่อดูว่าฉันสามารถหาวิธีแก้ไขเหตุการณ์ที่ฉันเห็นได้หรือไม่ เพื่อความเรียบง่ายสมมติว่าฉันมีวิธีที่เรียกสิ่งนี้จากผู้นำเสนอ

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

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

ข้อผิดพลาดนี้จะมีข้อผิดพลาดเดียวกัน บริการจะไม่เริ่มจนกว่าวิธีการจะเสร็จสมบูรณ์ดังนั้นจึงไม่มีonCreate()ในบริการ

ดังนั้นแม้ว่าคุณจะอัปเดต UI จากเธรดหลักหากคุณมีสิ่งใดที่อาจทำให้วิธีการนั้นค้างอยู่หลังจากนั้นมันก็จะไม่เริ่มทำงานในเวลาและให้ข้อผิดพลาด Foreground ที่น่ากลัวแก่คุณ ในกรณีของฉันเรากำลังโหลดบางสิ่งลงในคิวและแต่ละอันถูกเรียก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

พื้นหลัง. ปัญหาคือ Android ไม่สามารถรับประกันได้ว่าบริการจะเริ่มในเวลาที่กำหนด หนึ่งคิดว่าพวกเขาจะโยนข้อผิดพลาดเพื่อเพิกเฉยถ้าคุณต้องการ แต่ก็ไม่ใช่ Android มันจะต้องเป็นอันตรายถึงชีวิต สิ่งนี้ช่วยในแอพของเรา แต่ไม่ได้แก้ไขทั้งหมด
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(...)เป็นสิ่งสำคัญในการที่จะปรับปรุงServiceเมื่อได้รับการสร้างขึ้นแล้ว

เมื่อContextCompat.startForegroundService(...)ถูกเรียกหลังจากที่Serviceถูกสร้างขึ้นonBind(...)และonCreate(...)ไม่ถูกเรียก ดังนั้นการปรับปรุงข้อมูลที่สามารถผ่านเข้าไปในonStartCommand(...)ทางที่จะปรับปรุงข้อมูลในIntent BundleService

ตัวอย่าง

ฉันใช้รูปแบบนี้เพื่อนำไปใช้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)
        }

    }

}

Tester

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.