Context.startForegroundService () tidak memanggil Service.startForeground ()

258

Saya menggunakan ServiceClass pada OS Android O.

Saya berencana untuk menggunakan Servicedi latar belakang.

The dokumentasi Android menyatakan bahwa

Jika aplikasi Anda menargetkan API level 26 atau lebih tinggi, sistem memberlakukan batasan untuk menggunakan atau membuat layanan latar belakang kecuali jika aplikasi itu sendiri ada di latar depan. Jika suatu aplikasi perlu membuat layanan latar depan, aplikasi tersebut harus memanggil startForegroundService().

Jika Anda menggunakan startForegroundService(), Servicemelempar kesalahan berikut.

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

Ada apa dengan ini?

Orang baik
sumber
TKI, berikan contoh minimal yang dapat direproduksi . Itu akan mencakup seluruh jejak tumpukan Java dan kode yang memicu crash.
CommonsWare
3
Bug masih ada di sini di API 26 dan 27 (27.0.3). Versi android yang terpengaruh adalah 8.0 dan 8.1 Anda dapat mengurangi jumlah kerusakan dengan menambahkan startForeground () baik di onCreate () dan onStartCommand (), tetapi kerusakan itu masih akan terjadi pada beberapa pengguna. Satu-satunya cara untuk memperbaikinya adalah targetSdkVersion 25 di build.gradle Anda.
Alex
1
Saya menggunakan solusi ini sekarang. bekerja seperti pesona theandroiddeveloper.com/blog/…
Prasad Pawar
1
Saya memiliki masalah yang sama. Saya memperbaiki masalah ini. Saya membagikan implementasi saya dalam topik ini stackoverflow.com/questions/55894636/...
Beyazid

Jawaban:

99

Dari Google docs di Android 8.0 perubahan perilaku :

Sistem ini memungkinkan aplikasi untuk memanggil Context.startForegroundService () bahkan saat aplikasi di latar belakang. Namun, aplikasi harus memanggil metode startForeground () layanan itu dalam waktu lima detik setelah layanan dibuat.

Solusi: Panggilan startForeground()di onCreate()untuk Serviceyang Anda gunakanContext.startForegroundService()

Lihat juga: Batas Eksekusi Latar Belakang untuk Android 8.0 (Oreo)

zhuzhumouse
sumber
19
Saya melakukan ini dalam onStartCommandmetode ini, tetapi saya masih mendapatkan kesalahan ini. Aku menelepon startForegroundService(intent)di saya MainActivity. Mungkin layanan dimulai terlalu lambat. Saya pikir batas lima detik seharusnya tidak ada sebelum mereka dapat menjanjikan layanan dimulai segera.
Kimi Chiu
29
Periode 5 detik jelas tidak cukup, pengecualian ini sangat sering terjadi pada sesi debug. Saya menduga itu juga AKAN terjadi dalam mode rilis dari waktu ke waktu. Dan menjadi PENGECUALIAN FATAL itu hanya crash aplikasi! Saya mencoba untuk menangkapnya dengan Thread.setDefaultUncaughtExceptionHandler (), tetapi bahkan untuk mendapatkannya di sana dan mengabaikan android membekukan aplikasi. Karena pengecualian ini dipicu dari handleMessage (), dan loop utama secara efektif berakhir ... mencari solusinya.
southerton
Saya memanggil metode Context.startForegroundService () di penerima siaran. jadi bagaimana menangani dalam situasi ini? karena onCreate () tidak tersedia di penerima siaran
Anand Savjani
6
@sherherton Saya pikir Anda harus memanggilnya onStartCommand()alih-alih onCreate, karena jika Anda menutup layanan dan memulainya lagi, itu mungkin masuk onStartCommand()tanpa menelepon onCreate...
pengembang android
1
Kami dapat memeriksa respons dari tim google di sini issuetracker.google.com/issues/76112072#comment56
Prags
82

Saya menelepon ContextCompat.startForegroundService(this, intent)untuk memulai layanan itu

Dalam pelayanan 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);
        }
}
humazed
sumber
47
Saya juga. Tapi saya masih mendapatkan kesalahan ini sesekali. Mungkin Android tidak dapat menjamin itu akan memanggil onCreatedalam 5 detik. Jadi mereka harus mendesain ulang sebelum memaksa kita mengikuti aturan.
Kimi Chiu
5
Saya menelepon startForeground di onStartCommand (), dan saya kadang-kadang mendapatkan kesalahan ini. Saya memindahkannya ke onCreate dan sejak itu saya belum melihatnya (menyilangkan jari).
Tyler
4
Ini membuat pemberitahuan lokal di Oreo yang mengatakan "APP_NAME sedang berjalan. Ketuk untuk menutup atau melihat info". Bagaimana cara berhenti menampilkan notifikasi itu?
Artis404
6
Tidak, Tim Android mengklaim ini adalah perilaku yang dimaksudkan. Jadi saya hanya mendesain ulang aplikasi saya. Ini konyol. Selalu perlu mendesain ulang aplikasi karena "perilaku yang diinginkan" ini. Itu adalah ketiga kalinya.
Kimi Chiu
12
Penyebab utama masalah ini adalah layanan dihentikan sebelum dipromosikan ke latar depan. Namun pernyataan itu tidak berhenti setelah layanan dihancurkan. Anda dapat mencoba mereproduksi ini dengan menambahkan StopServicesetelah menelepon startForegroundService.
Kimi Chiu
55

Mengapa masalah ini terjadi adalah karena kerangka kerja Android tidak dapat menjamin layanan Anda memulai dalam waktu 5 detik tetapi di sisi lain kerangka kerja memang memiliki batasan ketat pada pemberitahuan latar depan harus dipecat dalam waktu 5 detik, tanpa memeriksa apakah kerangka kerja telah mencoba untuk memulai layanan .

Ini jelas merupakan masalah kerangka kerja, tetapi tidak semua pengembang yang menghadapi masalah ini melakukan yang terbaik:

  1. startForegroundpemberitahuan harus ada di keduanya onCreatedan onStartCommand, karena jika layanan Anda sudah dibuat dan entah bagaimana aktivitas Anda mencoba untuk memulainya lagi, onCreatetidak akan dipanggil.

  2. ID notifikasi tidak boleh 0 jika tidak, crash yang sama akan terjadi walaupun itu bukan alasan yang sama.

  3. stopSelftidak boleh dipanggil sebelumnya startForeground.

Dengan semua hal di atas 3 masalah ini dapat dikurangi sedikit tetapi masih bukan perbaikan, perbaikan nyata atau katakanlah solusinya adalah menurunkan versi target sdk Anda menjadi 25.

Dan perhatikan bahwa kemungkinan besar Android P akan tetap membawa masalah ini karena Google menolak untuk bahkan memahami apa yang sedang terjadi dan tidak percaya ini adalah kesalahan mereka, baca # 36 dan # 56 untuk informasi lebih lanjut

ZhouX
sumber
9
Mengapa onCreate dan onStartCommand? Bisakah Anda letakkan saja di onStartCommand?
rcell
stopSelf tidak boleh dipanggil sebelum startForeground => atau langsung setelahnya, karena sepertinya membatalkan "startForeground" ketika Anda melakukannya.
Frank
1
Saya menjalankan beberapa tes, dan bahkan jika onCreate tidak dipanggil jika layanan sudah dibuat, Anda tidak perlu memanggil startForeground lagi. Karenanya Anda bisa menyebutnya hanya pada metode onCreate dan tidak di onStartCommand. Mungkin mereka menganggap layanan instance tunggal berada di latar depan setelah panggilan pertama sampai akhirnya.
Lxu
Saya menggunakan 0 sebagai ID notifikasi panggilan awal untuk startForeground. Mengubahnya menjadi 1 memperbaiki masalah bagi saya (mudah-mudahan)
mr5
Jadi apa solusinya?
IgorGanapolsky
35

Saya tahu, sudah terlalu banyak jawaban yang telah dipublikasikan, namun kenyataannya adalah - startForegroundService tidak dapat diperbaiki pada tingkat aplikasi dan Anda harus berhenti menggunakannya. Rekomendasi Google untuk menggunakan API Service # startForeground () dalam waktu 5 detik setelah Konteks # startForegroundService () dipanggil bukan sesuatu yang selalu bisa dilakukan oleh suatu aplikasi.

Android menjalankan banyak proses secara bersamaan dan tidak ada jaminan bahwa Looper akan memanggil layanan target Anda yang seharusnya memanggil startForeground () dalam waktu 5 detik. Jika layanan target Anda tidak menerima panggilan dalam 5 detik, Anda kurang beruntung dan pengguna Anda akan mengalami situasi PPA. Di jejak tumpukan Anda, Anda akan melihat sesuatu seperti ini:

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)

Seperti yang saya mengerti, Looper telah menganalisis antrian di sini, menemukan "pelaku" dan hanya membunuhnya. Sistem itu bahagia dan sehat sekarang, sementara pengembang dan pengguna tidak, tetapi karena Google membatasi tanggung jawab mereka pada sistem, mengapa mereka harus peduli pada dua yang terakhir? Rupanya tidak. Bisakah mereka membuatnya lebih baik? Tentu saja, misalnya mereka dapat melayani dialog "Aplikasi sedang sibuk", meminta pengguna untuk membuat keputusan tentang menunggu atau membunuh aplikasi, tetapi mengapa repot, itu bukan tanggung jawab mereka. Yang utama adalah sistemnya sehat sekarang.

Dari pengamatan saya, ini terjadi relatif jarang, dalam kasus saya sekitar 1 crash dalam sebulan untuk pengguna 1K. Mereproduksi itu tidak mungkin, dan bahkan jika direproduksi, tidak ada yang dapat Anda lakukan untuk memperbaikinya secara permanen.

Ada saran yang bagus di utas ini untuk menggunakan "bind" bukannya "start" dan kemudian ketika layanan siap, proses onServiceConnected, tapi sekali lagi, itu berarti tidak menggunakan panggilan startForegroundService sama sekali.

Saya pikir, tindakan yang benar dan jujur ​​dari pihak Google adalah untuk memberi tahu semua orang bahwa startForegourndServcie memiliki kekurangan dan tidak boleh digunakan.

Pertanyaannya tetap: apa yang harus digunakan? Untungnya bagi kami, ada JobScheduler dan JobService sekarang, yang merupakan alternatif yang lebih baik untuk layanan latar depan. Itu pilihan yang lebih baik, karena itu:

Saat pekerjaan sedang berjalan, sistem memegang wakelock atas nama aplikasi Anda. Karena alasan ini, Anda tidak perlu melakukan tindakan apa pun untuk menjamin bahwa perangkat tetap terjaga selama pekerjaan berlangsung.

Ini berarti bahwa Anda tidak perlu lagi peduli menangani wakelocks dan itulah mengapa itu tidak berbeda dengan layanan latar depan. Dari sudut pandang implementasi, JobScheduler bukan layanan Anda, ini adalah sistem, mungkin akan menangani antrian dengan benar, dan Google tidak akan pernah menghentikan anaknya sendiri :)

Samsung telah beralih dari startForegroundService ke JobScheduler dan JobService di Samsung Accessory Protocol (SAP) mereka. Ini sangat membantu ketika perangkat seperti jam tangan pintar perlu berbicara dengan host seperti ponsel, di mana pekerjaan itu perlu berinteraksi dengan pengguna melalui utas utama aplikasi. Karena pekerjaan diposting oleh penjadwal ke utas utama, itu menjadi mungkin. Namun Anda harus ingat bahwa pekerjaan sedang berjalan di utas utama dan melepas semua tugas berat ke utas lain dan tugas asinkron.

Layanan ini mengeksekusi setiap pekerjaan masuk pada Handler yang berjalan di utas utama aplikasi Anda. Ini berarti Anda harus melepaskan logika eksekusi Anda ke utas / penangan / AsyncTask lain yang Anda pilih

Satu-satunya perangkap beralih ke JobScheduler / JobService adalah Anda harus memperbaiki kode lama, dan itu tidak menyenangkan. Saya telah menghabiskan dua hari terakhir melakukan hal itu untuk menggunakan implementasi SAP Samsung yang baru. Saya akan menonton laporan kerusakan saya dan memberi tahu Anda jika melihat lagi macet. Secara teoritis itu seharusnya tidak terjadi, tetapi selalu ada detail yang mungkin tidak kita sadari.

UPDATE Tidak ada lagi crash yang dilaporkan oleh Play Store. Ini berarti bahwa JobScheduler / JobService tidak memiliki masalah seperti itu dan beralih ke model ini adalah pendekatan yang tepat untuk menghilangkan masalah startForegroundService sekali dan selamanya. Saya harap, Google / Android membacanya dan pada akhirnya akan memberikan komentar / saran / memberikan panduan resmi untuk semua orang.

PEMBARUAN 2

Untuk mereka yang menggunakan SAP dan bertanya bagaimana SAP V2 menggunakan penjelasan JobService ada di bawah ini.

Dalam kode khusus Anda, Anda perlu menginisialisasi SAP (ini Kotlin):

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

Sekarang Anda perlu mendekompilasi kode Samsung untuk melihat apa yang terjadi di dalamnya. Dalam SAAgentV2 lihat implementasi requestAgent dan baris berikut:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

Buka kelas SAAdapter sekarang dan temukan fungsiServiceConnectionRequested yang menjadwalkan pekerjaan menggunakan panggilan berikut:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService hanyalah sebuah implementasi dari Android'd JobService dan ini adalah salah satu yang melakukan penjadwalan pekerjaan:

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

Seperti yang Anda lihat, baris terakhir di sini menggunakan Android'd JobScheduler untuk mendapatkan layanan sistem ini dan untuk menjadwalkan pekerjaan.

Dalam panggilan requestAgent kami telah melewati mAgentCallback, yang merupakan fungsi panggilan balik yang akan menerima kontrol ketika peristiwa penting terjadi. Ini adalah bagaimana callback didefinisikan di aplikasi saya:

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 di sini adalah kelas yang telah saya terapkan untuk memproses semua permintaan yang datang dari smartwatch Samsung. Ini bukan kode lengkap, hanya kerangka:

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

    }

}

Seperti yang Anda lihat, MessageJobs membutuhkan kelas MessageSocket juga yang perlu Anda implementasikan dan yang memproses semua pesan yang berasal dari perangkat Anda.

Intinya, ini tidak sesederhana itu dan itu membutuhkan beberapa penggalian untuk internal dan pengkodean, tetapi bekerja, dan yang paling penting - tidak crash.

Oleg Gryb
sumber
Jawaban yang bagus tetapi ada masalah besar, JobIntentServicelangsung berjalan seperti di IntentServicebawah Oreo, tetapi menjadwalkan Pekerjaan di atas dan di atas Oreo, jadi JobIntentServicejangan langsung mulai. Info lebih lanjut
CopsOnRoad
1
@CopsOnRoad Ini sangat berguna dan segera dimulai dalam kasus saya, Seperti yang saya tulis, ini digunakan untuk interaksi waktu nyata antara ponsel dan jam tangan pintar. Tidak mungkin, pengguna akan menunggu 15 menit untuk mengirim data di antara keduanya. Bekerja dengan sangat baik dan tidak pernah crash.
Oleg Gryb
1
Kedengarannya keren, akan membuat jawaban Anda jauh lebih berharga jika Anda dapat membagikan beberapa kode sampel, saya baru di Android, dan ternyata Job...butuh waktu untuk memulai dan ini adalah versi lanjutan dari AlarmManager.
CopsOnRoad
2
Mungkin saya akan ketika waktu mengizinkan: Saya harus
menghapus
1
@batmaci - JobService digunakan oleh SAP secara internal. Lihat Perbarui 2 di OP untuk detailnya.
Oleg Gryb
32

Aplikasi Anda akan macet jika Anda menelepon Context.startForegroundService(...)dan kemudian menelepon Context.stopService(...)sebelumnyaService.startForeground(...) dipanggil.

Saya punya repro yang jelas di sini ForegroundServiceAPI26

Saya telah membuka bug tentang hal ini di: Pelacak masalah Google

Beberapa bug pada ini telah dibuka dan ditutup tidak akan diperbaiki.

Semoga langkah saya dengan repro yang jelas akan membuat cut.

Informasi disediakan oleh tim google

Pelacak masalah Google, Komentar 36

Ini bukan bug kerangka kerja; disengaja. Jika aplikasi memulai instance layanan startForegroundService(), itu harus mentransisikan instance layanan itu ke status latar depan dan menampilkan pemberitahuan. Jika instance layanan dihentikan sebelum startForeground()dipanggil, janji itu tidak terpenuhi: ini adalah bug dalam aplikasi.

Re # 31 , menerbitkan Layanan yang aplikasi lain dapat memulai secara langsung pada dasarnya tidak aman. Anda dapat mengurangi itu sedikit dengan memperlakukan semua tindakan awal layanan tersebut sebagai persyaratan startForeground(), meskipun jelas itu mungkin bukan yang Anda pikirkan.

Pelacak masalah Google, Komentar 56

Ada beberapa skenario berbeda yang mengarah pada hasil yang sama di sini.

Masalah semantik langsung, bahwa itu hanyalah kesalahan untuk memulai sesuatu startForegroundService()tetapi mengabaikan untuk benar-benar mengalihkannya ke latar depan startForeground(), hanya itu: masalah semantik. Itu diperlakukan sebagai bug aplikasi, sengaja. Menghentikan layanan sebelum beralih ke latar depan adalah kesalahan aplikasi. Itulah inti dari OP, dan itulah sebabnya masalah ini telah ditandai "berfungsi sebagaimana dimaksud."

Namun, ada juga pertanyaan tentang deteksi palsu dari masalah ini. Bahwa ini adalah diperlakukan sebagai masalah asli, meskipun makhluk itu dilacak secara terpisah dari masalah bug tracker tertentu. Kami tidak tuli terhadap keluhan.

swooby
sumber
2
Pembaruan terbaik yang saya lihat adalah issuetracker.google.com/issues/76112072#comment56 . Saya menulis ulang kode saya untuk menggunakan Context.bindService yang sepenuhnya menghindari panggilan bermasalah ke Context.startForegroundService. Anda dapat melihat kode sampel saya di github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby
ya, seperti yang saya tulis, bind harus bekerja, tetapi saya sudah beralih ke JobServive dan saya tidak akan mengubah apa pun kecuali yang ini juga rusak ':)
Oleg Gryb
20

Saya telah meneliti ini selama beberapa hari dan mendapatkan solusinya. Sekarang di Android O Anda dapat mengatur batasan latar belakang seperti di bawah ini

Layanan yang memanggil kelas layanan

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

dan kelas layanan harus seperti

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
    }
}
Ahmad Arslan
sumber
3
Apakah Anda mencobanya di beberapa aplikasi yang memiliki setidaknya beberapa ribu pengguna? Saya mencoba, beberapa pengguna masih memiliki masalah kerusakan.
Alex
Tidak, tidak, saya menggunakan kode yang tepat dan saya tidak melihat klien mendapatkan crash.
Ahmad Arslan
Berapa banyak pengguna yang Anda miliki? Saya melihat ~ 10-30 + crash setiap hari (dengan kesalahan) di aplikasi yang memiliki 10k pengguna per hari. Tentu saja, itu terjadi hanya untuk pengguna dengan android 8.0 dan android 8.1, jika targetSdkVersion adalah 27. Semua masalah menghilang hingga 0 crash tepat setelah saya menetapkan targetSdkVersion ke 25.
Alex
hanya 2.200 pengguna: - / tetapi tidak mendapatkan crash
Ahmad Arslan
1
@ Ya ampun, tidak juga. Atm, saya menggunakan targetSdkVersion 25 dengan compileSdkVersion 27. Sepertinya cara terbaik setelah banyak percobaan .... Semoga mereka akan menyelesaikan developer.android.com/topic/libraries/architecture/… sebelum Agustus 2018 karena android-developers.googleblog .com / 2017/12 / ...
Alex
16

Saya memiliki widget yang melakukan pembaruan yang relatif sering ketika perangkat terjaga dan saya melihat ribuan crash hanya dalam beberapa hari.

Pemicu masalah

Saya bahkan memperhatikan masalah ini bahkan pada Pixel 3 XL saya ketika saya tidak akan berpikir perangkat memiliki banyak beban sama sekali. Dan setiap dan semua jalur kode ditutupi startForeground(). Tetapi kemudian saya menyadari bahwa dalam banyak kasus layanan saya menyelesaikan pekerjaan dengan sangat cepat. Saya percaya pemicu untuk aplikasi saya adalah layanan selesai sebelum sistem benar-benar menunjukkan pemberitahuan.

Solusi / solusi

Saya bisa menyingkirkan semua crash. Apa yang saya lakukan adalah menghapus panggilan stopSelf(). (Saya sedang berpikir untuk menunda berhenti sampai saya cukup yakin notifikasi ditampilkan, tetapi saya tidak ingin pengguna melihat notifikasi jika tidak diperlukan.) Ketika layanan telah menganggur selama satu menit atau sistem menghancurkannya secara normal tanpa membuang pengecualian.

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

Hanya kepala ketika aku menghabiskan terlalu banyak waktu untuk ini. Saya terus mendapatkan pengecualian ini meskipun saya menelepon startForeground(..)sebagai yang pertama masuk onCreate(..). Pada akhirnya saya menemukan bahwa masalah itu disebabkan oleh penggunaan NOTIFICATION_ID = 0. Menggunakan nilai lain sepertinya memperbaiki ini.

tobalr
sumber
Dalam kasus saya, saya menggunakan ID 1, terima kasih, masalah terselesaikan
Amin Pinjari
4
Saya menggunakan ID 101, terkadang saya masih menerima kesalahan ini.
CopsOnRoad
9

Saya punya solusi untuk masalah ini. Saya telah memverifikasi perbaikan ini di aplikasi saya sendiri (300K + DAU), yang dapat mengurangi setidaknya 95% dari jenis kerusakan ini, tetapi masih tidak dapat 100% menghindari masalah ini.

Masalah ini terjadi bahkan ketika Anda memastikan untuk memanggil startForeground () tepat setelah layanan dimulai saat Google didokumentasikan. Mungkin karena proses pembuatan dan inisialisasi layanan sudah menghabiskan biaya lebih dari 5 detik dalam banyak skenario, maka kapan pun dan di mana Anda memanggil metode startForeground (), kerusakan ini tidak dapat dihindari.

Solusi saya adalah memastikan bahwa startForeground () akan dieksekusi dalam 5 detik setelah metode startForegroundService (), tidak peduli berapa lama layanan Anda harus dibuat dan diinisialisasi. Inilah solusi terperinci.

  1. Jangan gunakan startForegroundService di tempat pertama, gunakan bindService () dengan flag auto_create. Ini akan menunggu inisialisasi layanan. Ini kodenya, layanan sampel saya adalah 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. Maka di sini adalah implementasi 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. Bagian terpenting, implementasi MusicService, metode forceForeground () akan memastikan bahwa metode startForeground () dipanggil tepat setelah 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. Jika Anda ingin menjalankan potongan kode langkah 1 dalam niat yang tertunda, seperti jika Anda ingin memulai layanan latar depan dalam sebuah widget (klik pada tombol widget) tanpa membuka aplikasi Anda, Anda dapat membungkus potongan kode di penerima siaran , dan nyalakan acara siaran alih-alih memulai perintah layanan.

Itu semuanya. Semoga ini bisa membantu. Semoga berhasil.

Hexise
sumber
7

Karena semua orang yang berkunjung ke sini menderita hal yang sama, saya ingin membagikan solusi yang tidak pernah dicoba oleh orang lain sebelumnya (dalam pertanyaan ini). Saya dapat meyakinkan Anda bahwa itu berfungsi, bahkan pada breakpoint yang berhenti yang mengkonfirmasi metode ini.

Masalahnya adalah menelepon Service.startForeground(id, notification)dari layanan itu sendiri, bukan? Sayangnya, Kerangka Android tidak menjamin untuk memanggil Service.startForeground(id, notification)dalam Service.onCreate()5 detik, tetapi tetap melempar pengecualian, jadi saya datang dengan cara ini.

  1. Bind layanan ke konteks dengan pengikat dari layanan sebelum menelepon Context.startForegroundService()
  2. Jika ikatan berhasil, panggil Context.startForegroundService() dari koneksi layanan dan segera panggil Service.startForeground() di dalam koneksi layanan.
  3. CATATAN PENTING: Panggil Context.bindService()metode di dalam try-catch karena dalam beberapa kesempatan panggilan tersebut dapat menimbulkan pengecualian, dalam hal ini Anda harus mengandalkan panggilan Context.startForegroundService()langsung dan berharap itu tidak akan gagal. Contoh dapat berupa konteks penerima siaran, namun mendapatkan konteks aplikasi tidak menimbulkan pengecualian dalam kasus itu, tetapi menggunakan konteks secara langsung.

Ini bahkan berfungsi ketika saya sedang menunggu breakpoint setelah mengikat layanan dan sebelum memicu panggilan "startForeground". Menunggu antara 3-4 detik tidak memicu pengecualian sementara setelah 5 detik ia melempar pengecualian. (Jika perangkat tidak dapat menjalankan dua baris kode dalam 5 detik, maka sudah waktunya untuk membuangnya di tempat sampah.)

Jadi, mulailah dengan membuat koneksi layanan.

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

Di dalam layanan Anda, buat binder yang mengembalikan instance layanan Anda.

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

Kemudian, cobalah untuk mengikat layanan dari konteks itu. Jika berhasil, itu akan memanggil ServiceConnection.onServiceConnected()metode dari koneksi layanan yang Anda gunakan. Kemudian, tangani logika dalam kode yang ditunjukkan di atas. Contoh kode akan terlihat seperti ini:

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

Tampaknya berfungsi pada aplikasi yang saya kembangkan, jadi itu harus bekerja ketika Anda mencoba juga.

Furkan Yurdakul
sumber
5

Masalah dengan Android O API 26

Jika Anda menghentikan layanan segera (sehingga layanan Anda tidak benar-benar berjalan (kata-kata / pemahaman) dan Anda berada di bawah interval ANR, Anda masih perlu memanggil startForeground sebelum stopSelf

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

Mencoba Pendekatan Ini Tetapi Masih menciptakan kesalahan: -

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

Saya Menggunakan ini sampai Kesalahan Terselesaikan

mContext.startService(playIntent);
Andy Cass
sumber
3
Harus Util.SDK_INT> = 26, tidak hanya lebih besar
Andris
4

Saya menghadapi masalah yang sama dan setelah menghabiskan waktu menemukan soluton Anda dapat mencoba kode di bawah ini. Jika Anda menggunakan Servicemaka masukkan kode ini di onCreate menggunakan Anda Intent Servicekemudian masukkan kode ini di 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);
    }
Sambhaji Karad
sumber
4

Saya telah meneliti masalah ini dan inilah yang saya temukan sejauh ini. Kecelakaan ini dapat terjadi jika kami memiliki kode yang mirip dengan ini:

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

Pengecualian dilemparkan dalam blok kode berikut:

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

Metode ini dilaksanakan sebelum onCreate()dari MyForegroundServicekarena jadwal Android penciptaan layanan pada handler thread utama tetapi bringDownServiceLockeddisebut pada BinderThread, yang merupakan kondisi lomba. Itu berarti bahwa MyForegroundServicetidak memiliki kesempatan untuk menelepon startForegroundyang akan menyebabkan crash.

Untuk memperbaiki ini kita harus memastikan bahwa bringDownServiceLockedtidak disebut sebelumnya onCreate()dari 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));
    }
}

Dengan menggunakan siaran lengket kami memastikan bahwa siaran tidak tersesat dan stopReceivermenerima berhenti niat secepat itu telah terdaftar di onCreate()dari MyForegroundService. Saat ini kita sudah menelepon startForeground(...). Kami juga harus menghapus siaran lengket itu untuk mencegah stopReceiver diberitahukan lain kali.

Harap dicatat bahwa metode sendStickyBroadcastini sudah usang dan saya menggunakannya hanya sebagai solusi sementara untuk memperbaiki masalah ini.

makovkastar
sumber
Anda harus memanggilnya alih-alih context.stopService (serviceIntent) ketika Anda ingin menghentikan layanan.
makovkastar
4

Begitu banyak jawaban tetapi tidak ada yang berhasil dalam kasus saya.

Saya sudah mulai layanan seperti ini.

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

Dan dalam layanan saya di 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);
    }

Dan jangan lupa untuk mengatur NOTIFICATION_ID bukan nol

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

SO semuanya sempurna tetapi masih crash pada 8.1 jadi penyebabnya adalah seperti di bawah ini.

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

Saya telah menelepon stop foreground dengan menghapus notificaton tetapi sekali pemberitahuan dihapus layanan menjadi latar belakang dan layanan latar belakang tidak dapat berjalan di android O dari latar belakang. dimulai setelah push diterima.

Jadi kata ajaibnya

   stopSelf();

Sejauh ini alasan apa pun layanan Anda macet, ikuti semua langkah di atas dan nikmati.

berpasir
sumber
if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } untuk apa ini apa lagi? kedua kasus Anda melakukan hal yang sama, stop Foreground (true);
user3135923
Saya yakin Anda bermaksud menghentikan stopSelf (); di dalam blok else bukan stopForeground (true);
Justin Stanley
1
Ya, @JustinStanley menggunakan stopSelf ();
sandy
Bukankah startForegroundseharusnya dipanggil onCreate?
Aba
Saya menggunakan NotificationManager dan bukan Notification class. Bagaimana saya bisa menerapkan ini?
Prajwal W
3

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

Mirip dengan startService (Intent), tetapi dengan janji tersirat bahwa Layanan akan memanggil startForeground (int, android.app.Notification) setelah mulai berjalan. Layanan diberi jumlah waktu yang sebanding dengan interval ANR untuk melakukan ini, jika tidak, sistem akan secara otomatis menghentikan layanan dan menyatakan aplikasi ANR.

Berbeda dengan startService biasa (Intent), metode ini dapat digunakan kapan saja, terlepas dari apakah aplikasi yang meng-hosting layanan ini dalam keadaan foreground.

pastikan Anda memanggil Service.startForeground(int, android.app.Notification)onCreate () sehingga Anda memastikan itu akan dipanggil..Jika Anda memiliki kondisi apa pun yang dapat mencegah Anda melakukan itu, maka Anda sebaiknya menggunakan yang normal Context.startService(Intent)dan menelepon Service.startForeground(int, android.app.Notification)diri sendiri.

Tampaknya Context.startForegroundService()menambahkan pengawas untuk memastikan Anda menelepon Service.startForeground(int, android.app.Notification)sebelum dihancurkan ...

Alécio Carvalho
sumber
3

Tolong jangan panggil StartForgroundServices apa pun di dalam metode onCreate () , Anda harus memanggil layanan StartForground di onStartCommand () setelah membuat utas pekerja jika tidak Anda akan selalu mendapatkan ANR, jadi jangan menulis login kompleks di utas utama 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: Perhatian! fungsi startForeground tidak dapat mengambil 0 sebagai argumen pertama, itu akan memunculkan pengecualian! contoh ini berisi panggilan fungsi yang salah, ubah 0 ke const Anda sendiri yang tidak bisa 0 atau lebih besar dari Max (Int32)

Shalu Gupta
sumber
2

Bahkan setelah memanggil startForegrounddi Service, itu crash pada beberapa perangkat jika kita sebut stopServicesebelum onCreatedisebut. Jadi, saya memperbaiki masalah ini dengan Memulai layanan dengan bendera tambahan:

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

dan menambahkan tanda centang di onStartCommand untuk melihat apakah sudah mulai berhenti:

@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 Jika layanan tidak berjalan, itu akan memulai layanan pertama, yang merupakan overhead.

Gaurav Singla
sumber
2

Anda harus menambahkan izin sebagai di bawah ini untuk perangkat Android 9 saat menggunakan target sdk 28 atau lebih baru atau pengecualian akan selalu terjadi:

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

Saya baru saja memeriksa PendingIntentnol atau tidak sebelum memanggil context.startForegroundService(service_intent) fungsi.

ini bekerja untuk saya

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);
        }
}
SaimumIslam27
sumber
Ini sebenarnya hanya menendang layanan ke startService daripada startForegroundService jika nol. Pernyataan if harus disarangkan, jika tidak aplikasi akan macet saat mencoba menggunakan layanan pada versi yang lebih baru di beberapa titik.
a54studio
1

panggil saja metode startForeground segera setelah Layanan atau IntentService Dibuat. seperti ini:

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

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
Hossein Karami
sumber
1
PENGECUALIAN FATAL: android.app.RemoteServiceException utama: pemberitahuan buruk untuk startSeground: java.lang.RuntimeException: saluran tidak valid untuk pemberitahuan layanan: Pemberitahuan (saluran = null pri = 0 contentView = null vibrate = null sound = null default = 0x0 flags = 0x40 flags = 0x40 flag = 0x40 flags color = 0x00000000 vis = PRIVATE) di android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) di android.os.Handler.dispatchMessage (Handler.java:106) di android.os.Looper.loop (Looper). java: 164) di android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry
1

Ok, sesuatu yang saya perhatikan pada hal ini mungkin dapat membantu beberapa orang lain juga. Ini semata-mata dari pengujian untuk melihat apakah saya bisa mencari cara untuk memperbaiki kejadian yang saya lihat. Demi kesederhanaan, katakanlah saya memiliki metode yang memanggil ini dari presenter.

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

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

Ini akan macet dengan kesalahan yang sama. Layanan TIDAK akan mulai sampai metode selesai, oleh karena itu tidak ada onCreate()dalam layanan.

Jadi, bahkan jika Anda memperbarui UI dari utas utama, JIKA Anda memiliki sesuatu yang mungkin menahan metode itu setelah itu, itu tidak akan mulai tepat waktu dan memberi Anda Foreground Error yang ditakuti. Dalam kasus saya, kami memuat beberapa hal ke antrian dan masing-masing memanggil startForegroundService, tetapi beberapa logika terlibat dengan masing-masing di latar belakang. Jadi jika logika terlalu lama untuk menyelesaikan metode itu karena mereka dipanggil kembali ke belakang, waktu macet. Yang lama startServicehanya mengabaikannya dan melanjutkannya dan karena kami menyebutnya setiap kali, babak selanjutnya akan selesai.

Ini membuat saya bertanya-tanya, jika saya memanggil layanan dari utas di latar belakang, tidak bisakah itu sepenuhnya terikat pada awal dan berjalan segera, jadi saya mulai bereksperimen. Meskipun ini TIDAK memulainya segera, itu tidak crash.

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

Saya tidak akan berpura-pura tahu mengapa itu tidak crash meskipun saya curiga ini memaksanya menunggu sampai utas utama dapat menanganinya tepat waktu. Saya tahu itu tidak ideal untuk mengikatnya ke utas utama, tetapi karena penggunaan saya menyebutnya di latar belakang, saya tidak benar-benar khawatir jika menunggu sampai dapat menyelesaikan daripada crash.

a54studio
sumber
Anda memulai layanan Anda dari latar belakang atau dalam aktivitas?
Mateen Chaudhry
Latar Belakang. Masalahnya adalah bahwa Android tidak dapat menjamin layanan akan mulai dalam waktu yang ditentukan. Orang akan berpikir mereka hanya akan membuang kesalahan untuk mengabaikannya jika Anda mau, tapi sayangnya, bukan Android. Itu harus berakibat fatal. Ini membantu dalam aplikasi kami, tetapi tidak benar-benar memperbaikinya.
a54studio
1

Saya menambahkan beberapa kode dalam jawaban @humazed. Jadi tidak ada notifikasi awal. Ini mungkin merupakan solusi tetapi itu bekerja untuk saya.

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

Saya menambahkan TransparentColor dalam ikon kecil dan warna pada notifikasi. Itu akan berhasil.

KHEM RAJ MEENA
sumber
0

Saya telah memperbaiki masalah dengan memulai layanan dengan startService(intent)alih - alih Context.startForeground()dan menelepon startForegound()segera setelah itu super.OnCreate(). Selain itu, jika Anda memulai layanan saat boot, Anda dapat memulai Aktivitas yang memulai layanan pada siaran boot. Meskipun ini bukan solusi permanen, ia bekerja.

melianor
sumber
di beberapa perangkat Xiaomi kegiatan tidak dapat dimulai dari latar belakang
Mateen Chaudhry
0

Memperbarui Data di onStartCommand(...)

onBind (...)

onBind(...)adalah peristiwa siklus hidup yang lebih baik untuk memulai startForegroundvs onCreate(...)karena onBind(...)melewati Intentyang dapat berisi data penting yang Bundlediperlukan untuk menginisialisasi Service. Namun, itu tidak perlu seperti onStartCommand(...)yang disebut ketika Servicedibuat untuk pertama kalinya atau disebut setelah waktu berikutnya.

onStartCommand (...)

startForegroundin onStartCommand(...)penting untuk memperbarui Servicesetelah itu telah dibuat.

Kapan ContextCompat.startForegroundService(...)dipanggil setelah a Servicetelah dibuat onBind(...)dan onCreate(...)tidak dipanggil. Oleh karena itu, data yang diperbarui dapat diteruskan ke onStartCommand(...)melalui Intent Bundleuntuk memperbarui data dalam Service.

Sampel

Saya menggunakan pola ini untuk menerapkan PlayerNotificationManagerdi aplikasi berita cryptocurrency Coinverse .

Activity / 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)
}
Adam Hurwitz
sumber
-1

Layanan

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

    }

}

Penguji

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

Hasil

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
JeanK
sumber