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

时间:2012-06-09 15:54:52

标签: android android-service foreground

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

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

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

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

16 个答案:

答案 0 :(得分:75)

更新:这在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. 启动您要运行的实际服务
  4. 停止第一项(假冒)服务(您可以拨打stopSelf()并在onDestroy中拨打stopForeground(true))。
  5. 瞧!完全没有通知,您的第二项服务一直在运行。

答案 1 :(得分:73)

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

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

答案 2 :(得分:19)

这在Android 7.1+中不再有效。此技术可能违反Google Play's developer policiesApps that introduce or exploit security vulnerabilities.)。

相反,我建议having 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
  • Sony 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起不再有效。

答案 3 :(得分:15)

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

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

<强>更新

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

答案 4 :(得分:9)

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

答案 5 :(得分:6)

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

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

答案 6 :(得分:6)

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

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

答案 7 :(得分: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);

答案 8 :(得分: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; x应用程序正在后台运行&#39;通知。

答案 9 :(得分:1)

阻止前台服务通知

Android 7.1+无法被利用来隐藏通知。相反,让用户阻止它。

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

  3. 注意这也会阻止你的应用程序的祝酒词。

    Android 8

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

    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. 拥有用户阻止频道的通知

答案 10 :(得分:0)

您可以使用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或更低版本

答案 11 :(得分:0)

对于开发人员来说,有时客户不希望前台服务的永久通知非常麻烦。我创建了一个假通知来启动服务,然后我在 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;
    }

答案 12 :(得分:0)

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

您需要做的就是从您的班级中删除 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")

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

答案 13 :(得分:-2)

这是一种让你的应用程序的oom_adj为1的方法(在ANDROID 6.0 SDK模拟器中测试)。在您的主要服务电话startForgroundService(NOTIFICATION_ID, notificion)中添加临时服务。然后在临时服务调用stopForgroundService(true)中暂时启动临时服务调用startForgroundService(NOTIFICATION_ID, notificion)并再次使用相同的通知ID,以解除onging ontification。

答案 14 :(得分:-3)

几个月前我开发了一个简单的媒体播放器。所以我相信如果你做的事情如下:

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

startService(ⅰ);

然后系统应该无法终止您的服务。

参考read the paragraph which discuss when system stops the service

答案 15 :(得分:-3)

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

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

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