startForeground gagal setelah meningkatkan ke Android 8.1

193

Setelah memutakhirkan telepon saya ke 8.1 Pengembang Pratinjau layanan latar belakang saya tidak lagi dijalankan dengan benar.

Dalam layanan jangka panjang saya, saya telah menerapkan metode startForeground untuk memulai pemberitahuan yang sedang berlangsung yang dipanggil pada buat.

    @TargetApi(Build.VERSION_CODES.O)
private fun startForeground() {
    // Safe call, handled by compat lib.
    val notificationBuilder = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)

    val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .build()
    startForeground(101, notification)
}

Pesan eror:

11-28 11:47:53.349 24704-24704/$PACKAGE_NAMEE/AndroidRuntime: FATAL EXCEPTION: main
    Process: $PACKAGE_NAME, PID: 24704
    android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=My channel pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x42 color=0x00000000 vis=PRIVATE)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

saluran yang tidak valid untuk pemberitahuan layanan , tampaknya saluran lama saya DEFAULT_CHANNEL_ID tidak lagi sesuai untuk API 27 Saya berasumsi. Apa saluran yang tepat? Saya sudah mencoba melihat melalui dokumentasi

Rawa
sumber
1
Jawaban ini adalah solusi saya.
Alex Jolig

Jawaban:

230

Setelah beberapa kali bermain-main dengan solusi yang berbeda saya menemukan bahwa seseorang harus membuat saluran notifikasi di Android 8.1 dan di atasnya.

private fun startForeground() {
    val channelId =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel("my_service", "My Background Service")
            } else {
                // If earlier version channel ID is not used
                // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
                ""
            }

    val notificationBuilder = NotificationCompat.Builder(this, channelId )
    val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(PRIORITY_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
    startForeground(101, notification)
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
    val chan = NotificationChannel(channelId,
            channelName, NotificationManager.IMPORTANCE_NONE)
    chan.lightColor = Color.BLUE
    chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
    val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    service.createNotificationChannel(chan)
    return channelId
}

Dari pemahaman saya, layanan latar belakang kini ditampilkan sebagai notifikasi normal yang kemudian dapat dipilih pengguna untuk tidak ditampilkan dengan membatalkan pilihan saluran notifikasi.

Perbarui : Juga jangan lupa untuk menambahkan izin latar depan seperti yang dipersyaratkan Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
sumber
Apakah kita perlu melakukan perubahan ini dalam hal JobIntentService? Atau sedang menanganinya secara internal?
Amrut
1
mengapa tidak IMPORTANCE_DEFAULTbukan IMPORTANCE_NONE?
user924
1
@ user924 Kotlin sebenarnya adalah bahasa yang lebih baru daripada Swift. Kotlin tidak menggantikan Java, itu hanya sebuah alternatif untuk Java untuk pengembangan Android. Jika Anda mencobanya, Anda akan melihat itu sebenarnya sangat mirip dalam sintaksis dengan Swift. Saya pribadi percaya itu lebih baik daripada Jawa, meskipun apa yang dikatakan indeks Tiobe (indeks tunduk pada sedikit bias non-respons). Ini memperbaiki banyak masalah yang dimiliki Java, termasuk NullPointerException yang ditakuti, kata kerja, dan beberapa hal lainnya. Menurut Google I / O terbaru, 95% pengembang yang menggunakan Kotlin untuk Android senang dengan itu.
Sub 6 Sumber Daya
3
Ini harus dipanggil dari onCreate () dari layanan Anda
Evgenii Vorobei
1
@Rawa Yah bahkan saya tidak yakin apa yang Anda lakukan dengan layanan Foreground Anda di aplikasi karena Dokumentasi tidak bohong. Ini dengan jelas menyatakan, Anda akan mendapatkan SecurityException jika Anda mencoba membuat layanan latar depan tanpa izin dalam manifes.
CopsOnRoad
134

Solusi Java (Android 9.0, API 28)

Di Servicekelas Anda , tambahkan ini:

@Override
public void onCreate(){
    super.onCreate();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        startMyOwnForeground();
    else
        startForeground(1, new Notification());
}

private void startMyOwnForeground(){
    String NOTIFICATION_CHANNEL_ID = "com.example.simpleapp";
    String channelName = "My Background Service";
    NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
    chan.setLightColor(Color.BLUE);
    chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    assert manager != null;
    manager.createNotificationChannel(chan);

    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
    Notification notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.drawable.icon_1)
            .setContentTitle("App is running in background")
            .setPriority(NotificationManager.IMPORTANCE_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build();
    startForeground(2, notification);
}

PEMBARUAN: ANDROID 9.0 PIE (API 28)

Tambahkan izin ini ke AndroidManifest.xmlfile Anda :

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
CopsOnRoad
sumber
Apakah ada alasan untuk menggunakan ID unik dalam dua panggilan startForeground ()? Tidak bisakah mereka sama di sini karena notifikasi yang sama?
Cody
@CopsOnRoad jadi tidak perlu saluran pemberitahuan untuk O?
Shruti
2
@Shruti Anda perlu menambahkan izin bersama dengan kode untuk Android 9.0. Keduanya dibutuhkan.
CopsOnRoad
1
@CopsOnRoad Ini adalah pengecualian 'Pengecualian Fatal: android.app.RemoteServiceException: Context.startForegroundService () kemudian tidak memanggil Service.startForeground ()'
Shruti
2
Apakah mungkin untuk menghindari menampilkan pemberitahuan saat layanan sedang berjalan?
matdev
29

Jawaban pertama sangat bagus hanya untuk orang-orang yang tahu kotlin, bagi mereka yang masih menggunakan java di sini saya menerjemahkan jawaban pertama

 public Notification getNotification() {
        String channel;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            channel = createChannel();
        else {
            channel = "";
        }
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, channel).setSmallIcon(android.R.drawable.ic_menu_mylocation).setContentTitle("snap map fake location");
        Notification notification = mBuilder
                .setPriority(PRIORITY_LOW)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();


        return notification;
    }

    @NonNull
    @TargetApi(26)
    private synchronized String createChannel() {
        NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

        String name = "snap map fake location ";
        int importance = NotificationManager.IMPORTANCE_LOW;

        NotificationChannel mChannel = new NotificationChannel("snap map channel", name, importance);

        mChannel.enableLights(true);
        mChannel.setLightColor(Color.BLUE);
        if (mNotificationManager != null) {
            mNotificationManager.createNotificationChannel(mChannel);
        } else {
            stopSelf();
        }
        return "snap map channel";
    } 

Untuk android, P jangan lupa untuk menyertakan izin ini

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
sumber
Terima kasih telah menerjemahkan kode ke Java. Ini sangat membantu proyek-proyek Java!
Ray Li
17

Bekerja dengan baik di Andorid 8.1:

Sampel yang diperbarui (tanpa kode yang ditinggalkan):

public NotificationBattery(Context context) {
    this.mCtx = context;

    mBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle(context.getString(R.string.notification_title_battery))
            .setSmallIcon(R.drawable.ic_launcher)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setChannelId(CHANNEL_ID)
            .setOnlyAlertOnce(true)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setWhen(System.currentTimeMillis() + 500)
            .setGroup(GROUP)
            .setOngoing(true);

    mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_view_battery);

    initBatteryNotificationIntent();

    mBuilder.setContent(mRemoteViews);

    mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

    if (AesPrefs.getBooleanRes(R.string.SHOW_BATTERY_NOTIFICATION, true)) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.notification_title_battery),
                    NotificationManager.IMPORTANCE_DEFAULT);
            channel.setShowBadge(false);
            channel.setSound(null, null);
            mNotificationManager.createNotificationChannel(channel);
        }
    } else {
        mNotificationManager.cancel(Const.NOTIFICATION_CLIPBOARD);
    }
}

Potongan lama (ini adalah aplikasi yang berbeda - tidak terkait dengan kode di atas):

@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
    Log.d(TAG, "onStartCommand");

    String CHANNEL_ONE_ID = "com.kjtech.app.N1";
    String CHANNEL_ONE_NAME = "Channel One";
    NotificationChannel notificationChannel = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
                CHANNEL_ONE_NAME, IMPORTANCE_HIGH);
        notificationChannel.enableLights(true);
        notificationChannel.setLightColor(Color.RED);
        notificationChannel.setShowBadge(true);
        notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.createNotificationChannel(notificationChannel);
    }

    Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    Notification notification = new Notification.Builder(getApplicationContext())
            .setChannelId(CHANNEL_ONE_ID)
            .setContentTitle(getString(R.string.obd_service_notification_title))
            .setContentText(getString(R.string.service_notification_content))
            .setSmallIcon(R.mipmap.ic_launcher)
            .setLargeIcon(icon)
            .build();

    Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);

    startForeground(START_FOREGROUND_ID, notification);

    return START_STICKY;
}
Martin Pfeffer
sumber
2
Bagian dari kode di atas sekarang sudah usang, yang dapat Anda atasi dengan mengubah Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...keNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-geoengineering
1
@ larangan geoengineering Anda benar sekali ... Saya menambahkan kode sampel baru. Terima kasih.
Martin Pfeffer
mengapa PRIORITY_MAXapa yang lebih baik untuk digunakan?
user924
7

Dalam kasus saya, itu karena kami mencoba mengirim pemberitahuan tanpa menentukan NotificationChannel:

public static final String NOTIFICATION_CHANNEL_ID_SERVICE = "com.mypackage.service";
public static final String NOTIFICATION_CHANNEL_ID_TASK = "com.mypackage.download_info";

public void initChannel(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, "App Service", NotificationManager.IMPORTANCE_DEFAULT));
        nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, "Download Info", NotificationManager.IMPORTANCE_DEFAULT));
    }
}

Tempat terbaik untuk meletakkan kode di atas adalah dalam onCreate()metode di Applicationkelas, sehingga kita hanya perlu mendeklarasikannya sekali untuk semua:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        initChannel();
    }
}

Setelah kami mengatur ini, kami dapat menggunakan pemberitahuan dengan yang channelIdbaru saja kami tentukan:

Intent i = new Intent(this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID_INFO);
            .setContentIntent(pi)
            .setWhen(System.currentTimeMillis())
            .setContentTitle("VirtualBox.exe")
            .setContentText("Download completed")
            .setSmallIcon(R.mipmap.ic_launcher);

Kemudian, kita dapat menggunakannya untuk mengirim pemberitahuan:

int notifId = 45;
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify(notifId, builder.build());

Jika Anda ingin menggunakannya sebagai notifikasi layanan foreground:

startForeground(notifId, builder.build());
Anggrayudi H
sumber
1
Haruskah NOTIFICATION_CHANNEL_ID_TASK konstan (baris ke-2) menjadi NOTIFICATION_CHANNEL_ID_INFO?
Timores
@Timores, tidak. Anda dapat menggantinya dengan konstanta Anda sendiri.
Anggrayudi H
4

Berkat @CopsOnRoad, solusinya sangat membantu tetapi hanya bekerja untuk SDK 26 dan lebih tinggi. Target aplikasi saya 24 dan lebih tinggi.

Agar Android Studio tidak mengeluh, Anda memerlukan persyaratan langsung di sekitar notifikasi. Tidak cukup pintar untuk mengetahui kode tersebut berada dalam metode yang bersyarat ke VERSION_CODE.O.

@Override
public void onCreate(){
    super.onCreate();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        startMyOwnForeground();
    else
        startForeground(1, new Notification());
}

private void startMyOwnForeground(){

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){

        String NOTIFICATION_CHANNEL_ID = "com.example.simpleapp";
        String channelName = "My Background Service";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert manager != null;
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        Notification notification = notificationBuilder.setOngoing(true)
                .setSmallIcon(AppSpecific.SMALL_ICON)
                .setContentTitle("App is running in background")
                .setPriority(NotificationManager.IMPORTANCE_MIN)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }
}
MobileMateo
sumber
Bisakah Anda menjelaskan perubahan apa yang Anda buat dalam kode ini, saya tidak mengerti.
CopsOnRoad
Versi 8.0 dan Android Pie berfungsi dengan sempurna. Tetapi mengapa kita membutuhkan saluran Pemberitahuan hanya untuk versi 8.1?
Thamarai T
2

Ini berhasil untuk saya. Di kelas layanan saya, saya membuat saluran notifikasi untuk android 8.1 seperti di bawah ini:

public class Service extends Service {

    public static final String NOTIFICATION_CHANNEL_ID_SERVICE = "com.package.MyService";
    public static final String NOTIFICATION_CHANNEL_ID_INFO = "com.package.download_info";

    @Override
    public void onCreate() {

        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, "App Service", NotificationManager.IMPORTANCE_DEFAULT));
            nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, "Download Info", NotificationManager.IMPORTANCE_DEFAULT));
        } else {
            Notification notification = new Notification();
            startForeground(1, notification);
        }
    }
}

Catatan: Buat saluran tempat Anda membuat Notifikasi untuk Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Arjun
sumber
-1

Ini solusinya

private static final int NOTIFICATION_ID = 200;
private static final String CHANNEL_ID = "myChannel";
private static final String CHANNEL_NAME = "myChannelName";

private void startForeground() {

    final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
            getApplicationContext(), CHANNEL_ID);

    Notification notification;



        notification = mBuilder.setTicker(getString(R.string.app_name)).setWhen(0)
                .setOngoing(true)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Send SMS gateway is running background")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setShowWhen(true)
                .build();

        NotificationManager notificationManager = (NotificationManager) getApplication().getSystemService(Context.NOTIFICATION_SERVICE);

        //All notifications should go through NotificationChannel on Android 26 & above
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    CHANNEL_NAME,
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);

        }
        notificationManager.notify(NOTIFICATION_ID, notification);

    }

Semoga ini akan membantu :)

Shahriar Nasim Nafi
sumber
1
Mohon luangkan waktu untuk menjelaskan alasan solusi Anda.
straya