Android - 实现服务的 startForeground 方法?

144

所以我不确定在哪里/如何实现这个方法来使我的服务在前台运行。目前,我通过在另一个活动中使用以下方式启动我的服务:

Intent i = new Intent(context, myService.class); 
context.startService(i);

然后在我的服务(myServices)的onCreate()方法中,我尝试使用startForeground()...?

Notification notification = new Notification();
startForeground(1, notification);

所以,我有点迷失并且不确定如何实现这个。


好吧,至少就我所知道的,这并不起作用,我的服务仍然作为后台服务运行并被终止。 - JDS
线程链接:https://dev59.com/lGgu5IYBdhLWcg3w8bds#12851219 - Snicolas
11个回答

141

我建议从完全填写Notification开始。这里有一个示例项目,演示了如何使用startForeground(). 请点击此处链接。


10
startForeground()可以不用通知吗?或者我们可以稍后更新同一个通知吗? - JRC
4
你为什么使用了“1337”这个词? - Cody
37
在应用程序中,它需要是独一无二的,但不一定在设备上唯一。我选择了1337,因为,嗯,它是1337。 :-) - CommonsWare
2
@Snicolas:感谢您指出安卓系统中的缺陷。我会努力解决这个问题。 - CommonsWare
@CommonsWare,一年多以后,显示0图标通知的漏洞已在JB中修补。顺便说一下,修补的方式非常棒。谢谢Mark,在RoboSpice中,我们正在最终确定我们服务的新实现,这将使我们的服务在这方面变得非常干净:只有当服务未绑定且有任务需要完成时,它们才会进入前台并显示前台通知。你有为修复这个漏洞做出贡献吗? - Snicolas
显示剩余47条评论

86

从您的主活动中,使用以下代码启动服务:

Intent i = new Intent(context, MyService.class); 
context.startService(i);

那么在您的onCreate()服务中,您将构建通知并将其设置为前台,如下所示:

然后在您的onCreate()服务中,您将构建通知并将其设置为前台,如下所示:

Intent notificationIntent = new Intent(this, MainActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);

Notification notification = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.app_icon)
                .setContentTitle("My Awesome App")
                .setContentText("Doing some work...")
                .setContentIntent(pendingIntent).build();

startForeground(1337, notification);

@mike,如何从MainActivity更新此通知? - Roon13
1
@Roon13使用ID,在这种情况下是1337,您应该能够构建一个新的通知并调用startForeground来启动它。 - mikebertiean
@mikebertiean 我知道我需要在Service类中再次调用startForeground,但是怎么做呢?我需要再次调用startService()吗? - Roon13
@mikebertiean 不用在意,我使用了NotificationManager从MainActivity更新通知。它起作用了。 - Roon13
2
不要忘记: <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> - Reeman Adel
显示剩余3条评论

47

Oreo 8.1解决方案

最近的Android版本中,我遇到了一些问题,比如无效通道ID导致RemoteServiceException。这是我的解决方法:

活动

override fun onCreate(savedInstanceState: Bundle?) {
    val intent = Intent(this, BackgroundService::class.java)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}

后台服务:

override fun onCreate() {
    super.onCreate()
    startForeground()
}

private fun startForeground() {

  
    val channelId =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel()
            } else {
                // If earlier version channel ID is not used
                // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
                ""
            }

    val notificationBuilder = NotificationCompat.Builder(this, channelId )
    val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(PRIORITY_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
    startForeground(101, notification)
}


@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String{
    val channelId = "my_service"
    val channelName = "My Background Service"
    val chan = NotificationChannel(channelId,
            channelName, NotificationManager.IMPORTANCE_HIGH)
    chan.lightColor = Color.BLUE
    chan.importance = NotificationManager.IMPORTANCE_NONE
    chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
    val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    service.createNotificationChannel(chan)
    return channelId
}

JAVA对应的等效代码

public class YourService extends Service {

    // Constants
    private static final int ID_SERVICE = 101;
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // do stuff like register for BroadcastReceiver, etc.

        // Create the Foreground Service
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? createNotificationChannel(notificationManager) : "";
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
        Notification notification = notificationBuilder.setOngoing(true)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setPriority(PRIORITY_MIN)
                .setCategory(NotificationCompat.CATEGORY_SERVICE)
                .build();

        startForeground(ID_SERVICE, notification);
    }
    
    @RequiresApi(Build.VERSION_CODES.O)
    private String createNotificationChannel(NotificationManager notificationManager){
        String channelId = "my_service_channelid";
        String channelName = "My Foreground Service";
        NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
        // omitted the LED color
        channel.setImportance(NotificationManager.IMPORTANCE_NONE);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        notificationManager.createNotificationChannel(channel);
        return channelId;
    }
}

8
您可以在您的Activity中使用ContextCompat.startForegroundService(Context,Intent),它会自动执行正确的操作。(https://developer.android.com/reference/android/support/v4/content/ContextCompat.html) - Simon Featherstone
3
如果您的最低API<21,则可能希望使用.setCategory(NotificationCompat.CATEGORY_SERVICE)而不是Notification.CATEGORY_SERVICE。请注意,翻译过程中不会添加解释或其他内容。 - Someone Somewhere
9
请注意,针对 Build.VERSION_CODES.P(API 级别 28)或更高版本的应用程序必须请求权限 Manifest.permission.FOREGROUND_SERVICE 才能使用 startForeground() 方法 - 详情请查看 https://developer.android.com/reference/android/app/Service.html#startForeground(int,%20android.app.Notification)。 - Vadim Kotov
请逐步说明权限设置过程,如果与通常的设置有任何不同,请注明。 :D @VadimKotov - gumuruh
在Android 12中,startForgroundService()引起了ForegroundServiceStartNotAllowedException异常。有人知道解决方法吗? - Liya

32

这是我设置服务前台的代码:

private void runAsForeground(){
    Intent notificationIntent = new Intent(this, RecorderMainActivity.class);
    PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,
            notificationIntent, Intent.FLAG_ACTIVITY_NEW_TASK);

    Notification notification=new NotificationCompat.Builder(this)
                                .setSmallIcon(R.drawable.ic_launcher)
                                .setContentText(getString(R.string.isRecording))
                                .setContentIntent(pendingIntent).build();

    startForeground(NOTIFICATION_ID, notification);

}

我需要使用PendingIntent构建一个通知,以便可以从通知中启动我的主Activity。

要删除该通知,只需调用stopForeground(true);

该方法在onStartCommand()中调用。请参考我的代码:https://github.com/bearstand/greyparrot/blob/master/src/com/xiong/richard/greyparrot/Mp3Recorder.java


如果您删除调用stopForeground(true)的通知,则会取消startforeground服务。 - sdelvalle57
6
你是从哪里调用这个方法的? - Srujan Barai
7
PendingIntent的上下文中,Intent.FLAG_ACTIVITY_NEW_TASK无效。 - mixel

23

除了 RAWA 的回答,这段代码也可以:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent)
} else {
    startService(intent)
}

你可以更改为:

ContextCompat.startForegroundService(context, yourIntent);
如果您查看此方法的内部,您会发现此方法为您完成了所有的检查工作。

15

如果你想将IntentService变成前台服务

那么你应该像这样覆盖onHandleIntent()

Override
protected void onHandleIntent(@Nullable Intent intent) {


    startForeground(FOREGROUND_ID,getNotification());     //<-- Makes Foreground

   // Do something

    stopForeground(true);                                // <-- Makes it again a normal Service                         

}

   

如何制作通知?

简单。这里是getNotification()方法。

public Notification getNotification()
{

    Intent intent = new Intent(this, SecondActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);


    NotificationCompat.Builder foregroundNotification = new NotificationCompat.Builder(this);
    foregroundNotification.setOngoing(true);

    foregroundNotification.setContentTitle("MY Foreground Notification")
            .setContentText("This is the first foreground notification Peace")
            .setSmallIcon(android.R.drawable.ic_btn_speak_now)
            .setContentIntent(pendingIntent);


    return foregroundNotification.build();
}

更深层次的理解

当一个服务变成前台服务时会发生什么

会发生以下情况

enter image description here

什么是前台服务?

前台服务:

  • 通过提供通知确保用户积极地意识到后台正在进行某些操作。

  • (最重要的)在系统内存不足时,不会被系统终止。

前台服务的用例

在音乐应用程序中实现歌曲下载功能


9
在onCreate()方法中添加针对"OS >= Build.VERSION_CODES.O"的代码Service类。
@Override
public void onCreate(){
    super.onCreate();

     .................................
     .................................

    //For creating the Foreground Service
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? getNotificationChannel(notificationManager) : "";
    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
    Notification notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_launcher)
           // .setPriority(PRIORITY_MIN)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .build();

    startForeground(110, notification);
}



@RequiresApi(Build.VERSION_CODES.O)
private String getNotificationChannel(NotificationManager notificationManager){
    String channelId = "channelid";
    String channelName = getResources().getString(R.string.app_name);
    NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
    channel.setImportance(NotificationManager.IMPORTANCE_NONE);
    channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    notificationManager.createNotificationChannel(channel);
    return channelId;
}

在清单文件中添加此权限:

 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

3

使用startCommand处理服务的意图。

 stopForeground(true)

这个调用将会使服务退出前台状态,如果需要更多的内存,服务可以被杀掉。这并不会停止服务的运行。如果想要停止服务,需要调用stopSelf()或相关方法。
传递值true或false表示是否要删除通知。
val ACTION_STOP_SERVICE = "stop_service"
val NOTIFICATION_ID_SERVICE = 1
...  
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    super.onStartCommand(intent, flags, startId)
    if (ACTION_STOP_SERVICE == intent.action) {
        stopForeground(true)
        stopSelf()
    } else {
        //Start your task

        //Send forground notification that a service will run in background.
        sendServiceNotification(this)
    }
    return Service.START_NOT_STICKY
}

当通过 stopSelf() 调用时,处理您的任务。

override fun onDestroy() {
    super.onDestroy()
    //Stop whatever you started
}

创建一个通知,以使服务在前台运行。
//This is from Util class so as not to cloud your service
fun sendServiceNotification(myService: Service) {
    val notificationTitle = "Service running"
    val notificationContent = "<My app> is using <service name> "
    val actionButtonText = "Stop"
    //Check android version and create channel for Android O and above
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        //You can do this on your own
        //createNotificationChannel(CHANNEL_ID_SERVICE)
    }
    //Build notification
    val notificationBuilder = NotificationCompat.Builder(applicationContext, CHANNEL_ID_SERVICE)
    notificationBuilder.setAutoCancel(true)
            .setDefaults(NotificationCompat.DEFAULT_ALL)
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.ic_location)
            .setContentTitle(notificationTitle)
            .setContentText(notificationContent)
            .setVibrate(null)
    //Add stop button on notification
    val pStopSelf = createStopButtonIntent(myService)
    notificationBuilder.addAction(R.drawable.ic_location, actionButtonText, pStopSelf)
    //Build notification
    val notificationManagerCompact = NotificationManagerCompat.from(applicationContext)
    notificationManagerCompact.notify(NOTIFICATION_ID_SERVICE, notificationBuilder.build())
    val notification = notificationBuilder.build()
    //Start notification in foreground to let user know which service is running.
    myService.startForeground(NOTIFICATION_ID_SERVICE, notification)
    //Send notification
    notificationManagerCompact.notify(NOTIFICATION_ID_SERVICE, notification)
}

在通知中添加一个停止按钮,以便用户需要时停止服务。
/**
 * Function to create stop button intent to stop the service.
 */
private fun createStopButtonIntent(myService: Service): PendingIntent? {
    val stopSelf = Intent(applicationContext, MyService::class.java)
    stopSelf.action = ACTION_STOP_SERVICE
    return PendingIntent.getService(myService, 0,
            stopSelf, PendingIntent.FLAG_CANCEL_CURRENT)
}

1
注意:如果您的应用程序目标API级别为26或更高级别,则系统会对使用或创建后台服务施加限制,除非应用程序本身在前台。
如果应用程序需要创建前台服务,则应用程序应调用startForegroundService()。该方法创建一个后台服务,但该方法向系统发出信号,表明服务将自我提升到前台。
一旦服务被创建,服务必须在五秒内调用其startForeground()方法

1
我希望你是在谈论当前的问题。否则,在Stackoverflow社区中没有这样的规定。 - Farid
@RogerGusmao 在生产环境中,代码并不总是能够保存您的项目。此外,在我的回答下面和上面有很多很好的带有代码的示例。我的项目在发布期间出现了问题,正是因为我不知道 startForegroundService 方法。 - Andrii Kovalchuk

0
在我的情况下,完全不同,因为我没有活动来在Oreo中启动服务。
以下是我用来解决前台服务问题的步骤 -
public class SocketService extends Service {
    private String TAG = this.getClass().getSimpleName();

    @Override
    public void onCreate() {
        Log.d(TAG, "Inside onCreate() API");
        if (Build.VERSION.SDK_INT >= 26) {
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
            mBuilder.setSmallIcon(R.drawable.ic_launcher);
            mBuilder.setContentTitle("Notification Alert, Click Me!");
            mBuilder.setContentText("Hi, This is Android Notification Detail!");
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            // notificationID allows you to update the notification later on.
            mNotificationManager.notify(100, mBuilder.build());
            startForeground(100, mBuilder.mNotification);
        }
        Toast.makeText(getApplicationContext(), "inside onCreate()", Toast.LENGTH_LONG).show();
    }


    @Override
    public int onStartCommand(Intent resultIntent, int resultCode, int startId) {
        Log.d(TAG, "inside onStartCommand() API");

        return startId;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "inside onDestroy() API");

    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }
}

然后要启动该服务,我触发了以下命令 -


adb -s " + serial_id + " shell am startforegroundservice -n com.test.socket.sample/.SocketService

的意思是启动一个名为 com.test.socket.sample/.SocketService 的前台服务,其中的 serial_id 为设备的序列号。


这样可以帮助我在 Oreo 设备上启动服务而不需要活动 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接