105

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

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

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

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

4

16 回答 16

106

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

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

于 2012-06-09T20:10:56.527 回答
82

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

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

于 2013-08-16T20:19:59.173 回答
22

这在 Android 7.1 中不再有效,并且可能违反Google Play 的开发者政策

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


这是我在Lior Iluz的回答中实现的技术。

代码

前台服务.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.X
  • Genymotion
    • 5.0
    • 6.0
  • CyanogenMod
    • 5.1.1

自 Android 7.1 起不再工作。

于 2016-06-01T22:33:00.533 回答
17

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

原答案:


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

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

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

编辑

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

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

startForeground(1, notification)

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

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

使用notificationManager.removeNotificationChannel(channel)

于 2015-05-22T08:05:36.607 回答
16

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

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

更新:

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

于 2012-10-12T02:02:18.840 回答
13

您可以使用 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>

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

于 2019-01-03T07:57:30.793 回答
9

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


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

    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("Title")
            .setTicker("Title")
            .setContentText("App running")
            //.setSmallIcon(R.drawable.picture)
            .build();
    startForeground(101,  notification);
于 2016-06-08T12:27:59.050 回答
9

阻止前台服务通知

这里的大多数答案要么不起作用,要么破坏前台服务,要么违反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);
    
  2. 让用户阻止应用程序的通知

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

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

于 2016-12-16T05:29:59.867 回答
7

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


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

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

于 2012-06-18T01:08:25.700 回答
5

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);
于 2015-03-09T17:58:29.537 回答
4

我发现在 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 应用程序正在后台运行”通知。

于 2017-11-19T11:29:44.347 回答
2

对于开发人员的有时客户不想要前台服务的永久通知来说,这是一个相当麻烦的事情。我创建了一个虚假通知来启动服务,之后我取消了它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;
    }
于 2021-02-12T11:33:07.187 回答
0

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


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

于 2016-04-06T14:44:31.933 回答
0

最合适的解决方案是使用 Notification Channel。

你需要做的就是 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")

虽然,不建议删除前台服务使用的通知。

于 2021-05-27T12:38:45.177 回答
0

尽管这不是直接的问题,但许多搜索此问题的人可以通过使用WorkManager轻松解决创建持久、长时间运行的任务的挑战

截至 2022 年,它是 Google 推荐的用于运行所有类型的后台任务(一次性、定期、加速、前台等)的 API。

于 2022-01-14T21:26:32.653 回答
-1

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

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

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

于 2012-08-15T00:43:44.760 回答