startForeground falla después de actualizar a Android 8.1

193

Después de actualizar mi teléfono a 8.1 Developer Preview, mi servicio en segundo plano ya no se inicia correctamente.

En mi servicio de larga ejecución, he implementado un método startForeground para iniciar la notificación en curso que se llama al crear.

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

Mensaje de error:

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)

canal no válido para notificación de servicio , aparentemente mi canal anterior, el DEFAULT_CHANNEL_ID, ya no es apropiado para API 27, supongo. ¿Cuál sería el canal adecuado? He intentado revisar la documentación.

Rawa
fuente
1
Esta respuesta fue mi solución.
Alex Jolig

Respuestas:

231

Después de algunos retoques por un tiempo con diferentes soluciones, descubrí que uno debe crear un canal de notificación en Android 8.1 y superior.

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
}

Según tengo entendido, los servicios en segundo plano ahora se muestran como notificaciones normales que el usuario puede seleccionar para no mostrar anulando la selección del canal de notificación.

Actualización : Tampoco olvides agregar el permiso de primer plano según sea necesario Android P:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Rawa
fuente
¿Necesitamos hacer estos cambios en caso de JobIntentService? ¿O lo está manejando internamente?
Amrut
1
¿Por qué no en IMPORTANCE_DEFAULTlugar de IMPORTANCE_NONE?
user924
1
@ user924 Kotlin es en realidad un lenguaje más nuevo que Swift. Kotlin no reemplaza Java, es solo una alternativa a Java para el desarrollo de Android. Si lo prueba, verá que en realidad es bastante similar en sintaxis a Swift. Personalmente, creo que es mejor que Java, a pesar de lo que dice el índice Tiobe (el índice está sujeto a un poco de sesgo de no respuesta). Soluciona muchos de los problemas que tiene Java, incluida la temida NullPointerException, la verbosidad y muchas otras cosas. Según el último Google I / O, el 95% de los desarrolladores que usan Kotlin para Android están contentos con él.
Sub 6 Resources
3
Esto debería llamarse desde onCreate () de su servicio
Evgenii Vorobei
1
@Rawa Bueno, incluso yo no estoy seguro de lo que está haciendo con su servicio de primer plano en la aplicación porque la documentación no miente. Establece claramente que obtendrá SecurityException si intenta crear un servicio en primer plano sin el permiso en el manifiesto.
CopsOnRoad
134

Solución Java (Android 9.0, API 28)

En tu Serviceclase, agrega esto:

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

ACTUALIZACIÓN: ANDROID 9.0 PIE (API 28)

Agregue este permiso a su AndroidManifest.xmlarchivo:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
PolicíasEn Carretera
fuente
¿Hay alguna razón para usar un ID único en las dos llamadas startForeground ()? ¿No pueden ser iguales aquí ya que es la misma notificación?
Cody
@CopsOnRoad, así que no hay necesidad de un canal de notificación para O?
Shruti
2
@Shruti Debe agregar permiso junto con el código para Android 9.0. Ambos son necesarios.
CopsOnRoad
1
@CopsOnRoad Esta es la excepción 'Excepción grave: android.app.RemoteServiceException: Context.startForegroundService () no llamó a Service.startForeground ()'
Shruti
2
¿Es posible evitar mostrar la notificación mientras se ejecuta el servicio?
matdev
29

La primera respuesta es excelente solo para aquellas personas que conocen kotlin, para aquellos que todavía usan Java aquí traduzco la primera respuesta

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

Para Android, P no olvide incluir este permiso

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Hamza
fuente
Gracias por traducir el código a Java. ¡Es una gran ayuda para proyectos Java!
Ray Li
17

Funciona correctamente en Andorid 8.1:

Muestra actualizada (sin ningún código obsoleto):

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

Viejo cortado (es una aplicación diferente, no relacionada con el código anterior):

@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
fuente
2
Parte del código anterior ahora está en desuso, lo que puede superar cambiando Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...aNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
prohibición de geoingeniería el
1
@ ban-geoingeniería tienes toda la razón ... Agregué un nuevo código de muestra. Gracias.
Martin Pfeffer
¿Por PRIORITY_MAXqué es mejor usar?
user924
7

En mi caso, es porque intentamos publicar una notificación sin especificar lo siguiente 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));
    }
}

El mejor lugar para poner el código anterior es el onCreate()método en la Applicationclase, por lo que solo tenemos que declararlo de una vez por todas:

public class App extends Application {

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

Después de configurar esto, podemos usar la notificación channelIdque acabamos de especificar:

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

Luego, podemos usarlo para publicar una notificación:

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

Si desea utilizarlo como notificación de servicio en primer plano:

startForeground(notifId, builder.build());
Anggrayudi H
fuente
1
¿Debería la NOTIFICATION_CHANNEL_ID_TASK constante (2da línea) ser NOTIFICATION_CHANNEL_ID_INFO?
Timores
@Timores, no. Puede reemplazarlo con su propia constante.
Anggrayudi H
4

Gracias a @CopsOnRoad, su solución fue de gran ayuda pero solo funciona para SDK 26 y superior. Mi aplicación apunta a 24 y superior.

Para evitar que Android Studio se queje, necesita un condicional directamente alrededor de la notificación. No es lo suficientemente inteligente como para saber que el código está en un método condicional a 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
fuente
¿Puede aclarar qué cambios ha realizado en este código? No lo entendí.
CopsOnRoad
Las versiones 8.0 y Android Pie funcionan perfectamente. Pero, ¿por qué necesitamos un canal de notificación solo para la versión 8.1?
Thamarai T
2

Esto funcionó para mí. En mi clase de servicio, creé el canal de notificación para Android 8.1 de la siguiente manera:

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

Nota: Cree el canal donde está creando la Notificación para Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

Arjun
fuente
-1

Aqui esta mi solucion

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

    }

Espero que ayude :)

Shahriar Nasim Nafi
fuente
1
Tómese un tiempo para explicar la justificación de su solución.
straya