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

Muh*_*ama 72 android foreground android-service

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

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

你能举个例子吗?还有其他选择吗?

我的应用服务正在做媒体播放器.如何使系统不杀死我的服务,除了应用程序自杀(如按钮暂停或停止音乐).

Kri*_*ski 84

作为Android平台的安全功能,在任何情况下,您都不能在没有通知的情况下拥有前瞻性服务.这是因为前景服务消耗了更多的资源并且受到与后台服务不同的调度限制(即,它不会被快速杀死),并且用户需要知道什么可能正在吃他们的电池.所以,要这样做.

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

  • 好吧,似乎你有理由这样做,用户已经要求它.虽然你说的可能是不诚实的,但我要求测试组正在研究的产品.也许Android应该有一个选项来隐藏你知道一直运行的某些服务的通知.否则,您的通知栏将看起来像一棵圣诞树. (16认同)
  • @Radu实际上你*不应该*在前台拥有那么多应用程序:这会扼杀你的电池寿命,因为它们的安排方式不同(基本上,它们不会死).对于一个mod来说这可能是可以接受的,但是我没有看到这个选项很快成为了香草Android."用户"通常不知道什么对他们最好.. (3认同)
  • 谢谢.这让我清楚为什么setForeground需要通知. (2认同)
  • @Radu不支持你的论点."获奖应用程序"通常是通过系统不一致性(如任务杀手)"修复"Android漏洞的应用程序.没有理由你只需要使用前台服务,因为"屡获殊荣的应用程序"可以:如果你这样做,你承认要杀死用户的电池. (2认同)
  • 显然,这种方法将不再适用于4.3.https://plus.google.com/u/0/105051985738280261832/posts/MTinJWdNL8t (2认同)

Lio*_*luz 76

更新:这在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);
Run Code Online (Sandbox Code Playgroud)

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

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

瞧!根本没有通知,您的第二个服务一直在运行.

  • 谢谢你.我认为有些人没有意识到Android内部商业用途的开发(也就是说,它不打算出现在市场上或被卖给Joe Blow).有时候人们要求"停止显示服务通知"这样的事情,即使它不是一般消费的犹太人,也可以看到周围的工作. (21认同)
  • 当然,您应该假设这在平台的未来版本中不起作用. (9认同)
  • @JamieB我简单地同意了......我向Dianne Hackborn建议他们需要重新思考后台服务的整个概念,并且可能会在没有通知的情况下添加在后台运行服务的权限.对于我们开发人员来说,如果有通知是不重要的 - 这是USER要求删除它!:)顺便问一下,你能验证它对你有用吗? (5认同)

Sam*_*Sam 19

这在Android 7.1+中不再有效.此技术可能违反Google Play的开发者政策(__CODE__).

相反,我建议让用户阻止服务通知.


下面是我的实现在技术,答案利奥尔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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

AndroidManifest.xml中

<service android:name=".ForegroundEnablingService" />
<service android:name=".ForegroundService" />
Run Code Online (Sandbox Code Playgroud)

兼容性

经过测试和处理:

  • 官方模拟器
    • 4.0.2
    • 4.1.2
    • 4.2.2
    • 4.3.1
    • 4.4.2
    • 5.0.2
    • 5.1.1
    • 6
    • 7
  • 索尼Xperia M.
    • 4.1.2
    • 4.3
  • 三星Galaxy ?
    • 4.4.2
    • 5.X
  • Genymotion
    • 5
    • 6
  • 的CyanogenMod
    • 5.1.1

不再适用于Android 7.1.

  • 还没有测试过,但至少获得一个权限会很好!!! 对于"To Google"部分+1,我的用户抱怨要让应用在后台运行所需的通知,我还抱怨skype/llama和其他任何应用所需的通知 (2认同)

Sni*_*las 15

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

Notification note = new Notification( 0, null, System.currentTimeMillis() );
note.flags |= Notification.FLAG_NO_CLEAR;
startForeground( 42, note );
Run Code Online (Sandbox Code Playgroud)

更新:

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

  • 使用Android 18时,通知不再是不可见的,但会显示"应用程序正在运行,触摸以获取更多信息或停止应用程序" (4认同)
  • 请注意,SDK 17似乎不再接受这一点.如果传递给通知的drawable为0,则服务不会转到前台. (2认同)
  • 警告:我收到来自索尼爱立信LT26i(Xperia S)和Android 4.1.2(SDK 16)的崩溃报告:android.app.RemoteServiceException:从包发布错误通知...:无法创建图标:StatusBarIcon(pkg = ... id = 0x7f020052 level = 0 visible = true num = 0)我已经将图标ID设置为0,直到SDK 17.从SDK 18我已将其设置为有效的可绘制资源.也许,您需要SDK 16中的有效图标ID! (2认同)

phn*_*mnn 12

您可以使用 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);
Run Code Online (Sandbox Code Playgroud)

custom_notif.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp">
</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

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


Ang*_*i H 10

只需将通知的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);
    ...
Run Code Online (Sandbox Code Playgroud)

您可以获得的好处是Service,除非在高内存压力下,否则将能够在不被Android系统破坏的情况下以高优先级运行.

编辑

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


Sam*_*Sam 8

阻止前台服务通知

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

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

安卓 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);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 让用户阻止应用程序的通知

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

安卓 8.0 - 8.1

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

安卓 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);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 让用户屏蔽频道的通知


Vla*_*ski 7

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

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

  • 在Android N预览版4中,这不起作用.当你没有指定图标时,Android会显示`(YourServiceName)正在运行`通知而不是你自己的通知.根据CommonsWare,自Android 4.3以来就是这种情况:https://commonsware.com/blog/2013/07/30/notifications-foreground-services-android-4p3.html (3认同)

Ale*_*uss 6

我将icon参数设置为Notification的构造函数为零,然后将生成的通知传递给startForeground().日志中没有错误,也没有显示任何通知.不过,我不知道这项服务是否成功有效 - 有什么方法可以检查吗?

编辑:使用dumpsys进行检查,确实该服务是在我的2.3系统上进行的.还没有检查过其他操作系统版本.

  • 使用命令"adb shell dumpsys activity services"检查"isForeground"是否设置为true. (9认同)