Android 如何在不显示通知的情况下 startForeground()?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10962418/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 05:35:21  来源:igfitidea点击:

How to startForeground() without showing notification?

androidandroid-serviceforeground

提问by Muhammad Resna Rizki Pratama

I want to create a service and make it run in the foreground.

我想创建一个服务并让它在前台运行。

Most example codes have notifications on it. But I don't want to show any notification. Is that possible?

大多数示例代码都有通知。但我不想显示任何通知。那可能吗?

Can you give me some examples? Are there any alternatives?

你能给我一些例子吗?有没有其他选择?

My app service is doing mediaplayer. How to make system not kill my service except the app kill it itself(like pausing or stopping the music by button).

我的应用服务正在做媒体播放器。如何使系统不终止我的服务,除非应用程序自行终止(例如通过按钮暂停或停止音乐)。

回答by Kristopher Micinski

As a security feature of the Android platform, you cannot, under anycircumstance, have a foregrounded service without also having a notification. This is because a foregrounded service consumes a heavier amount of resources and is subject to different scheduling constraints (i.e., it doesn't get killed as quickly) than background services, and the user needs to know what's possibly eating their battery. So, don'tdo this.

作为 Android 平台的一项安全功能,您在任何情况下都不能在没有通知的情况下拥有前台服务。这是因为前台服务比后台服务消耗更多的资源并且受到不同的调度约束(即,它不会很快被终止),并且用户需要知道什么可能会消耗他们的电池。所以,要这样做。

However, it ispossible to have a "fake" notification, i.e., you can make a transparent notification icon (iirc). This is extremely disingenuous to your users, and you have no reason to do it, other than killing their battery and thus creating malware.

但是,它可能有一个“假”的通知,也就是说,你可以做一个透明的通知图标(IIRC)。这对您的用户来说是非常不诚实的,除了杀死他们的电池并因此创建恶意软件之外,您没有理由这样做。

回答by Lior Iluz

Update:This was "fixed" on Android 7.1. https://code.google.com/p/android/issues/detail?id=213309

更新:这是在 Android 7.1 上“修复”的。 https://code.google.com/p/android/issues/detail?id=213309

Since the 4.3 update, it's basically impossibleto start a service with startForeground()without showing a notification.

自 4.3 更新以来,基本上不可能startForeground()不显示通知的情况下启动服务。

You can, however, hide the icon using official APIs... no need for a transparent icon: (Use NotificationCompatto support older versions)

但是,您可以使用官方 API 隐藏图标...不需要透明图标:(NotificationCompat用于支持旧版本)

NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setPriority(Notification.PRIORITY_MIN);

I've made peace with the fact the notification itself still needs to be there but for who ever who still wants to hide it, I may have found a workaround for that as well:

我已经接受了通知本身仍然需要存在的事实,但是对于仍然想隐藏它的人,我可能也找到了解决方法:

  1. Start a fake service with startForeground()with the notification and everything.
  2. Start the real service you want to run, also with startForeground()(same notification ID)
  3. Stop the first (fake) service (you can call stopSelf()and in onDestroy call stopForeground(true)).
  1. startForeground()使用通知和所有内容启动虚假服务。
  2. 启动您要运行的真实服务,也使用startForeground()(相同的通知 ID)
  3. 停止第一个(假)服务(你可以stopSelf()在 onDestroy call 中调用和调用stopForeground(true))。

Voilà! No notification at all and your second service keeps running.

瞧!根本没有通知,您的第二个服务继续运行。

回答by Sam

This no longer works as of Android 7.1 and it may violate Google Play's developer policies.

这不再适用于 Android 7.1,并且可能违反Google Play 的开发者政策

Instead, have the user block the service notification.

相反,让用户阻止服务通知



Here's my implementation of the technique in the answerby Lior Iluz.

下面是我的实现在技术,答案利奥尔Iluz

Code

代码

ForegroundService.java

前台服务.java

public class ForegroundService extends Service {

    static ForegroundService instance;

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

        instance = this;

        if (startService(new Intent(this, ForegroundEnablingService.class)) == null)
            throw new RuntimeException("Couldn't find " + ForegroundEnablingService.class.getSimpleName());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        instance = null;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

ForegroundEnablingService.java

前景启用服务.java

public class ForegroundEnablingService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (ForegroundService.instance == null)
            throw new RuntimeException(ForegroundService.class.getSimpleName() + " not running");

        //Set both services to foreground using the same notification id, resulting in just one notification
        startForeground(ForegroundService.instance);
        startForeground(this);

        //Cancel this service's notification, resulting in zero notifications
        stopForeground(true);

        //Stop this service so we don't waste RAM.
        //Must only be called *after* doing the work or the notification won't be hidden.
        stopSelf();

        return START_NOT_STICKY;
    }

    private static final int NOTIFICATION_ID = 10;

    private static void startForeground(Service service) {
        Notification notification = new Notification.Builder(service).getNotification();
        service.startForeground(NOTIFICATION_ID, notification);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

AndroidManifest.xml

AndroidManifest.xml

<service android:name=".ForegroundEnablingService" />
<service android:name=".ForegroundService" />

Compatibility

兼容性

Tested and working on:

测试和工作:

  • Official Emulator
    • 4.0.2
    • 4.1.2
    • 4.2.2
    • 4.3.1
    • 4.4.2
    • 5.0.2
    • 5.1.1
    • 6.0
    • 7.0
  • Sony Xperia M
    • 4.1.2
    • 4.3
  • Samsung Galaxy ?
    • 4.4.2
    • 5.X
  • Genymotion
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1
  • 官方模拟器
    • 4.0.2
    • 4.1.2
    • 4.2.2
    • 4.3.1
    • 4.4.2
    • 5.0.2
    • 5.1.1
    • 6.0
    • 7.0
  • 索尼 Xperia M
    • 4.1.2
    • 4.3
  • 三星Galaxy ?
    • 4.4.2
    • 5.X
  • 基因运动
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1

No longer working as of Android 7.1.

从 Android 7.1 开始不再工作。

回答by Snicolas

You can use this (as suggested by @Kristopher Micinski):

您可以使用它(如@Kristopher Micinski 所建议):

Notification note = new Notification( 0, null, System.currentTimeMillis() );
note.flags |= Notification.FLAG_NO_CLEAR;
startForeground( 42, note );

UPDATE:

更新:

Please note that this is not allowed anymore with Android KitKat+ releases. And keep in mind that this is more or less violating the design principle in Android that makes background operations visible to users as mentioned by @Kristopher Micinski

请注意,Android KitKat+ 版本不再允许这样做。请记住,这或多或少违反了 Android 中的设计原则,该原则使用户可以看到@Kristopher Micinski 提到的后台操作

回答by Anggrayudi H

Warning: although this answer appears to work, it in fact silently prevents your service from becoming a foreground service.

警告:虽然这个答案似乎有效,但实际上它会默默地阻止您的服务成为前台服务

Original answer:

原答案:



Just set your notification's ID to zero:

只需将您的通知 ID 设置为零:

// field for notification ID
private static final int NOTIF_ID = 0;

    ...
    startForeground(NOTIF_ID, mBuilder.build());
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    mNotificationManager.cancel(NOTIF_ID);
    ...

A benefit you can get is, a Servicewill be able to runs on high priority without destroyed by Android system, unless on high memory pressure.

你可以获得的一个好处是,aService将能够以高优先级运行而不会被 Android 系统破坏,除非在高内存压力下。

EDIT

编辑

To make it work with Pre-Honeycomb and Android 4.4 and higher, make sure that you use NotificationCompat.Builderwhich provided by Support Library v7, instead of Notification.Builder.

要使其适用于 Pre-Honeycomb 和 Android 4.4 及更高版本,请确保使用NotificationCompat.BuilderSupport Library v7 提供的Notification.Builder.

回答by Vladimir Petrovski

Update: this no longer works in Android 4.3 and above

更新:这不再适用于 Android 4.3 及更高版本



There is one workaround. Try creating notification without setting icon, and the notification would not show. Don't know how it works, but it does :)

有一种解决方法。尝试在不设置图标的情况下创建通知,通知不会显示。不知道它是如何工作的,但确实如此:)

    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("Title")
            .setTicker("Title")
            .setContentText("App running")
            //.setSmallIcon(R.drawable.picture)
            .build();
    startForeground(101,  notification);

回答by phnmnn

You can hide notification on Android 9+by using custom layout with layout_height = "0dp"

您可以使用 layout_height = "0dp" 的自定义布局在Android 9+上隐藏通知

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.custom_notif);
builder.setContent(remoteViews);
builder.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setVisibility(Notification.VISIBILITY_SECRET);

custom_notif.xml

custom_notif.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp">
</LinearLayout>

Tested on Pixel 1, android 9. This solution doesn't work on Android 8 or less

在 Pixel 1、android 9 上测试。此解决方案不适用于 Android 8 或更低版本

回答by Alexander Pruss

Update: this no longer works in Android 4.3 and above

更新:这不再适用于 Android 4.3 及更高版本



I set the icon parameter to the constructor for Notification to zero, and then passed the resulting notification to startForeground(). No errors in the log and no notification shows up. I don't know, though, whether the service was successfully foregrounded--is there any way to check?

我将 Notification 的构造函数的 icon 参数设置为零,然后将生成的通知传递给 startForeground()。日志中没有错误,也没有显示通知。但是,我不知道该服务是否已成功前景化——有什么方法可以检查吗?

Edited: Checked with dumpsys, and indeed the service is foregrounded on my 2.3 system. Haven't checked with other OS versions yet.

编辑:使用 dumpsys 检查,确实该服务在我的 2.3 系统上是前台的。尚未检查其他操作系统版本。

回答by Sam

Block the foreground service notification

阻止前台服务通知

Most answers here either don't work, break the foreground service, or violate Google Play policies.

这里的大多数答案要么不起作用,要么破坏前台服务,要么违反Google Play 政策

The only way to reliably and safely hide the notification is to have the user block it.

可靠且安全地隐藏通知的唯一方法是让用户阻止它。

Android 4.1 - 7.1

安卓 4.1 - 7.1

The only way is to block allnotifications from your app:

唯一的方法是阻止来自您的应用的所有通知:

  1. Send user to app's details screen:

    Uri uri = Uri.fromParts("package", getPackageName(), null);
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
    startActivity(intent);
    
  2. Have user block app's notifications

  1. 将用户发送到应用程序的详细信息屏幕:

    Uri uri = Uri.fromParts("package", getPackageName(), null);
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
    startActivity(intent);
    
  2. 让用户阻止应用程序的通知

Note this also blocks your app's toasts.

请注意,这也会阻止您的应用程序的吐司。

Android 8.0 - 8.1

安卓 8.0 - 8.1

It's not worth blocking the notification on Android O because the OS will just replace it with a "running in the background" or "using battery" notification.

在 Android O 上阻止通知是不值得的,因为操作系统只会将其替换为“在后台运行”或“使用电池”通知。

Android 9+

安卓 9+

Use a Notification Channelto block the service notification without affecting your other notifications.

使用通知渠道来阻止服务通知而不影响您的其他通知。

  1. Assign service notification to notification channel
  2. Send user to notification channel's settings

    Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
        .putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())
        .putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
    startActivity(intent);
    
  3. Have user block channel's notifications

  1. 将服务通知分配给通知通道
  2. 将用户发送到通知频道的设置

    Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
        .putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())
        .putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
    startActivity(intent);
    
  3. 让用户屏蔽频道的通知

回答by Pete

I've found on Android 8.0 it's still possible by not using a notification channel.

我发现在 Android 8.0 上仍然可以通过不使用通知渠道来实现。

public class BootCompletedIntentReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {

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

                Intent notificationIntent = new Intent(context, BluetoothService.class);    
                context.startForegroundService(notificationIntent);

            } else {
                //...
            }

        }
    }
}

And in BluetoothService.class:

在 BluetoothService.class 中:

 @Override
    public void onCreate(){    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            Intent notificationIntent = new Intent(this, BluetoothService.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

            Notification notification = new Notification.Builder(this)
                    .setContentTitle("Title")
                    .setContentText("App is running")
                    .setSmallIcon(R.drawable.notif)
                    .setContentIntent(pendingIntent)
                    .setTicker("Title")
                    .setPriority(Notification.PRIORITY_DEFAULT)
                    .build();

            startForeground(15, notification);

        }

    }

A persistent notification is not shown, however you will see the Android 'x apps are running in the background' notification.

未显示持久通知,但您会看到 Android“x 应用程序正在后台运行”通知。