startForeground không thành công sau khi nâng cấp lên Android 8.1


191

Sau khi nâng cấp điện thoại của tôi lên 8.1 Nhà phát triển Xem trước dịch vụ nền của tôi không còn khởi động đúng cách.

Trong dịch vụ chạy dài của tôi, tôi đã triển khai một phương thức startForeground để bắt đầu thông báo đang diễn ra được gọi khi tạo.

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

Thông báo lỗi:

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)

kênh không hợp lệ để thông báo dịch vụ , rõ ràng kênh cũ của tôi DEFAULT_CHANNEL_ID không còn phù hợp với API 27 tôi giả sử. Điều gì sẽ là kênh thích hợp? Tôi đã cố gắng xem qua các tài liệu


1
Câu trả lời này là giải pháp của tôi.
Alex Jolig

Câu trả lời:


226

Sau một vài lần mày mò với các giải pháp khác nhau, tôi phát hiện ra rằng người ta phải tạo một kênh thông báo trong Android 8.1 trở lên.

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
}

Theo hiểu biết của tôi, các dịch vụ nền hiện được hiển thị dưới dạng thông báo bình thường mà người dùng sau đó có thể chọn để không hiển thị bằng cách bỏ chọn kênh thông báo.

Cập nhật : Cũng đừng quên thêm quyền nền trước như Android P yêu cầu:

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

Chúng ta có cần thực hiện những thay đổi này trong trường hợp của JobIntentService không? Hoặc nó đang xử lý nó trong nội bộ?
Amrut

1
Tại sao không IMPORTANCE_DEFAULTthay thế IMPORTANCE_NONE?
user924

1
@ user924 Kotlin thực sự là một ngôn ngữ mới hơn Swift. Kotlin không thay thế Java, nó chỉ là một sự thay thế cho Java để phát triển Android. Nếu bạn dùng thử, bạn sẽ thấy nó thực sự khá giống cú pháp với Swift. Cá nhân tôi tin rằng nó tốt hơn Java, bất chấp những gì chỉ số Tiobe nói (chỉ số này chịu một chút sai lệch không phản hồi). Nó sửa chữa nhiều vấn đề mà Java gặp phải, bao gồm NullPulumException đáng sợ, tính dài dòng và một số thứ khác. Theo Google I / O mới nhất, 95% các nhà phát triển sử dụng Kotlin cho Android hài ​​lòng với nó.
Tài nguyên phụ 6

3
Điều này nên được gọi từ onCreate () của dịch vụ của bạn
Evgenii Vorobei

1
@Rawa Ngay cả tôi cũng không chắc bạn đang làm gì với dịch vụ Tiền cảnh trong ứng dụng vì Tài liệu không nói dối. Nó nêu rõ, bạn sẽ nhận được SecurityException nếu bạn cố gắng tạo dịch vụ nền trước mà không có sự cho phép trong tệp kê khai.
CopsOnRoad

135

Giải pháp Java (Android 9.0, API 28)

Trong Servicelớp của bạn , thêm điều này:

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

CẬP NHẬT: ANDROID 9.0 PIE (API 28)

Thêm quyền này vào AndroidManifest.xmltệp của bạn :

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

Có lý do để sử dụng một ID duy nhất trong hai lệnh gọi startForeground () không? Họ có thể giống nhau ở đây vì thông báo giống nhau không?
Cody

@CopsOnRoad vì vậy không cần kênh thông báo cho O?
Shruti

2
@Shruti Bạn cần thêm quyền cùng với mã cho Android 9.0. Cả hai đều cần thiết.
CopsOnRoad

1
@CopsOnRoad Đây là ngoại lệ 'Ngoại lệ gây tử vong: android.app.RemoteServiceException: Context.startForegroundService () sau đó không gọi Service.startForeground ()'
Shruti

2
Có thể tránh hiển thị thông báo trong khi dịch vụ đang chạy không?
matdev

29

Câu trả lời đầu tiên chỉ tuyệt vời cho những người biết kotlin, cho những người vẫn sử dụng java ở đây tôi dịch câu trả lời đầu tiên

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

Đối với Android, P đừng quên bao gồm quyền này

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

Cảm ơn đã dịch mã sang Java. Đó là một trợ giúp lớn cho các dự án Java!
Ray Li

17

Hoạt động đúng trên Andorid 8.1:

Mẫu được cập nhật (không có bất kỳ mã không dùng nữa):

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

Snipped cũ (đó là một ứng dụng khác - không liên quan đến mã ở trên):

@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
Một phần của mã trên hiện không được chấp nhận, mà bạn có thể khắc phục bằng cách thay đổi Notification.Builder(getApplicationContext()).setChannelId(CHANNEL_ONE_ID)...thànhNotification.Builder(getApplicationContext(), CHANNEL_ONE_ID)...
ban-geengineering

1
@ ban-Geoengineering bạn hoàn toàn đúng ... Tôi đã thêm mã mẫu mới. Cảm ơn.
Martin Pfeffer

Tại sao PRIORITY_MAXcái gì tốt hơn để sử dụng?
user924

7

Trong trường hợp của tôi, đó là vì chúng tôi đã cố gắng đăng thông báo mà không chỉ định 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));
    }
}

Nơi tốt nhất để đặt mã ở trên là trong onCreate()phương thức trong Applicationlớp, do đó chúng ta chỉ cần khai báo nó một lần cho tất cả:

public class App extends Application {

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

Sau khi chúng tôi thiết lập tính năng này, chúng tôi có thể sử dụng thông báo với thông báo channelIdchúng tôi vừa chỉ định:

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

Sau đó, chúng ta có thể sử dụng nó để đăng thông báo:

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

Nếu bạn muốn sử dụng nó làm thông báo của dịch vụ tiền cảnh:

startForeground(notifId, builder.build());

1
Hằng số THÔNG BÁO_CHANNEL_ID_TASK (dòng thứ 2) có phải là THÔNG BÁO_CHANNEL_ID_INFO không?
Timores

@Timores, không. Bạn có thể thay thế nó bằng hằng số của riêng bạn.
Anggrayudi H

4

Nhờ @CopsOnRoad, giải pháp của anh ấy là một trợ giúp lớn nhưng chỉ hoạt động cho SDK 26 trở lên. Ứng dụng của tôi nhắm mục tiêu 24 trở lên.

Để giữ cho Android Studio không phàn nàn, bạn cần có điều kiện trực tiếp xung quanh thông báo. Nó không đủ thông minh để biết mã trong một phương thức có điều kiện để 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);
    }
}

Bạn có thể vui lòng làm rõ những thay đổi bạn đã thực hiện trong mã này không, tôi đã không nhận được điều này.
CopsOnRoad

Phiên bản 8.0 và Android Pie hoạt động hoàn hảo. Nhưng tại sao chúng ta chỉ cần kênh Thông báo cho phiên bản 8.1?
Thamarai T

2

Điều này làm việc cho tôi. Trong lớp dịch vụ của mình, tôi đã tạo kênh thông báo cho Android 8.1 như sau:

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

Lưu ý: Tạo kênh nơi bạn đang tạo Thông báo cho Build.VERSION.SDK_INT >= Build.VERSION_CODES.O


-1

Đây là giải pháp của tôi

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

    }

Hy vọng nó sẽ giúp :)


Xin vui lòng dành chút thời gian để giải thích lý do cho giải pháp của bạn.
straya
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.