Android 8.1로 업그레이드 한 후 startForeground가 실패 함


193

휴대 전화를 8.1 Developer Preview로 업그레이드 한 후 백그라운드 서비스가 더 이상 제대로 시작되지 않습니다.

장기 실행 서비스에서 createFor에서 호출되는 지속적인 알림을 시작하기 위해 startForeground 메소드를 구현했습니다.

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

에러 메시지:

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)

서비스 알림에 유효하지 않은 채널 , 분명히 내 이전 채널 DEFAULT_CHANNEL_ID 는 더 이상 API 27에 적합하지 않습니다. 적절한 채널은 무엇입니까? 설명서를 살펴 보았습니다.


1
이 대답 은 나의 해결책이었습니다.
Alex Jolig

답변:


230

다른 솔루션으로 잠시 동안 땜질을 한 후에 Android 8.1 이상에서 알림 채널을 만들어야한다는 것을 알았습니다.

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
}

내가 이해 한 배경 서비스는 이제 일반 알림으로 표시되며 사용자는 알림 채널을 선택 해제하여 표시하지 않도록 선택할 수 있습니다.

최신 정보 : 필요한 Android P에 따라 포 그라운드 권한을 추가하는 것을 잊지 마십시오.

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

JobIntentService의 경우 이러한 변경을 수행해야합니까? 아니면 내부적으로 처리하고 있습니까?
Amrut

1
IMPORTANCE_DEFAULT대신에 IMPORTANCE_NONE?
user924

1
@ user924 Kotlin은 실제로 Swift보다 새로운 언어입니다. Kotlin은 Java를 대체하지 않으며 Android 개발을위한 Java의 대안 일뿐입니다. 시도해 보면 실제로 Swift와 구문이 매우 비슷하다는 것을 알 수 있습니다. 나는 개인적으로 Tiobe 지수의 말에도 불구하고 Java보다 낫다고 믿는다. 그것은 두려워하는 NullPointerException, 자세한 정보 및 기타 여러 가지를 포함하여 Java에있는 많은 문제를 해결합니다. 최신 Google I / O에 따르면 Android 용 Kotlin을 사용하는 개발자의 95 %가 만족합니다.
Sub 6 Resources

3
이것은 서비스의 onCreate ()에서 호출되어야합니다
Evgenii Vorobei 2016 년

1
@Rawa 문서가 거짓말하지 않기 때문에 앱에서 Foreground 서비스로 무엇을하고 있는지 잘 모르겠습니다. 매니페스트의 권한없이 포 그라운드 서비스를 만들려고하면 SecurityException이 발생합니다.
CopsOnRoad

134

자바 솔루션 (Android 9.0, API 28)

당신에 Service수업이 추가 :

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

업데이트 : ANDROID 9.0 PIE (API 28)

AndroidManifest.xml파일 에이 권한을 추가 하십시오.

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

두 개의 startForeground () 호출에서 고유 ID를 사용하는 이유가 있습니까? 동일한 알림 때문에 여기에서 동일 할 수 없습니까?
Cody

@CopsOnRoad 그래서 O에 대한 알림 채널이 필요하지 않습니까?
Shruti

2
@Shruti Android 9.0 코드와 함께 권한을 추가해야합니다. 둘 다 필요합니다.
CopsOnRoad

1
@CopsOnRoad '치명적 예외 : android.app.RemoteServiceException : Context.startForegroundService ()가 Service.startForeground ()를 호출하지 않았습니다' '예외
Shruti

2
서비스가 실행되는 동안 알림이 표시되지 않도록 할 수 있습니까?
matdev 2016 년

29

첫 번째 답변은 kotlin을 아는 사람들에게만 훌륭합니다. 여전히 Java를 사용하는 사람들은 첫 번째 답변을 번역합니다.

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

Android의 경우 P는이 권한을 포함하는 것을 잊지 마십시오

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

코드를 Java로 번역 해 주셔서 감사합니다. Java 프로젝트에 큰 도움이됩니다!
Ray Li

17

Andorid 8.1에서 올바르게 작동합니다 :

더 이상 사용되지 않는 코드없이 업데이트 된 샘플 :

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

오래된 도청 (다른 앱입니다- 위의 코드와 관련이 없음 ) :

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

2
위의 코드 중 일부는 더 이상 사용되지 않으며 다음과 같이 변경 Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...하여 극복 할 수 있습니다.Notification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-geoengineering

1
@ ban-geoengineering 당신이 옳습니다 ... 새로운 샘플 코드를 추가했습니다. 감사.
Martin Pfeffer

PRIORITY_MAX더 나은 것을 사용합니까?
user924

7

제 경우에는 다음을 지정하지 않고 알림을 게시하려고했기 때문입니다 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));
    }
}

위의 코드를 작성하는 가장 좋은 장소 onCreate()Application클래스의 메소드에 있으므로 한 번만 선언하면됩니다.

public class App extends Application {

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

이를 설정 한 후 channelId방금 지정한 알림 메시지를 사용할 수 있습니다 .

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

그런 다음이를 사용하여 알림을 게시 할 수 있습니다.

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

포 그라운드 서비스의 알림으로 사용하려는 경우 :

startForeground(notifId, builder.build());

1
NOTIFICATION_CHANNEL_ID_TASK 상수 (2 행)는 NOTIFICATION_CHANNEL_ID_INFO 여야합니까?
Timores

@ 티 모어 자신의 상수로 바꿀 수 있습니다.
Anggrayudi H

4

@CopsOnRoad 덕분에 그의 솔루션은 큰 도움이되었지만 SDK 26 이상에서만 작동합니다. 내 앱은 24 이상을 대상으로합니다.

Android Studio에서 불만을 제기하지 않으려면 알림 주위에 직접 조건이 필요합니다. 코드가 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);
    }
}

이 코드에서 변경 한 내용을 명확히 설명해 주시겠습니까?
CopsOnRoad

버전 8.0 및 Android Pie는 완벽하게 작동합니다. 그러나 왜 버전 8.1에만 알림 채널이 필요한가요?
Thamarai T

2

이것은 나를 위해 일했습니다. 내 서비스 클래스에서 다음과 같이 android 8.1에 대한 알림 채널을 만들었습니다.

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

참고 : 알림을 생성 할 채널을 만듭니다. Build.VERSION.SDK_INT >= Build.VERSION_CODES.O


-1

여기 내 해결책이 있습니다

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

    }

그것이 도움이되기를 바랍니다 :)


1
솔루션의 이론적 근거를 설명하는 데 약간의 시간이 걸립니다.
straya
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.