我想创建一个服务并让它在前台运行。
大多数示例代码都有通知。但我不想显示任何通知。那可能吗?
你能给我一些例子吗?有没有其他选择?
我的应用服务正在做媒体播放器。如何使系统不杀死我的服务,除了应用程序自己杀死它(比如通过按钮暂停或停止音乐)。
我想创建一个服务并让它在前台运行。
大多数示例代码都有通知。但我不想显示任何通知。那可能吗?
你能给我一些例子吗?有没有其他选择?
我的应用服务正在做媒体播放器。如何使系统不杀死我的服务,除了应用程序自己杀死它(比如通过按钮暂停或停止音乐)。
作为 Android 平台的一项安全功能,在任何情况下,您都不能在没有通知的情况下拥有前台服务。这是因为与后台服务相比,前台服务消耗更多资源并且受到不同的调度约束(即它不会被杀死得那么快),并且用户需要知道什么可能在消耗他们的电池。所以,不要这样做。
但是,可能会有“假”通知,即您可以制作透明通知图标 (iirc) 。这对你的用户来说是非常不诚实的,你没有理由这样做,除了杀死他们的电池并因此产生恶意软件。
更新:这在 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);
我已经接受了通知本身仍然需要存在的事实,但是对于仍然想要隐藏它的人,我可能也找到了解决方法:
startForeground()
使用通知和所有内容启动虚假服务。startForeground()
(相同的通知 ID)stopSelf()
并在 onDestroy 调用stopForeground(true)
)。瞧!根本没有通知,您的第二个服务继续运行。
这在 Android 7.1 中不再有效,并且可能违反Google Play 的开发者政策。
相反,让用户阻止服务通知。
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;
}
}
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;
}
}
<service android:name=".ForegroundEnablingService" />
<service android:name=".ForegroundService" />
测试和工作:
自 Android 7.1 起不再工作。
警告:虽然这个答案似乎有效,但实际上它默默地阻止了您的服务成为前台服务。
原答案:
只需将通知的 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.Builder
Support Library v7 提供的版本,而不是Notification.Builder
.
编辑
由于较新的 api 级别的安全原因,此代码将不再工作
NotificationId
不能设置为“0”(这会导致应用崩溃)
startForeground(1, notification)
这是显示通知的完美方式(推荐方法)
但是,如果您需要它而不考虑推荐的方法,请尝试"notificationManager.createNotificationChannel("channel_id")"
从您的代码中删除。
或
使用notificationManager.removeNotificationChannel(channel)
您可以使用它(如@Kristopher Micinski 建议的那样):
Notification note = new Notification( 0, null, System.currentTimeMillis() );
note.flags |= Notification.FLAG_NO_CLEAR;
startForeground( 42, note );
更新:
请注意,Android KitKat+ 版本不再允许这样做。请记住,这或多或少违反了 Android 中的设计原则,即@Kristopher Micinski 提到的让用户可以看到后台操作的设计原则
您可以使用 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 或更低版本
更新:这不再适用于 Android 4.3 及更高版本
有一种解决方法。尝试在不设置图标的情况下创建通知,并且通知不会显示。不知道它是如何工作的,但确实如此:)
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("Title")
.setTicker("Title")
.setContentText("App running")
//.setSmallIcon(R.drawable.picture)
.build();
startForeground(101, notification);
这里的大多数答案要么不起作用,要么破坏前台服务,要么违反Google Play 政策。
可靠且安全地隐藏通知的唯一方法是让用户阻止它。
唯一的方法是阻止来自您的应用程序的所有通知:
将用户发送到应用程序的详细信息屏幕:
Uri uri = Uri.fromParts("package", getPackageName(), null);
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
startActivity(intent);
让用户阻止应用程序的通知
请注意,这也会阻止您的应用程序的 toast。
在 Android O 上阻止通知是不值得的,因为操作系统只会将其替换为“在后台运行”或“使用电池”通知。
使用通知渠道来阻止服务通知,而不影响您的其他通知。
将用户发送到通知通道的设置
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())
.putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
startActivity(intent);
让用户屏蔽频道的通知
更新:这不再适用于 Android 4.3 及更高版本
我将 Notification 的构造函数的图标参数设置为零,然后将生成的通知传递给 startForeground()。日志中没有错误,也没有显示通知。但是,我不知道该服务是否已成功前台 - 有什么方法可以检查吗?
编辑:用dumpsys检查,确实该服务在我的2.3系统上处于前台。尚未检查其他操作系统版本。
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 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 应用程序正在后台运行”通知。
对于开发人员的有时客户不想要前台服务的永久通知来说,这是一个相当麻烦的事情。我创建了一个虚假通知来启动服务,之后我取消了它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;
}
更新:这不再适用于 Android 7.1 及更高版本
这是使您的应用程序的 oom_adj 为 1 的方法(在 ANDROID 6.0 SDK 模拟器中测试)。添加临时服务,在您的主要服务调用startForgroundService(NOTIFICATION_ID, notificion)
中。然后再次启动startForgroundService(NOTIFICATION_ID, notificion)
具有相同通知 id 的临时服务调用,一段时间后在临时服务调用 stopForgroundService(true) 以关闭正在运行的通知。
最合适的解决方案是使用 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")
虽然,不建议删除前台服务使用的通知。
尽管这不是直接的问题,但许多搜索此问题的人可以通过使用WorkManager轻松解决创建持久、长时间运行的任务的挑战
截至 2022 年,它是 Google 推荐的用于运行所有类型的后台任务(一次性、定期、加速、前台等)的 API。
您还可以将您的应用程序声明为持久的。
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/Theme"
*android:persistent="true"* >
</application>
这实质上将您的应用程序设置为更高的内存优先级,从而降低了它被杀死的可能性。