如何不显示通知就启动startForeground()?


87

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

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

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

我的应用程序服务正在执行mediaplayer。如何使系统不会杀死我的服务,但应用程序会杀死它本身(例如通过按钮暂停或停止音乐)。


11
没有通知,您将无法获得“前台”服务。期。
Jug6ernaut 2012年

1
发出“假”通知怎么样?那是把戏吗?
Muhammad Resna Rizki Pratama 2012年

设置前台通知后,您可以使用Rubber应用程序删除“在后台通知中运行” 。
洛朗

1
@MuhammadResnaRizkiPratama,您会考虑更改已接受的答案吗?这个问题非常受欢迎,但是被接受的答案是完全过时的,过于笨拙的,并假设了开发人员为什么需要这个答案。我建议这个答案,因为它是可靠的,不会利用OS错误,并且可以在大多数Android设备上使用。
山姆

Answers:


95

作为Android平台的安全功能,在任何情况下都不能在没有通知的情况下获得前台服务。这是因为与前台服务相比,前台服务会消耗大量资源,并且受制于不同的调度约束(即不会很快消失),并且用户需要知道可能在消耗什么电量。所以,要这样做。

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


3
谢谢。这使我很清楚为什么setForeground需要通知。
Muhammad Resna Rizki Pratama 2012年

21
好吧,看来您确实有这样做的理由,用户对此有所要求。尽管您说的可能有些不屑一顾,但测试小组仍要求我使用该产品。也许Android应该可以选择隐藏一些您一直在运行的服务中的通知。否则,您的通知栏将看起来像一棵圣诞树。
Radu

3
@Radu实际上,您不应该在前台拥有太多应用程序:这会浪费您的电池寿命,因为它们的调度时间不同(本质上说,它们不会死)。对于mod来说,这可能是可以接受的,但我看不到该选项会很快将其变成普通的Android。“用户”平时不知道什么是对他们最好的..
Kristopher Micinski

2
@Radu不支持您的论点。“屡获殊荣的应用程序”通常是实际上通过系统不一致(例如任务杀手)来“修复” Android漏洞的应用程序。您没有理由仅因为“获奖应用程序”就必须使用前台服务:如果您这样做,那就意味着要杀死用户的电池。
Kristopher Micinski

2
显然,此方法从4.3开始将不再起作用。plus.google.com/u/0/105051985738280261832/posts/MTinJWdNL8t
erbi

79

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

从4.3版开始,从根本上讲startForeground()如果不显示通知就无法启动服务。

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

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

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

  1. startForeground()使用通知和所有内容启动虚假服务。
  2. 也使用startForeground()(相同的通知ID)启动要运行的真实服务
  3. 停止第一个(伪)服务(您可以调用stopSelf(),也可以在onDestroy调用中stopForeground(true))。

瞧!根本没有任何通知,您的第二项服务保持运行。


23
谢谢你 我认为有些人没有意识到正在进行内部业务使用的Android开发(也就是说,它既不打算出现在市场上也不打算出售给Joe Blow)。有时,人们会要求诸如“停止显示服务通知”之类的东西,并且即使周围环境不适合一般消费,也很高兴能看到解决方法。
JamieB

5
@JamieB我完全不同意...我向Dianne Hackborn建议,他们需要重新考虑后台服务的整个概念,并可能添加一个在没有通知的情况下在后台运行服务的权限。无论是否有通知,对我们开发人员来说都不重要-这是USER要求删除的通知!:)顺便说一句,您可以验证它也适用吗?
Lior Iluz

1
@JamieB user875707表示将图标参数(图标的资源ID)设置为0,而不是通知ID(如果这样,则文档说“ startForeground”将不起作用)。
wangqi060934 2013年

9
当然,您应该假定这在平台的将来版本中将不起作用。
hackbod 2014年

1
@JamieB我现在实际上在Android 5.0.1上对此进行了测试,它仍然适用于我。
Lior Iluz'3

20

从Android 7.1开始,该功能不再起作用,并且可能违反了Google Play的开发者政策

而是让用户阻止服务通知


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

ForegroundService.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

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

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

兼容性

经过测试并致力于:

  • 官方模拟器
    • 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倍
  • Genymotion
    • 5.0
    • 6.0
  • 氰基莫德
    • 5.1.1

从Android 7.1起不再可用。


2
还没有测试过,但是至少对此有一个许可会很好!!!对于“致Google”部分+1,我的用户抱怨使应用程序在后台运行所需的通知,我也抱怨skype / llama和其他任何应用程序所需的通知
Rami Dabain

很抱歉,如何使用此示例来侦听警报。我正在开发一个使用alarmManager设置警报的应用,而我遇到的问题是,当我关闭应用(最近的应用->清除)时,所有警报也会被删除,我正在训练以找到一种方法来防止这种情况
Ares91

@ Ares91,为此尝试尝试一个单独的StackOverflow问题,并确保包含尽可能多的信息,包括相关代码。我对警报了解不多,这个问题仅与服务有关。
山姆

@Sam应该先调用哪个服务,ForegroundEnablingService或Forground
Android Man

@AndroidManForegroundService
山姆

14

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

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

更新:

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


2
请注意,这似乎并没有要与SDK 17.服务将不会去前台,如果绘制传递给通知了接受0
Snicolas

这是一个错误,希望可以修复。前台服务的想法是,它们不太容易被杀死,这是确保用户意识到其存在的一种方式。
Martin Marconcini 2013年

4
使用Android 18时,该通知不再可见,而是显示“应用程序正在运行,请触摸以获取更多信息或停止该应用程序”
Emanuel Moecklin 2013年

1
我的一位客户的屏幕截图:1gravity.com/k10/Screenshot_2013-07-25-12-35-49.jpg。我也有一个4.3设备,可以确认这一发现。
伊曼纽尔·莫克林

2
警告:我正在从Android 4.1.2(SDK 16)的索尼爱立信LT26i(Xperia S)收到崩溃报告:android.app.RemoteServiceException:从程序包中发布了错误的通知...:无法创建图标:StatusBarIcon(pkg = ... id = 0x7f020052 level = 0 visible = true num = 0)我也将图标ID设置为0,直到SDK 17。从SDK 18开始,我将其设置为有效的可绘制资源。也许您需要SDK 16中的有效图标ID!
almisoft

13

警告:尽管此答案似乎有效,但实际上它默默地阻止您的服务成为前台服务

原始答案:


只需将您的通知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);
    ...

您可以得到的好处是Service,除非内存压力很大,否则a可以在不被Android系统破坏的情况下以高优先级运行。

编辑

要使其与Pre-Honeycomb和Android 4.4及更高版本一起使用,请确保使用NotificationCompat.BuilderSupport Library v7(而不是)提供的支持Notification.Builder


1
这对4.3(18)@vlad sol来说非常适合我。我想知道为什么还没有其他人对此答案表示支持?
罗伯·麦克菲利

我在Android N Preview 4中尝试了此操作,但没有成功。我试过了Notification.Builder,支持库v4NotificationCompat.Builder和支持库v7 NotificationCompat.Builder。通知没有出现在通知抽屉或状态栏中,但是当我使用getRunningServices()和检查服务时,该服务未在前台模式下运行dumpsys
山姆

@Sam,所以这个错误没有出现在早期版本中,对吗?
Anggrayudi H

@AnggrayudiH,通过“此错误”,您表示通知没有出现?还是说服务没有进入前台模式?
山姆

1
这对我不起作用。发送id!= 0时,该过程作为前台启动,但是当id = 0时,该过程不作为前台启动。使用adb shell dumpsys activity services来验证。
beetree

8

您可以通过使用自定义布局(在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

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

已在Android 9 Pixel 1上进行了测试。此解决方案在Android 8或更低版本上不起作用


不错的解决方法。我也认为它需要在android 10上使用图标。使用透明的占位符图像作为图标可以解决此问题。
shyos

7

更新:此功能不再适用于Android 4.3及更高版本


有一种解决方法。尝试创建没有设置图标的通知,通知将不会显示。不知道它是如何工作的,但是它是有效的:)

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

3
在Android N Preview 4中,此功能无效。(YourServiceName) is running当您未指定图标时,Android会显示通知而不是您自己的通知。根据CommonsWare的说法,自Android 4.3开始就是这种情况:commonsware.com/blog/2013/07/30/…–
山姆

1
我今天做了一些测试,并确认这在Android 4.3及更高版本中不起作用。
山姆

5

更新:此功能不再适用于Android 4.3及更高版本


我将icon参数设置为Notification的构造函数,并将其设置为零,然后将结果通知传递给startForeground()。日志中没有错误,也没有通知显示。不过,我不知道该服务是否已成功推出-是否可以检查?

编辑:经过dumpsys的检查,并且确实该服务在我的2.3系统上处于前台。尚未检查其他操作系统版本。


9
使用命令“ adb shell dumpsys activity services”检查“ isForeground”是否设置为true。
黑色

1
或者只是调用空的Notification()构造函数。
johsin18

这对我有用。我第一次让我的服务在一夜之间运行,因此它肯定在前台(加上dumpsys表示是这样),并且没有通知。
JamieB

请注意,这在Android 4.3(API 18)中不再起作用。操作系统在通知抽屉中放置一个占位符通知。
山姆

4

阻止前台服务通知

此处的大多数答案都不起作用,破坏前台服务或违反Google Play政策

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

Android 4.1-7.1

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

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

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

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

Android 8.0-8.1

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

Android 9+

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

  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. 让用户屏蔽频道的通知


2

版本4.3(18)及更高版本无法隐藏服务通知,但您可以禁用该图标,版本4.3(18)及以下版本可以隐藏通知

Notification noti = new Notification();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
    noti.priority = Notification.PRIORITY_MIN;
}
startForeground(R.string.app_name, noti);

尽管此答案可能是正确且有用的,但如果您在其中包含一些解释以说明它如何帮助解决问题,则最好使用该答案。如果发生更改(可能不相关)导致它停止工作,并且用户需要了解它曾经如何工作,那么这在将来特别有用。
凯文·布朗

我认为Android 4.3实际上是API18。此外,此方法仅隐藏状态栏中的图标;该图标仍存在于通知中。
2015年

为了从通知中隐藏图标,您可以添加空图像mBuilder.setSmallIcon(R.drawable.blank);
vlad sol

1
我正在查看kitkat源,并且零ID方法似乎无法正常工作。如果ID为零,则服务记录将更新为NOT前台。
杰弗里·布拉特曼

2

我发现在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 {
                //...
            }

        }
    }
}

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

        }

    }

系统不会显示永久性通知,但是您会看到Android“ x应用正在后台运行”通知。


在Android 8.1中:“ startForeground的错误通知:java.lang.RuntimeException:服务通知的无效通道”
T-igra

就个人而言,我认为该apps are running in the background通知甚至比针对特定应用的通知更糟糕,因此我不确定这种方法是否值得。
山姆

-2

更新:此功能不再适用于Android 7.1及更高版本


这是使您的应用的oom_adj变为1的方法(在ANDROID 6.0 SDK仿真器中测试)。添加临时服务,在您的主要服务呼叫中startForgroundService(NOTIFICATION_ID, notificion)。然后startForgroundService(NOTIFICATION_ID, notificion),在临时服务调用stopForgroundService(true)中一段时间​​后,以相同的通知ID再次启动临时服务调用以消除持续更新。


这是我所知道的唯一可行的解​​决方案。但是,它已经被另一个答案覆盖。
山姆

-3

您也可以将应用程序声明为持久性的。

<application
    android:icon="@drawable/icon"
    android:label="@string/app_name"
    android:theme="@style/Theme"
    *android:persistent="true"* >
</application>

从本质上讲,这会将您的应用设置为更高的内存优先级,从而降低了该应用被杀死的可能性。


5
这是错误的,只有系统应用程序才能持久存在:groups.google.com/forum/?
fromgroups=#!topic/android

-6

几个月前,我开发了一个简单的媒体播放器。所以我相信如果您正在执行以下操作:

Intent i = new Intent(this, someServiceclass.class);

startService(i);

然后,系统将无法终止您的服务。

参考阅读讨论系统何时停止服务的段落


我读了一些文章。如果只是执行startService(),则系统需要更多内存。因此系统将终止该服务。那是真实的?我只是没有时间在服务中进行实验。
Muhammad Resna Rizki Pratama 2012年

1
Muhammad,的确如此-startForeground()的目的是有效地给服务提供比后台服务更高的“内存优先级”,以便它可以生存更长的时间。startForeground()应该使用通知,以便用户更清楚地了解正在运行的更持久/更难解决的服务。
DustinB

这是不正确的; 提供几乎无法在Android上终止的服务几乎是不可能的。Android会轻易终止非前台服务以释放内存。
山姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.