Context.startForegroundService () sau đó không gọi Service.startForeground ()


258

Tôi đang sử dụng ServiceClass trên HĐH Android O.

Tôi có kế hoạch sử dụng Servicetrong nền.

Các tài liệu Android bang mà

Nếu ứng dụng của bạn nhắm mục tiêu API cấp 26 trở lên, hệ thống sẽ áp dụng các hạn chế trong việc sử dụng hoặc tạo các dịch vụ nền trừ khi chính ứng dụng nằm ở phía trước. Nếu một ứng dụng cần tạo một dịch vụ nền trước, ứng dụng sẽ gọi startForegroundService().

Nếu bạn sử dụng startForegroundService(), Serviceném lỗi sau.

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

Có chuyện gì với cái này vậy?


IOW, vui lòng cung cấp một ví dụ tái sản xuất tối thiểu . Điều đó sẽ bao gồm toàn bộ dấu vết ngăn xếp Java và mã đang gây ra sự cố.
CommonsWare

3
Lỗi vẫn còn ở đây trong API 26 và 27 (27.0.3). Các phiên bản Android bị ảnh hưởng là 8.0 và 8.1 Bạn có thể giảm số lượng sự cố bằng cách thêm cả startForeground () vào onCreate () và onStartCommand (), nhưng sự cố vẫn sẽ xảy ra với một số người dùng. Cách duy nhất để sửa lỗi atm là targetSdkVersion 25 trong build.gradle của bạn.
Alex

1
Công ty phát hành FYIriss.google.com/issues/76112072
v1k

1
Tôi đang sử dụng công việc này khoảng bây giờ. hoạt động như charm theandroiddeveloper.com/blog/ từ
Prasad Pawar

1
Tôi có cùng một vấn đề. Tôi khắc phục vấn đề này. Tôi đã chia sẻ việc triển khai của mình trong chủ đề này stackoverflow.com/questions/55894636/
trộm

Câu trả lời:


99

Từ các tài liệu của Google về Android 8.0 thay đổi hành vi :

Hệ thống cho phép các ứng dụng gọi Context.startForegroundService () ngay cả khi ứng dụng ở chế độ nền. Tuy nhiên, ứng dụng phải gọi phương thức startForeground () của dịch vụ đó trong vòng năm giây sau khi dịch vụ được tạo.

Giải pháp: Gọi startForeground()trong onCreate()cho Servicemà bạn sử dụngContext.startForegroundService()

Xem thêm: Giới hạn thực thi nền cho Android 8.0 (Oreo)


19
Tôi đã làm điều này trong onStartCommandphương thức, nhưng tôi vẫn nhận được lỗi này. Tôi gọi startForegroundService(intent)trong tôi MainActivity. Có lẽ dịch vụ được bắt đầu quá chậm. Tôi nghĩ rằng giới hạn năm giây không nên tồn tại trước khi họ có thể hứa dịch vụ được bắt đầu ngay lập tức.
Kimi Chiu

29
Thời gian 5 giây chắc chắn là không đủ, ngoại lệ này xảy ra rất thường xuyên trong các phiên gỡ lỗi. Tôi nghi ngờ nó cũng sẽ xảy ra trong chế độ phát hành theo thời gian. Và bị NGOẠI TRỪ, nó chỉ làm hỏng ứng dụng! Tôi đã cố gắng bắt nó với Thread.setDefaultUncaughtExceptionHandler (), nhưng ngay cả khi nhận được nó ở đó và bỏ qua Android đóng băng ứng dụng. Đó là vì ngoại lệ này được kích hoạt từ handleMessage () và vòng lặp chính kết thúc một cách hiệu quả ... tìm cách giải quyết.
miền nam

Tôi đã gọi phương thức Context.startForegroundService () trong máy thu quảng bá. Vậy làm thế nào để xử lý trong tình huống này? bởi vì onCreate () không khả dụng trong máy thu quảng bá
Anand Savigate

6
@southerton Tôi nghĩ bạn nên gọi nó onStartCommand()thay vì onCreate, vì nếu bạn đóng dịch vụ và khởi động lại, nó có thể đi đến onStartCommand()mà không gọi onCreate...
nhà phát triển Android

1
Chúng tôi có thể kiểm tra phản hồi từ đội google đây issuetracker.google.com/issues/76112072#comment56
Prags

82

Tôi gọi ContextCompat.startForegroundService(this, intent)để bắt đầu dịch vụ rồi

Phục vụ 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
Tôi cũng vậy. Nhưng tôi vẫn thỉnh thoảng gặp lỗi này. Có lẽ Android không thể đảm bảo nó sẽ gọi onCreatetrong 5 giây. Vì vậy, họ nên thiết kế lại nó trước khi họ buộc chúng tôi tuân theo các quy tắc.
Kimi Chiu

5
Tôi đã gọi startForeground trong onStartCommand () và thỉnh thoảng tôi gặp lỗi này. Tôi đã chuyển nó sang onCreate và tôi đã không nhìn thấy nó kể từ khi (bắt chéo ngón tay).
Tyler

4
Điều này tạo ra một thông báo cục bộ trong Oreo với nội dung "APP_NAME đang chạy. Nhấn để đóng hoặc xem thông tin". Làm thế nào để ngừng hiển thị thông báo đó?
Nghệ sĩ404

6
Không, Nhóm Android tuyên bố đây là hành vi có chủ đích. Vì vậy, tôi chỉ thiết kế lại ứng dụng của mình. Chuyện này thật vớ vẩn. Luôn cần thiết kế lại các ứng dụng do "hành vi dự định" này. Đây là lần thứ ba.
Kimi Chiu

12
Nguyên nhân chính của vấn đề này là dịch vụ đã bị dừng trước khi nó được quảng bá lên tiền cảnh. Nhưng sự khẳng định đã không dừng lại sau khi dịch vụ bị phá hủy. Bạn có thể thử tái tạo điều này bằng cách thêm StopServicesau khi gọi startForegroundService.
Kimi Chiu

55

Tại sao sự cố này xảy ra là do khung Android không thể đảm bảo dịch vụ của bạn bắt đầu trong vòng 5 giây nhưng mặt khác, khung có giới hạn nghiêm ngặt về thông báo tiền cảnh phải được kích hoạt trong vòng 5 giây, mà không kiểm tra xem khung đã cố gắng khởi động dịch vụ chưa .

Đây chắc chắn là một vấn đề khung, nhưng không phải tất cả các nhà phát triển phải đối mặt với vấn đề này đều cố gắng hết sức:

  1. startForegroundmột thông báo phải có cả hai onCreateonStartCommand, bởi vì nếu dịch vụ của bạn đã được tạo và bằng cách nào đó, hoạt động của bạn đang cố gắng bắt đầu lại, onCreatesẽ không được gọi.

  2. ID thông báo không được bằng 0 nếu không sự cố tương tự sẽ xảy ra ngay cả khi đó không phải là lý do tương tự.

  3. stopSelfphải không được gọi trước khi startForeground.

Với tất cả 3 vấn đề trên có thể giảm bớt một chút nhưng vẫn không khắc phục được, cách khắc phục thực sự hoặc giả sử cách khắc phục là hạ cấp phiên bản sdk mục tiêu của bạn xuống 25.

Và lưu ý rằng rất có thể Android P vẫn sẽ gặp phải sự cố này vì Google từ chối thậm chí không hiểu chuyện gì đang xảy ra và không tin đây là lỗi của họ, hãy đọc # 36# 56 để biết thêm thông tin


9
Tại sao cả onCreate và onStartCommand? Bạn có thể đặt nó trong onStartCommand không?
rcell

stopSelf không được gọi trước startForeground => hoặc trực tiếp sau, bởi vì nó dường như hủy "startForeground" khi bạn thực hiện.
Frank

1
Tôi đã thực hiện một số thử nghiệm và ngay cả khi onCreate không được gọi nếu dịch vụ đã được tạo, bạn không cần phải gọi startForeground nữa. Do đó, bạn chỉ có thể gọi nó trên phương thức onCreate chứ không phải trong onStartCommand. Có lẽ họ coi trường hợp dịch vụ duy nhất là ở phía trước sau cuộc gọi đầu tiên cho đến khi kết thúc.
Lxu

Tôi đang sử dụng 0 làm ID thông báo của cuộc gọi ban đầu startForeground. Thay đổi nó thành 1 khắc phục sự cố cho tôi (hy vọng)
mr5

vậy giải pháp là gì?
IgorGanapolsky

35

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.


Câu trả lời tốt nhưng có một vấn đề lớn, JobIntentServicechạy ngay IntentServicedưới Oreo, nhưng lên lịch cho một Công việc trên và trên Oreo, vì vậy JobIntentServicekhông bắt đầu ngay lập tức. Thông tin thêm
CopsOnRoad

1
@CopsOnRoad Nó rất hữu ích và nó bắt đầu ngay lập tức trong trường hợp của tôi, như tôi đã viết, nó được sử dụng cho các tương tác thời gian thực giữa điện thoại và smartwatch. Không có cách nào, một người dùng sẽ đợi 15 phút để gửi dữ liệu giữa hai người này. Hoạt động rất tốt và không bao giờ gặp sự cố.
Oleg Gryb

1
Âm thanh tuyệt vời, sẽ làm cho câu trả lời của bạn có giá trị nếu bạn có thể chia sẻ một số mã mẫu, tôi mới sử dụng Android và thấy rằng Job...cần có thời gian để bắt đầu và đó là phiên bản trước AlarmManager.
CopsOnRoad

2
Có lẽ tôi sẽ làm khi thời gian cho phép: Tôi sẽ cần phải ghép lại nó từ mã cụ thể tùy chỉnh và biên dịch lại một số libs Samsung độc quyền
Oleg Gryb

1
@batmaci - Dịch vụ công việc được sử dụng bởi SAP trong nội bộ. Xem Cập nhật 2 trong OP để biết chi tiết.
Oleg Gryb

32

Ứng dụng của bạn sẽ sập nếu bạn gọi Context.startForegroundService(...)và sau đó gọi Context.stopService(...)trước khi Service.startForeground(...)được gọi.

Tôi có một repro rõ ràng ở đây ForegroundServiceAPI26

Tôi đã mở một lỗi về vấn đề này tại: Trình theo dõi sự cố của Google

Một số lỗi về điều này đã được mở và đóng Không sửa.

Hy vọng của tôi với các bước repro rõ ràng sẽ làm cho việc cắt giảm.

Thông tin được cung cấp bởi nhóm google

Trình theo dõi vấn đề của Google Nhận xét 36

Đây không phải là một lỗi khung; đó là cố ý. Nếu ứng dụng khởi động một thể hiện dịch vụ startForegroundService(), nó phải chuyển thể hiện dịch vụ đó sang trạng thái nền trước và hiển thị thông báo. Nếu phiên bản dịch vụ bị dừng trước khi startForeground()được gọi trên đó, thì lời hứa đó không được thực hiện: đây là một lỗi trong ứng dụng.

Re # 31 , xuất bản một Dịch vụ mà các ứng dụng khác có thể bắt đầu trực tiếp về cơ bản là không an toàn. Bạn có thể giảm thiểu một chút bằng cách coi tất cả các hành động bắt đầu của dịch vụ đó là yêu cầu startForeground(), mặc dù rõ ràng đó có thể không phải là những gì bạn có trong tâm trí.

Trình theo dõi vấn đề của Google Nhận xét 56

Có một vài kịch bản khác nhau dẫn đến cùng một kết quả ở đây.

Vấn đề hoàn toàn về ngữ nghĩa, đó đơn giản chỉ là một lỗi khi khởi động một cái gì đó startForegroundService()mà bỏ qua việc thực sự chuyển nó sang tiền cảnh qua startForeground(), chỉ là: một vấn đề ngữ nghĩa. Điều đó được coi là một lỗi ứng dụng, cố ý. Dừng dịch vụ trước khi chuyển nó sang tiền cảnh là một lỗi ứng dụng. Đó là mấu chốt của OP và là lý do tại sao vấn đề này được đánh dấu là "hoạt động như dự định".

Tuy nhiên, cũng có những câu hỏi về phát hiện giả về vấn đề này. Điều đó đang được coi là một vấn đề thực sự, mặc dù nó được theo dõi riêng biệt với vấn đề theo dõi lỗi cụ thể này. Chúng tôi không điếc để khiếu nại.


2
Bản cập nhật tốt nhất mà tôi đã thấy làsuetracker.google.com/issues/76112072#comment56 . Tôi viết lại mã của mình để sử dụng Context.bindService để tránh hoàn toàn cuộc gọi có vấn đề đến Context.startForegroundService. Bạn có thể xem mã mẫu của tôi tại github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/
Kẻ

vâng, như tôi đã viết, liên kết sẽ hoạt động, nhưng tôi đã chuyển sang JobServive và sẽ không thay đổi bất cứ điều gì trừ khi cái này bị hỏng quá ':)
Oleg Gryb

20

Tôi đã nghiên cứu về điều này trong một vài ngày và có giải pháp. Bây giờ trong Android O, bạn có thể đặt giới hạn nền như bên dưới

Dịch vụ gọi một lớp dịch vụ

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

và lớp dịch vụ nên như thế nào

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
Bạn đã thử nó trong một số ứng dụng có ít nhất vài nghìn người dùng chưa? Tôi đã thử, một số người dùng vẫn gặp sự cố.
Alex

Không không tôi đang sử dụng mã chính xác và tôi không thấy các khách hàng gặp sự cố.
Ahmad Arslan

Bạn có bao nhiêu người dùng? Tôi đã thấy ~ 10-30 + sự cố hàng ngày (có lỗi) trong ứng dụng có 10k người dùng mỗi ngày. Tất nhiên, điều này chỉ xảy ra với người dùng với Android 8.0 và Android 8.1, trong trường hợp targetSdkVersion là 27. Tất cả các sự cố biến mất thành 0 sự cố ngay sau khi tôi đặt targetSdkVersion thành 25.
Alex

chỉ có 2200 người dùng: - / nhưng không gặp sự cố
Ahmad Arslan

1
@Jeeva, không hẳn vậy. Atm, tôi sử dụng targetSdkVersion 25 với compileSdkVersion 27. Nó vẻ thích cách tốt nhất sau rất nhiều thí nghiệm .... Hy vọng họ sẽ kết thúc developer.android.com/topic/libraries/architecture/... trước tháng 8 năm 2018 vì android-developers.googleblog .com / 2017/12 / Thẻ
Alex

16

Tôi có một tiện ích cập nhật tương đối thường xuyên khi thiết bị còn hoạt động và tôi đã thấy hàng ngàn sự cố chỉ sau vài ngày.

Vấn đề kích hoạt

Tôi thậm chí đã nhận thấy vấn đề ngay cả trên Pixel 3 XL của mình khi tôi không nghĩ thiết bị có tải nhiều. Và bất kỳ và tất cả các đường dẫn mã đã được bảo hiểm startForeground(). Nhưng sau đó tôi nhận ra rằng trong nhiều trường hợp, dịch vụ của tôi hoàn thành công việc thực sự nhanh chóng. Tôi tin rằng trình kích hoạt cho ứng dụng của tôi là dịch vụ đã hoàn tất trước khi hệ thống thực sự xuất hiện để hiển thị thông báo.

Giải pháp / giải pháp

Tôi đã có thể thoát khỏi tất cả các tai nạn. Những gì tôi đã làm là để loại bỏ cuộc gọi đến stopSelf(). (Tôi đã suy nghĩ về việc trì hoãn việc dừng cho đến khi tôi khá chắc chắn rằng thông báo đã được hiển thị, nhưng tôi không muốn người dùng thấy thông báo nếu không cần thiết.) Khi dịch vụ không hoạt động trong một phút hoặc hệ thống phá hủy nó bình thường mà không ném bất kỳ ngoại lệ.

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

11

Chỉ cần ngẩng cao đầu là tôi đã lãng phí quá nhiều giờ cho việc này. Tôi tiếp tục nhận được ngoại lệ này mặc dù tôi gọi startForeground(..)là điều đầu tiên onCreate(..). Cuối cùng, tôi thấy rằng vấn đề là do sử dụng NOTIFICATION_ID = 0. Sử dụng bất kỳ giá trị khác dường như để khắc phục điều này.


Trong trường hợp của tôi, tôi đang sử dụng ID 1, cảm ơn, vấn đề đã được giải quyết
Amin Pinjari

4
Tôi đang sử dụng ID 101, đôi khi tôi vẫn nhận được lỗi này.
CopsOnRoad

9

Tôi có một công việc xung quanh cho vấn đề này. Tôi đã xác minh sửa lỗi này trong ứng dụng của riêng tôi (300K + DAU), có thể giảm ít nhất 95% loại sự cố này, nhưng vẫn không thể tránh được 100% vấn đề này.

Sự cố này xảy ra ngay cả khi bạn đảm bảo gọi startForeground () ngay sau khi dịch vụ bắt đầu như tài liệu của Google. Có thể là do quá trình tạo và khởi tạo dịch vụ đã tốn hơn 5 giây trong nhiều tình huống, nên bất kể khi nào và ở đâu bạn gọi phương thức startForeground (), sự cố này là không thể tránh khỏi.

Giải pháp của tôi là đảm bảo rằng startForeground () sẽ được thực thi trong vòng 5 giây sau phương thức startForegroundService (), bất kể dịch vụ của bạn cần được tạo và khởi tạo trong bao lâu. Dưới đây là giải pháp chi tiết.

  1. Không sử dụng startForegroundService ở vị trí đầu tiên, sử dụng bindService () với cờ auto_create. Nó sẽ chờ khởi tạo dịch vụ. Đây là mã, dịch vụ mẫu của tôi là 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. Sau đây là triển khai 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. Phần quan trọng nhất, triển khai MusicService, phương thức ForceForeground () sẽ đảm bảo rằng phương thức startForeground () được gọi ngay sau 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. Nếu bạn muốn chạy đoạn mã bước 1 trong một mục đích đang chờ xử lý, chẳng hạn như nếu bạn muốn bắt đầu dịch vụ tiền cảnh trong một tiện ích (nhấp vào nút tiện ích) mà không cần mở ứng dụng của mình, bạn có thể bọc đoạn mã trong máy thu quảng bá và bắn một sự kiện phát sóng thay vì bắt đầu lệnh dịch vụ.

Đó là tất cả. Hy vọng nó giúp. Chúc may mắn.



7

Vì tất cả mọi người truy cập ở đây đều phải chịu đựng điều tương tự, tôi muốn chia sẻ giải pháp của mình mà chưa có ai từng thử trước đây (dù sao trong câu hỏi này). Tôi có thể đảm bảo với bạn rằng nó đang hoạt động, ngay cả trên một điểm dừng đã xác nhận phương pháp này.

Vấn đề là gọi Service.startForeground(id, notification)từ chính dịch vụ, phải không? Rất tiếc, Android Framework không đảm bảo gọi Service.startForeground(id, notification)trong vòng Service.onCreate()5 giây nhưng vẫn ném ngoại lệ, vì vậy tôi đã nghĩ ra cách này.

  1. Liên kết dịch vụ với ngữ cảnh với chất kết dính từ dịch vụ trước khi gọi Context.startForegroundService()
  2. Nếu liên kết thành công, hãy gọi Context.startForegroundService() từ kết nối dịch vụ và gọi ngay Service.startForeground() bên trong kết nối dịch vụ.
  3. LƯU Ý QUAN TRỌNG: Gọi Context.bindService()phương thức bên trong một lần thử vì trong một số trường hợp, cuộc gọi có thể đưa ra một ngoại lệ, trong trường hợp đó bạn cần dựa vào gọi Context.startForegroundService()trực tiếp và hy vọng nó sẽ không thất bại. Một ví dụ có thể là bối cảnh máy thu quảng bá, tuy nhiên việc lấy bối cảnh ứng dụng không tạo ra ngoại lệ trong trường hợp đó, nhưng sử dụng bối cảnh trực tiếp thì có.

Điều này thậm chí hoạt động khi tôi đang chờ điểm dừng sau khi ràng buộc dịch vụ và trước khi kích hoạt lệnh gọi "startForeground". Chờ trong khoảng 3-4 giây không kích hoạt ngoại lệ trong khi sau 5 giây, nó sẽ ném ngoại lệ. (Nếu thiết bị không thể thực thi hai dòng mã trong 5 giây, thì đã đến lúc ném nó vào thùng rác.)

Vì vậy, hãy bắt đầu với việc tạo kết nối dịch vụ.

// 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..");
    }
};

Trong dịch vụ của bạn, tạo một chất kết dính trả về thể hiện của dịch vụ của bạn.

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

Sau đó, cố gắng ràng buộc dịch vụ từ bối cảnh đó. Nếu thành công, nó sẽ gọi ServiceConnection.onServiceConnected()phương thức từ kết nối dịch vụ mà bạn đang sử dụng. Sau đó, xử lý logic trong mã được hiển thị ở trên. Một mã ví dụ sẽ trông như thế này:

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

Nó dường như đang làm việc trên các ứng dụng mà tôi phát triển, vì vậy nó cũng hoạt động khi bạn thử.


5

Sự cố với API Android O 26

Nếu bạn dừng dịch vụ ngay lập tức (vì vậy dịch vụ của bạn không thực sự chạy (từ ngữ / hiểu) và bạn đang đi theo khoảng ANR, bạn vẫn cần gọi startForeground trước khi dừng

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

Đã thử cách tiếp cận này nhưng nó vẫn tạo ra một lỗi: -

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

Tôi đang sử dụng điều này cho đến khi lỗi được giải quyết

mContext.startService(playIntent);

3
Phải là Util.SDK_INT> = 26, không chỉ lớn hơn
Andris

4

Tôi đang đối mặt với vấn đề tương tự và sau khi dành thời gian tìm thấy một solutons bạn có thể thử mã bên dưới. Nếu bạn đang sử dụng Servicethì hãy đặt mã này vào onCreate khác bằng cách sử dụng của bạn Intent Servicesau đó đặt mã này vào 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

Tôi đã nghiên cứu vấn đề này và đây là những gì tôi đã khám phá cho đến nay. Sự cố này có thể xảy ra nếu chúng ta có mã tương tự như sau:

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

Ngoại lệ được ném trong khối mã sau:

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

Phương pháp này được thực hiện trước khi onCreate()các MyForegroundServicevì lịch trình Android việc tạo ra các dịch vụ về xử lý chủ đề chính nhưng bringDownServiceLockedđược gọi trên BinderThread, mà là một điều kiện chủng tộc. Điều đó có nghĩa là MyForegroundServicekhông có cơ hội gọi startForegroundsẽ gây ra sự cố.

Để khắc phục điều này, chúng ta phải đảm bảo rằng bringDownServiceLockedkhông được gọi trước onCreate()của 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));
    }
}

Bằng cách sử dụng chương trình hấp dẫn, chúng tôi chắc chắn rằng phát sóng không bị lạc và stopReceivernhận được điểm dừng ý định ngay sau khi nó đã được đăng ký tại onCreate()các MyForegroundService. Đến lúc này chúng tôi đã gọi rồi startForeground(...). Chúng tôi cũng phải xóa chương trình phát dính đó để ngăn chặn thông báo dừng được thông báo vào lần tới.

Xin lưu ý rằng phương pháp sendStickyBroadcastnày không được dùng nữa và tôi chỉ sử dụng nó như một cách giải quyết tạm thời để khắc phục vấn đề này.


Bạn nên gọi nó thay vì context.stopService (serviceIntent) khi bạn muốn dừng dịch vụ.
makovkastar

4

Rất nhiều câu trả lời nhưng không ai làm việc trong trường hợp của tôi.

Tôi đã bắt đầu dịch vụ như thế này.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

Và trong dịch vụ của tôi trong 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);
    }

Và đừng quên đặt THÔNG BÁO_ID khác không

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

Vì vậy, mọi thứ đều hoàn hảo nhưng vẫn bị sập vào ngày 8.1, vì vậy nguyên nhân là như dưới đây.

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

Tôi đã gọi stop foreground với remove notificaton nhưng một khi thông báo đã xóa dịch vụ trở thành nền và dịch vụ nền không thể chạy trong Android O từ nền. bắt đầu sau khi nhận được đẩy.

Vì vậy, từ huyền diệu là

   stopSelf();

Cho đến nay, bất kỳ lý do nào khiến dịch vụ của bạn gặp sự cố, hãy làm theo tất cả các bước trên và tận hưởng.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } other {stopForeground (đúng); } Đây là cái gì nếu khác? cả hai trường hợp bạn đều làm như vậy, stopForeground (true);
dùng3135923

Tôi tin rằng bạn có nghĩa là đặt stopSelf (); bên trong khối khác thay vì stopForeground (true);
Justin Stanley

1
Có, @JustinStanley sử dụng stopSelf ();
cát

Không nên startForegroundđược gọi trong onCreate?
Aba

Tôi đang sử dụng lớp Thông báo và không phải lớp Thông báo. Làm thế nào tôi có thể thực hiện điều này?
Prajwal W

3

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

Tương tự như startService (Ý định), nhưng với một lời hứa ngầm rằng Dịch vụ sẽ gọi startForeground (int, android.app.Notification) khi nó bắt đầu chạy. Dịch vụ được cung cấp một lượng thời gian tương đương với khoảng ANR để thực hiện việc này, nếu không hệ thống sẽ tự động dừng dịch vụ và khai báo ứng dụng ANR.

Không giống như startService thông thường (Ý định), phương pháp này có thể được sử dụng bất cứ lúc nào, bất kể ứng dụng lưu trữ dịch vụ có ở trạng thái nền trước hay không.

hãy chắc chắn rằng bạn gọi Service.startForeground(int, android.app.Notification)onCreate () để bạn đảm bảo nó sẽ được gọi..nếu bạn có bất kỳ điều kiện nào có thể ngăn bạn làm điều đó, thì tốt hơn hết bạn nên sử dụng bình thường Context.startService(Intent)và gọi choService.startForeground(int, android.app.Notification) mình.

Có vẻ như Context.startForegroundService()thêm một cơ quan giám sát để đảm bảo bạn đã gọi Service.startForeground(int, android.app.Notification)trước khi nó bị phá hủy ...


3

Vui lòng không gọi bất kỳ dịch vụ StartForgroundService nào bên trong phương thức onCreate () , bạn phải gọi dịch vụ StartForground trong onStartCommand () sau khi tạo luồng công nhân nếu không bạn sẽ luôn nhận được ANR, vì vậy vui lòng không viết thông tin đăng nhập phức tạp trong luồng chính của 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);

    }
}

EDIT: Chú ý! Hàm startForeground không thể lấy 0 làm đối số đầu tiên, nó sẽ đưa ra một ngoại lệ! ví dụ này chứa hàm gọi sai, thay đổi 0 thành const của riêng bạn không thể bằng 0 hoặc lớn hơn Max (Int32)


2

Ngay cả sau khi gọi startForegroundtrong Service, Nó bị treo trên một số thiết bị nếu chúng ta gọi là stopServicengay trước khi onCreateđược gọi. Vì vậy, tôi đã khắc phục sự cố này bằng cách Bắt đầu dịch vụ với một cờ bổ sung:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

và thêm một kiểm tra trong onStartCommand để xem nếu nó đã bắt đầu dừng:

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

PS Nếu dịch vụ không chạy, nó sẽ khởi động dịch vụ trước, đây là chi phí hoạt động.


2

Bạn phải thêm quyền dưới dạng cho thiết bị Android 9 khi sử dụng mục tiêu sdk 28 trở lên hoặc ngoại lệ sẽ luôn xảy ra:

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

1

Tôi chỉ kiểm tra PendingIntentnull hoặc không trước khi gọi context.startForegroundService(service_intent)hàm.

cái này hiệu quả với tôi

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

Điều này thực sự chỉ đá dịch vụ cho startService chứ không phải startForegroundService nếu null. Câu lệnh if sẽ phải được lồng nhau, nếu không ứng dụng sẽ gặp sự cố khi sử dụng dịch vụ trên phiên bản mới hơn vào một lúc nào đó.
a54studio

1

chỉ cần gọi phương thức startForeground ngay sau khi Dịch vụ hoặc IntentService được tạo. như thế này:

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

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

1
NGOẠI TRỪ BẠC color = 0x00000000 vis = PRIVATE) tại android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) tại android.os.Handler.dispatchMessage (Handler.java:106) tại android.os.Looper.loop (Looper. java: 164) tại android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry

1

Ok, một cái gì đó tôi nhận thấy về điều này cũng có thể giúp một vài người khác. Điều này hoàn toàn từ thử nghiệm để xem liệu tôi có thể tìm ra cách khắc phục sự cố tôi đang thấy hay không. Để đơn giản, giả sử tôi có một phương pháp gọi phương thức này từ người trình bày.

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

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

Điều này sẽ sụp đổ với cùng một lỗi. Dịch vụ sẽ KHÔNG bắt đầu cho đến khi phương thức hoàn tất, do đó không có onCreate()trong dịch vụ.

Vì vậy, ngay cả khi bạn cập nhật giao diện người dùng ra khỏi luồng chính, NẾU bạn có bất cứ điều gì có thể theo kịp phương thức đó sau nó, nó sẽ không bắt đầu đúng giờ và cung cấp cho bạn Lỗi Tiền cảnh đáng sợ. Trong trường hợp của tôi, chúng tôi đã tải một số thứ lên hàng đợi và mỗi thứ được gọi startForegroundService, nhưng một số logic có liên quan đến từng thứ trong nền. Vì vậy, nếu logic mất quá nhiều thời gian để hoàn thành phương thức đó vì chúng được gọi trở lại, thời gian sụp đổ. Người giàstartService chỉ phớt lờ nó và tiếp tục đi và vì chúng tôi gọi nó mỗi lần, vòng tiếp theo sẽ kết thúc.

Điều này khiến tôi tự hỏi, nếu tôi gọi dịch vụ từ một luồng trong nền, nó có thể không bị ràng buộc hoàn toàn khi bắt đầu và chạy ngay lập tức, vì vậy tôi bắt đầu thử nghiệm. Mặc dù điều này KHÔNG khởi động nó ngay lập tức, nhưng nó không sụp đổ.

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

Tôi sẽ không giả vờ để biết tại sao nó không bị sập mặc dù tôi nghi ngờ điều này buộc nó phải đợi cho đến khi chủ đề chính có thể xử lý kịp thời. Tôi biết không lý tưởng để buộc nó vào luồng chính, nhưng vì cách sử dụng của tôi gọi nó ở chế độ nền, tôi không thực sự lo lắng nếu nó đợi cho đến khi nó có thể hoàn thành thay vì sụp đổ.


bạn đang bắt đầu dịch vụ của bạn từ nền tảng hoặc trong hoạt động?
Mateen Chaudhry

Lý lịch. Vấn đề là Android không thể đảm bảo dịch vụ sẽ bắt đầu trong thời gian quy định. Người ta sẽ nghĩ rằng họ sẽ chỉ đưa ra một lỗi để bỏ qua nếu bạn muốn, nhưng than ôi, không phải Android. Nó phải gây tử vong. Điều này đã giúp trong ứng dụng của chúng tôi, nhưng đã không khắc phục hoàn toàn.
a54studio

1

Tôi đang thêm một số mã trong câu trả lời @humazed. Vì vậy, không có thông báo ban đầu. Nó có thể là một cách giải quyết nhưng nó làm việc cho tôi.

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

Tôi đang thêm trong suốt Màu sắc trong biểu tượng nhỏ và màu sắc trên thông báo. Nó sẽ làm việc.


0

Tôi đã khắc phục sự cố với việc bắt đầu dịch vụ startService(intent)thay vì Context.startForeground()và gọi startForegound()ngay sau đó super.OnCreate(). Ngoài ra, nếu bạn bắt đầu dịch vụ khi khởi động, bạn có thể bắt đầu Hoạt động bắt đầu dịch vụ khi phát sóng khởi động. Mặc dù nó không phải là một giải pháp lâu dài, nhưng nó hoạt động.


trong một số thiết bị của Xiaomi, các hoạt động không thể được bắt đầu từ nền tảng
Mateen Chaudhry

0

Cập nhật dữ liệu trong onStartCommand(...)

onBind (...)

onBind(...)là một sự kiện vòng đời tốt hơn để bắt đầu startForegroundso với onCreate(...)onBind(...)vượt qua trong mộtIntent đó có thể chứa dữ liệu quan trọng Bundlecần thiết để khởi tạo Service. Tuy nhiên, không cần thiết như onStartCommand(...)được gọi khi Serviceđược tạo lần đầu tiên hoặc được gọi lần sau.

trênStartCommand (...)

startForeground trong onStartCommand(...) là quan trọng để cập nhật Servicemột khi nó đã được tạo.

Khi ContextCompat.startForegroundService(...)được gọi sau khi a Serviceđã được tạo onBind(...)onCreate(...)không được gọi. Do đó, dữ liệu cập nhật có thể được truyền onStartCommand(...)quaIntent Bundle để cập nhật dữ liệu trong Service.

Mẫu vật

Tôi đang sử dụng mẫu này để triển khai PlayerNotificationManagertrong Coinverse ứng dụng tin tức cryptocurrency.

Hoạt động / 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()
            })
        })

Dịch vụ âm thanh.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

Dịch vụ

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

    }

}

Kiểm thử

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

Kết quả

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.