Tôi biết, đã có quá nhiều câu trả lời đã được công bố, tuy nhiên sự thật là - startForegroundService không thể được sửa ở cấp ứng dụng và bạn nên ngừng sử dụng nó. Đó là khuyến nghị của Google về việc sử dụng API Service # startForeground () trong vòng 5 giây sau khi Context # startForegroundService () được gọi không phải là điều mà một ứng dụng luôn có thể làm.
Android chạy rất nhiều quy trình đồng thời và không có gì đảm bảo rằng Looper sẽ gọi dịch vụ mục tiêu của bạn được gọi là startForeground () trong vòng 5 giây. Nếu dịch vụ mục tiêu của bạn không nhận được cuộc gọi trong vòng 5 giây, bạn sẽ không gặp may và người dùng của bạn sẽ gặp tình huống ANR. Trong ngăn xếp của bạn, bạn sẽ thấy một cái gì đó như thế này:
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)
Theo tôi hiểu, Looper đã phân tích hàng đợi ở đây, tìm thấy một "kẻ lạm dụng" và chỉ đơn giản là giết nó. Hiện tại hệ thống đang hạnh phúc và khỏe mạnh, trong khi các nhà phát triển và người dùng thì không, nhưng vì Google giới hạn trách nhiệm của họ đối với hệ thống, tại sao họ phải quan tâm đến hai cái sau? Rõ ràng là họ không. Họ có thể làm cho nó tốt hơn? Tất nhiên, ví dụ: họ có thể phục vụ hộp thoại "Ứng dụng đang bận", yêu cầu người dùng đưa ra quyết định về việc chờ đợi hoặc hủy ứng dụng, nhưng tại sao lại phải lo lắng, đó không phải là trách nhiệm của họ. Điều chính là hệ thống đang khỏe mạnh.
Theo quan sát của tôi, điều này hiếm khi xảy ra, trong trường hợp của tôi, khoảng 1 sự cố trong một tháng cho người dùng 1K. Tái tạo nó là không thể, và ngay cả khi nó được sao chép, bạn không thể làm gì để khắc phục nó vĩnh viễn.
Có một đề xuất tốt trong luồng này là sử dụng "liên kết" thay vì "bắt đầu" và sau đó khi dịch vụ đã sẵn sàng, xử lý onServiceConnected, nhưng một lần nữa, điều đó có nghĩa là hoàn toàn không sử dụng các lệnh gọi startForegroundService.
Tôi nghĩ rằng, hành động đúng đắn và trung thực từ phía Google sẽ là nói với mọi người rằng startForegourndServcie có một thiếu sót và không nên được sử dụng.
Câu hỏi vẫn còn: sử dụng cái gì thay thế? May mắn thay cho chúng tôi, hiện tại đã có JobScheduler và JobService, đây là một lựa chọn tốt hơn cho các dịch vụ tiền cảnh. Đó là một lựa chọn tốt hơn, vì điều đó:
Trong khi một công việc đang chạy, hệ thống giữ một wakelock thay mặt cho ứng dụng của bạn. Vì lý do này, bạn không cần phải thực hiện bất kỳ hành động nào để đảm bảo rằng thiết bị vẫn hoạt động trong suốt thời gian của công việc.
Điều đó có nghĩa là bạn không cần quan tâm đến việc xử lý wakelocks nữa và đó là lý do tại sao nó không khác với các dịch vụ tiền cảnh. Từ quan điểm triển khai, JobScheduler không phải là dịch vụ của bạn, đó là một hệ thống, có lẽ nó sẽ xử lý hàng đợi đúng và Google sẽ không bao giờ chấm dứt con của mình :)
Samsung đã chuyển từ startForegroundService sang JobScheduler và JobService trong Giao thức phụ kiện Samsung (SAP) của họ. Nó rất hữu ích khi các thiết bị như smartwatch cần nói chuyện với máy chủ như điện thoại, nơi công việc cần phải tương tác với người dùng thông qua chuỗi chính của ứng dụng. Vì các công việc được đăng bởi người lập lịch lên luồng chính, nó trở nên có thể. Bạn nên nhớ rằng công việc đang chạy trên luồng chính và giảm tải tất cả những thứ nặng nề cho các luồng khác và các tác vụ không đồng bộ.
Dịch vụ này thực thi từng công việc đến trên Trình xử lý đang chạy trên luồng chính của ứng dụng của bạn. Điều này có nghĩa là bạn phải giảm tải logic thực thi của mình sang một luồng / trình xử lý / AsyncTask khác mà bạn chọn
Cạm bẫy duy nhất của việc chuyển sang JobScheduler / JobService là bạn sẽ cần cấu trúc lại mã cũ và điều đó không vui chút nào. Tôi đã dành hai ngày qua để làm việc đó để sử dụng triển khai SAP mới của Samsung. Tôi sẽ xem các báo cáo sự cố của tôi và cho bạn biết nếu gặp lại sự cố. Về mặt lý thuyết thì điều đó không nên xảy ra, nhưng luôn có những chi tiết mà chúng ta có thể không biết.
CẬP NHẬT
Không có thêm sự cố được báo cáo bởi Play Store. Điều đó có nghĩa là JobScheduler / JobService không gặp vấn đề như vậy và chuyển sang mô hình này là cách tiếp cận phù hợp để thoát khỏi vấn đề startForegroundService một lần và mãi mãi. Tôi hy vọng, Google / Android sẽ đọc nó và cuối cùng sẽ bình luận / tư vấn / cung cấp hướng dẫn chính thức cho mọi người.
CẬP NHẬT 2
Đối với những người sử dụng SAP và hỏi cách thức SAP V2 sử dụng giải thích về Dịch vụ công việc dưới đây.
Trong mã tùy chỉnh của bạn, bạn sẽ cần khởi tạo SAP (đó là Kotlin):
SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)
Bây giờ bạn cần dịch ngược mã của Samsung để xem những gì đang diễn ra bên trong. Trong SAAgentV2, hãy xem triển khai requestAgent và dòng sau:
SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);
where d defined as below
private SAAdapter d;
Đi đến lớp SAAd CHƯƠNG ngay bây giờ và tìm thấy hàm onServiceConnectionRequested lập lịch công việc bằng cách sử dụng lệnh gọi sau:
SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);
SAJobService chỉ là một triển khai của Dịch vụ việc làm của Android và đây là dịch vụ lập lịch trình công việc:
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());
}
Như bạn thấy, dòng cuối cùng ở đây sử dụng JobScheduler của Android để có được dịch vụ hệ thống này và lên lịch công việc.
Trong cuộc gọi requestAgent, chúng tôi đã thông qua mAgentCallback, đây là chức năng gọi lại sẽ nhận quyền kiểm soát khi một sự kiện quan trọng xảy ra. Đây là cách gọi lại được xác định trong ứng dụng của tôi:
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 ở đây là một lớp mà tôi đã triển khai để xử lý tất cả các yêu cầu đến từ một chiếc smartwatch của Samsung. Nó không phải là mã đầy đủ, chỉ là một bộ xương:
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) {
}
}
Như bạn thấy, MessageJobs yêu cầu lớp MessageSocket cũng như bạn sẽ cần triển khai và xử lý tất cả các tin nhắn đến từ thiết bị của bạn.
Điểm mấu chốt, nó không đơn giản và nó đòi hỏi phải đào sâu vào bên trong và mã hóa, nhưng nó hoạt động, và quan trọng nhất - nó không bị sập.