startForeground ล้มเหลวหลังจากอัปเกรดเป็น Android 8.1


193

หลังจากอัพเกรดโทรศัพท์เป็น 8.1 Developer Preview Preview บริการของฉันไม่เริ่มทำงานอย่างถูกต้อง

ในบริการที่ใช้เวลานานของฉันฉันได้ใช้วิธีการ 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
คำตอบนี้เป็นทางออกของฉัน
อเล็กซ์ 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 เป็นเพียงทางเลือกสำหรับการพัฒนา Java สำหรับ Android หากคุณลองใช้คุณจะเห็นว่ามันค่อนข้างคล้ายกันในรูปแบบของ Swift ฉันเองเชื่อว่าเป็นการดีกว่า Java แม้จะมีสิ่งที่ดัชนี Tiobe พูดว่า (ดัชนีอาจมีอคติเล็กน้อยที่ไม่ตอบสนอง) มันแก้ไขปัญหาต่าง ๆ ที่ Java มีรวมถึง NullPointerException, verbosity และสิ่งอื่น ๆ อีกมากมาย ตาม Google I / O ล่าสุด 95% ของนักพัฒนาที่ใช้ Kotlin สำหรับ Android มีความสุขกับมัน
ย่อย 6 ทรัพยากร

3
สิ่งนี้ควรถูกเรียกจาก onCreate () ของบริการของคุณ
Evgenii Vorobei

1
@Rawa แม้ว่าฉันไม่แน่ใจว่าคุณกำลังทำอะไรกับบริการ Foreground ของคุณในแอพเพราะเอกสารไม่โกหก ระบุอย่างชัดเจนว่าคุณจะได้รับ SecurityException หากคุณพยายามสร้างบริการเบื้องหน้าโดยไม่ได้รับอนุญาตในไฟล์ Manifest
CopsOnRoad

134

โซลูชัน Java (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" />

มีเหตุผลในการใช้ ID ที่ไม่ซ้ำกันในการโทร startForeground () สองครั้งหรือไม่? พวกเขาไม่สามารถเหมือนกันที่นี่ตั้งแต่การแจ้งเตือนเดียวกันหรือไม่
โคดี้

@CopsOnRoad ดังนั้นไม่จำเป็นต้องมีช่องการแจ้งเตือนสำหรับ O?
Shruti

2
@Shruti คุณต้องเพิ่มการอนุญาตพร้อมกับรหัสสำหรับ Android 9.0 ทั้งคู่มีความจำเป็น
CopsOnRoad

1
@CopsOnRoad นี่คือข้อยกเว้น 'ข้อยกเว้นร้ายแรง: android.app.RemoteServiceException: Context.startForegroundService () ไม่ได้เรียก Service.startForeground ()'
Shruti

2
เป็นไปได้หรือไม่ที่จะไม่แสดงการแจ้งเตือนในขณะที่บริการกำลังทำงานอยู่?
matdev

29

คำตอบแรกนั้นยอดเยี่ยมสำหรับคนที่รู้จัก kotlin เท่านั้นสำหรับผู้ที่ยังใช้ภาษาจาวาที่นี่ฉันแปลคำตอบแรก

 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
ควรจะมีค่าคงที่
Timores

@Timores, no. คุณสามารถแทนที่ด้วยค่าคงที่ของคุณเอง
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.