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