【问题标题】:How to startForeground() without showing notification?如何在不显示通知的情况下 startForeground()?
【发布时间】:2012-06-13 07:32:20
【问题描述】:

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

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

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

我的应用服务正在播放媒体播放器。 如何让系统不杀死我的服务,除了应用程序自己杀死它(比如通过按钮暂停或停止音乐)。

【问题讨论】:

  • 您不能在没有通知的情况下拥有“前台”服务。期间。
  • 给一个“假”通知怎么样?这是个把戏?
  • 设置前台通知后,您可以使用Rubber app 删除“在后台运行的通知”。
  • @MuhammadResnaRizkiPratama,您会考虑更改接受的答案吗?这个问题非常受欢迎,但公认的答案完全过时,过于严厉,并且假设开发人员为什么需要这个。我建议this answer,因为它可靠,不会利用操作系统漏洞,并且适用于大多数 Android 设备。

标签: android android-service foreground


【解决方案1】:

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

但是,可能会有“假”通知,即,您可以制作一个透明的通知图标 (iirc)。这对你的用户来说是极其不诚实的,你没有理由这样做,除了耗尽他们的电池并因此产生恶意软件。

【讨论】:

  • 谢谢。这让我清楚为什么 setForeground 需要通知。
  • 好吧,看来您确实有理由这样做,用户要求这样做。尽管您说的可能不诚实,但测试组要求我为我正在开发的产品提供它。也许Android应该有一个选项来隐藏一些你知道一直在运行的服务的通知。否则你的通知栏会看起来像一棵圣诞树。
  • @Radu 实际上你不应该在前台有那么多应用程序:这会缩短你的电池寿命,因为它们的调度方式不同(基本上,它们不会死)。这对于一个模组来说可能是可以接受的,但我看不到这个选项很快就会变成香草 Android。 “用户”通常不知道什么对他们最有利..
  • @Radu 不支持您的论点。 “屡获殊荣的应用程序”通常是那些通过系统不一致(如任务杀手)实际“修复”Android 漏洞的应用程序。没有理由仅仅因为“屡获殊荣的应用程序”就必须使用前台服务:如果这样做,您就承认会耗尽用户的电量。
  • 显然,从 4.3 开始,此方法将不再有效。 plus.google.com/u/0/105051985738280261832/posts/MTinJWdNL8t
【解决方案2】:

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


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

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

【讨论】:

  • 使用命令“adb shell dumpsys activity services”检查“isForeground”是否设置为true。
  • 或者直接调用空的Notification()构造函数。
  • 这对我有用。我第一次让我的服务在一夜之间运行,所以它肯定在前台(加上 dumpsys 说它是)并且没有通知。
  • 请注意,这不再适用于 Android 4.3 (API 18)。操作系统在通知抽屉中放置一个占位符通知。
【解决方案3】:

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

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

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

【讨论】:

【解决方案4】:

您可以使用它(如@Kristopher Micinski 建议的那样):

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

更新:

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

【讨论】:

  • 请注意,SDK 17 似乎不再接受这一点。如果传递给通知的可绘制对象为 0,则服务不会进入前台。
  • 这是一个错误,希望得到修复。前台服务的想法是它们不太容易被杀死,这是确保用户知道其存在的一种方式。
  • 在 Android 18 中,通知不再不可见,而是显示“应用正在运行,触摸以获取更多信息或停止应用”
  • 我的一位客户的屏幕截图:1gravity.com/k10/Screenshot_2013-07-25-12-35-49.jpg。我也有一个 4.3 的设备,可以确认这一发现。
  • 警告:我收到来自索尼爱立信 LT26i (Xperia S) 与 Android 4.1.2 (SDK 16) 的崩溃报告:android.app.RemoteServiceException:从包发布的错误通知...:Couln' t 创建图标:StatusBarIcon(pkg=... id=0x7f020052 level=0 visible=true num=0 ) 在 SDK 17 之前,我也将 icon id 设置为 0。从 SDK 18 开始,我已将其设置为有效的可绘制资源。也许,您需要来自 SDK 16 的有效图标 ID!
【解决方案5】:

更新:这已在 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))。

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

【讨论】:

  • 谢谢。我认为有些人没有意识到 Android 开发是为内部业务使用而进行的(也就是说,它不打算出现在市场上或出售给 Joe Blow)。有时人们会要求诸如“停止显示服务通知”之类的东西,并且很高兴看到解决方法,即使它不适合一般消费。
  • @JamieB 我完全同意...我向 Dianne Hackborn 建议他们需要重新考虑后台服务的整个概念,并可能添加在没有通知的情况下在后台运行服务的权限。是否有通知对我们开发人员来说并不重要 - 这是用户请求删除它! :) 顺便问一下,你能验证它也适合你吗?
  • @JamieB user875707 说将图标参数(图标的资源 id)设置为 0,而不是通知的 id(如果是这样,正如文档所说,“startForeground”将不起作用)。
  • 您当然应该假设这在平台的未来版本中不起作用。
  • @JamieB 我现在在 Android 5.0.1 上实际测试了它,它仍然适用于我。
【解决方案6】:

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实际上是API 18。另外,这种方法只是从状态栏中隐藏图标;该图标仍然存在于通知中。
  • 为了从通知中隐藏图标,您可以添加空图像 mBuilder.setSmallIcon(R.drawable.blank);
  • 我正在查看 kitkat 源码,零 ID 方法似乎不起作用。如果 ID 为零,则服务记录更新为 NOT foreground。
【解决方案7】:

警告:虽然这个答案似乎有效,但实际上是在默默地prevents your service from becoming a foreground service

原答案:


只需将通知的 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 将能够以高优先级运行而不会被 Android 系统破坏,除非内存压力很大。

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

编辑

由于新 api 级别的安全原因,此代码将不再工作

NotificationId不能设置为“0”(这会导致应用崩溃)

startForeground(1, notification)

这是显示通知的完美方式(推荐方式)

但如果您需要它而不考虑推荐的方法,请尝试从您的代码中删除 "notificationManager.createNotificationChannel("channel_id")"

使用notificationManager.removeNotificationChannel(channel)

【讨论】:

  • 这对我来说非常适合 4.3(18) @vlad sol。为什么我想知道这个答案还没有其他人赞成?
  • 我在 Android N Preview 4 中尝试过,但没有成功。我尝试了Notification.Builder、支持库 v4 NotificationCompat.Builder 和支持库 v7 NotificationCompat.Builder。通知没有出现在通知抽屉或状态栏中,但是当我使用getRunningServices()dumpsys 检查它时,该服务没有在前台模式下运行。
  • @Sam,所以这个错误不会出现在早期版本中,对吗?
  • @AnggrayudiH,通过“这个错误”,你的意思是通知没有出现吗?或者你的意思是服务没有进入前台模式?
  • 这对我不起作用。当发送 id!=0 时进程作为前台启动,但在 id=0 时不作为前台启动。使用adb shell dumpsys activity services进行验证。
【解决方案8】:

更新:此no longer works 在 Android 7.1 及更高版本中


这是一种使您的应用程序的oom_adj为1的方法(在ANDROID 6.0 SDK​​emulator中测试)。添加一个临时服务,在你的主服务调用startForgroundService(NOTIFICATION_ID, notificion)。然后再次启动具有相同通知 id 的临时服务调用startForgroundService(NOTIFICATION_ID, notificion),一段时间后在临时服务调用 stopForgroundService(true) 以关闭正在运行的通知。

【讨论】:

  • 这是我知道的唯一可行的解​​决方案。但是,它已经被another answer 覆盖了。
【解决方案9】:

这不再适用于 Android 7.1,它可能违反 Google Play's developer policies

改为have the user block the service notification


这是我在the answer 中通过Lior 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
  • 三星银河?
    • 4.4.2
    • 5.X
  • Genymotion
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1

从 Android 7.1 起不再工作。

【讨论】:

  • 还没有测试过,但至少有一个权限就好了!!! +1 对于“To Google”部分,我的用户抱怨让应用程序在后台运行所需的通知,我还抱怨 skype/llama 和任何其他应用程序所需的通知
  • 很抱歉这个例子可以用来监听警报。我正在开发一个使用 alarmManager 设置警报的应用程序,我遇到的问题是,当我关闭应用程序(最近的应用程序-> 清除)时,所有警报也会被删除,我正在训练寻找防止这种情况发生的方法
  • @Ares91,尝试为此制作一个单独的 StackOverflow 问题,并确保包含尽可能多的信息,包括相关代码。我对警报了解不多,这个问题只是关于服务。
  • @Sam 应该首先调用哪个服务,ForegroundEnablingService 或 Forground
  • @AndroidMan ForegroundService
【解决方案10】:

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


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

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

【讨论】:

  • 在 Android N Preview 4 中,这不起作用。当您未指定图标时,Android 会显示(YourServiceName) is running 通知而不是您自己的通知。根据 CommonsWare 的说法,自 Android 4.3 以来就是这种情况:commonsware.com/blog/2013/07/30/…
  • 我今天做了一些测试,确认这在 Android 4.3 及更高版本中不起作用。
【解决方案11】:

屏蔽前台服务通知

这里的大多数答案要么不起作用,break the foreground service,要么违反Google Play policies

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

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

请注意,这也会阻止您应用的 toast。

Android 8.0 - 8.1

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

Android 9+

使用Notification Channel 阻止服务通知,而不影响您的其他通知。

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

【讨论】:

    【解决方案12】:

    我发现在 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:服务通知的无效通道”
    • 就个人而言,我认为apps are running in the background 通知甚至比特定于应用程序的通知还要糟糕,所以我不确定这种方法是否值得。
    【解决方案13】:

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

    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>
    

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

    【讨论】:

    • 不错的解决方法。另外我认为它需要 android 10 上的图标。使用透明占位符图像作为图标可以解决问题。
    【解决方案14】:

    对于开发人员有时客户端不想永久通知前台服务来说,这是一个相当麻烦的事情。我创建了一个虚假通知来启动服务,然后我通过notificationManager.cancel(1);取消它

      final String NOTIFICATION_CHANNEL_ID = "com.exmaple.project";
        final String channelName = "Notification";
       @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onCreate() {
        super.onCreate();
        stopForeground(true);
        Intent stopSelf = new Intent(this, Notification_Service.class);
        stopSelf.setAction("ACTION_STOP_SERVICE");
        PendingIntent pStopSelf = PendingIntent
                .getService(this, 0, stopSelf
                        , PendingIntent.FLAG_CANCEL_CURRENT);
        Notification notification;
        NotificationCompat.Action action =
                new NotificationCompat.Action.Builder(
                        0, "Close", pStopSelf
                ).build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "Notification One", NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(serviceChannel);
            notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_launcher_background)
                    .setContentText("Welcome to App.")
                    .setPriority(Notification.PRIORITY_MIN)
                    .addAction(action)
                    .build();
        } else {
            notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_launcher_background)
                    .setContentTitle("App")
                    .setContentText("Welcome to App.")
                    .setPriority(Notification.PRIORITY_MIN)
                    .addAction(action)
                    .build();
        }
        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
        notificationManager.notify(1, notification);
        startForeground(1, notification);
        notificationManager.cancel(1);
    }
    

    有时notificationManager.cancel(1); 不会删除永久通知,因为我添加了虚假的关闭操作按钮。

    操作按钮结果:

     @Override
            public int onStartCommand(Intent intent, int flags, int startId) {
                super.onStartCommand(intent, flags, startId);
                if ("ACTION_STOP_SERVICE".equals(intent.getAction())) {
                    stopSelf();
                }
         
                return START_STICKY;
            }
    

    启动服务:

     if (!isMyServiceRunning()) {
           Intent serviceIntent = new Intent(this, Notification_Service.class);
                    ContextCompat.startForegroundService(this, serviceIntent);
                }
    

    检查服务是否已经在运行。

    private boolean isMyServiceRunning() {
            ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
                if (Notification_Service.class.getName().equals(service.service.getClassName())) {
                    return true;
                }
            }
            return false;
        }
    

    【讨论】:

      【解决方案15】:

      最合适的解决方案是使用通知通道。

      您需要做的就是从您的班级中删除 notificationManager.createNotificationChannel(channel)

      val notificationManager =
                  getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
              val channel = NotificationChannel(
                  notificationChannelId,
                  "Endless Service notifications channel",
                  NotificationManager.IMPORTANCE_HIGH
              ).let {
                  it.description = "Endless Service channel"
                  it.enableLights(true)
                  it.lightColor = Color.RED
                  it.enableVibration(true)
                  it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
                  it
              }
              notificationManager.createNotificationChannel(channel)
      

      只需使用notificationManager.deleteNotificationChannel("channel_id")

      尽管不建议删除 前台服务 使用的通知。

      【讨论】:

        猜你喜欢
        • 2019-08-28
        • 1970-01-01
        • 2012-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-27
        • 2017-02-10
        相关资源
        最近更新 更多