2

In the Android documentation for running a Service in the forground, the following example code is provided:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

However, this code does not work. Firstly, the constructor used for Notification is deprecated. Second, the method setLatestEventInfo(Context, String, String, PendingIntent) is no longer included in the Notification class. When I eliminate these and create a notification the correct way, an error occurs which looks like this:

Caused by: java.lang.NullPointerException: class name is null
  at android.content.ComponentName.<init>(ComponentName.java:114)
  at android.app.Service.startForeground(Service.java:654)
  at com.vcapra1.motionsensors.MainActivity.startService(MainActivity.java:53)

EDIT: Here is the code I am using, from the link provided by @ RusheelJain:

// prepare intent which is triggered if the
// notification is selected
Intent intent = new Intent(ctx, MotionMonitorService.class);
// use System.currentTimeMillis() to have a unique ID for the pending intent
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, (int) System.currentTimeMillis(), intent, 0);

// build notification
// the addAction re-use the same intent to keep the example short
Notification notification = new Notification.Builder(ctx)
  .setContentTitle("Monitoring Motion")
  .setContentText("No events yet...")
  .setSmallIcon(R.mipmap.ic_launcher)
  .setContentIntent(pendingIntent).build();

NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(NOTIFICATION_SERVICE);

// notificationManager.notify(1, notification);
startForeground(1, notification);

When I use the notificationManager.notify(1, notification); line, a notification is created, but it is not persistent and does not start the Service (which, of course, is not meant to happen). However, when I use the startForeground(1, notification); line, the app crashes with the above stack trace.

So my final question is: what is the correct way to start a Service that will keep running even if the app is closed? I have checked several sources and they all include the method which I found on the Android docs.

vcapra1
  • 1,988
  • 3
  • 26
  • 45
  • here is the example. Should work fine. http://www.vogella.com/tutorials/AndroidNotifications/article.html – Rusheel Jain Dec 26 '15 at 17:26
  • "When I eliminate these and create a notification the correct way" -- rather than posting the code that you are *not* using, you might consider posting the code that you *are* using and that generates this stack trace. – CommonsWare Dec 26 '15 at 17:40
  • @CommonsWare - I updated my post with the code that I am using – vcapra1 Dec 26 '15 at 17:58
  • In the added code, ctx is the main activity's context, and the code is inside my Service class – vcapra1 Dec 26 '15 at 18:09
  • "ctx is the main activity's context" -- your service does not have access to the activity. "and the code is inside my Service class" -- your stack trace claims otherwise. – CommonsWare Dec 26 '15 at 18:13
  • @CommonsWare - The code I have is inside of a method called start(Context ctx), which is inside the Service class and called by MainActivity when a button is pressed – vcapra1 Dec 26 '15 at 18:16
  • "The code I have is inside of a method called start(Context ctx)" -- not according to your stack trace. "which is inside the Service class and called by MainActivity when a button is pressed" -- `MainActivity` does not have access to your service object. – CommonsWare Dec 26 '15 at 18:19
  • As @CommonsWare pointed out, the problem is that you are calling startForeground() from an Activity. You have to start the service normally, _then_ the service itself calls startForeground(...) to request foreground status. – ctate Dec 26 '15 at 19:33
  • The OP has declared (or overriden) `startService()` method and most likely instantiated a new instance of the `Service` subclass by directly invoking the constructor. This would explain why the `mClassName` has not been set on the `Service` (which causes `ComponentName` to throw a NPE). I don't see anything wrong with calling `startForeground()` from outside the `Service` but maybe it needs to be called on the `Main` thread and not from within the `Service` constructor or initializer. – Mark Dec 28 '15 at 03:33

2 Answers2

3

Call startForeground() with a valid Notification from a service lifecycle method (e.g., onCreate(), onStartCommand()) or a method invoked by one of those lifecycle methods (e.g., onHandleIntent() of an IntentService).

For example, this service uses a foreground Notification while a download is going on in onHandleIntent():

/***
  Copyright (c) 2008-2012 CommonsWare, LLC
  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain   a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  From _The Busy Coder's Guide to Android Development_
    https://commonsware.com/Android
 */

package com.commonsware.android.foredown;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.v4.app.NotificationCompat;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class Downloader extends IntentService {
  public static final String ACTION_COMPLETE=
      "com.commonsware.android.downloader.action.COMPLETE";
  private static int NOTIFY_ID=1337;
  private static int FOREGROUND_ID=1338;

  public Downloader() {
    super("Downloader");
  }

  @Override
  public void onHandleIntent(Intent i) {
    try {
      String filename=i.getData().getLastPathSegment();

      startForeground(FOREGROUND_ID,
                      buildForegroundNotification(filename));

      File root=
          Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

      root.mkdirs();

      File output=new File(root, filename);

      if (output.exists()) {
        output.delete();
      }

      URL url=new URL(i.getData().toString());
      HttpURLConnection c=(HttpURLConnection)url.openConnection();
      FileOutputStream fos=new FileOutputStream(output.getPath());
      BufferedOutputStream out=new BufferedOutputStream(fos);

      try {
        InputStream in=c.getInputStream();
        byte[] buffer=new byte[8192];
        int len=0;

        while ((len=in.read(buffer)) >= 0) {
          out.write(buffer, 0, len);
        }

        out.flush();
      }
      finally {
        fos.getFD().sync();
        out.close();
        c.disconnect();
      }

      stopForeground(true);
      raiseNotification(i, output, null);
    }
    catch (IOException e2) {
      stopForeground(true);
      raiseNotification(i, null, e2);
    }
  }

  private void raiseNotification(Intent inbound, File output,
                                 Exception e) {
    NotificationCompat.Builder b=new NotificationCompat.Builder(this);

    b.setAutoCancel(true).setDefaults(Notification.DEFAULT_ALL)
     .setWhen(System.currentTimeMillis());

    if (e == null) {
      b.setContentTitle(getString(R.string.download_complete))
       .setContentText(getString(R.string.fun))
       .setSmallIcon(android.R.drawable.stat_sys_download_done)
       .setTicker(getString(R.string.download_complete));

      Intent outbound=new Intent(Intent.ACTION_VIEW);

      outbound.setDataAndType(Uri.fromFile(output), inbound.getType());

      b.setContentIntent(PendingIntent.getActivity(this, 0, outbound, 0));
    }
    else {
      b.setContentTitle(getString(R.string.exception))
       .setContentText(e.getMessage())
       .setSmallIcon(android.R.drawable.stat_notify_error)
       .setTicker(getString(R.string.exception));
    }

    NotificationManager mgr=
        (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    mgr.notify(NOTIFY_ID, b.build());
  }

  private Notification buildForegroundNotification(String filename) {
    NotificationCompat.Builder b=new NotificationCompat.Builder(this);

    b.setOngoing(true);

    b.setContentTitle(getString(R.string.downloading))
     .setContentText(filename)
     .setSmallIcon(android.R.drawable.stat_sys_download)
     .setTicker(getString(R.string.downloading));

    return(b.build());
  }
}

(from this sample project)

Your stack trace indicates that you are trying to call startForeground() from MainActivity, which should not be possible and certainly is not an appropriate pattern.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • This creates a service but no notification and the Service stops when the app is closed – vcapra1 Dec 26 '15 at 18:34
  • @vcapra1: As noted in the answer, this particular sample only has the foreground status while a download is going on. It has nothing to do with whether the app is "closed" (whatever that means) or not. I chose this particular code listing because it is short, shows the timing of the calls, and shows constructing the `Notification`. – CommonsWare Dec 26 '15 at 18:38
  • @vcapra1: More durable services are demonstrated in [this sample app](https://github.com/commonsguy/cw-omnibus/tree/master/WebServer/Simple) and [this sample app](https://github.com/commonsguy/cw-omnibus/blob/master/MediaProjection/andcorder), but those samples are considerably more complex. – CommonsWare Dec 26 '15 at 18:39
  • @CommonsWare why is it necessary to call startForeground in a lifecycle method? Could you not just call it when triggering some long-running action (and then call stopForeground when it completes) that must not be killed? I suppose your answer would be that the service should not even be running outside of those long-running actions. However, there are some edge cases, for example a music player has just been stopped, but we keep the service around for a little longer in case the user is skipping to another track soon. – Mark Dec 27 '15 at 03:18
  • @MarkCarter: "why is it necessary to call startForeground in a lifecycle method?" -- every method in a component is either a constructor/initializer, a lifecycle method, or something called from a lifecycle method. You cannot call `startForeground()` from a constructor (crashes), and since it returns `void` it cannot be used in an initializer. The other options are fine, as I stated in my answer. The OP seems to be calling `startForeground()` from outside the service. "Could you not just call it when triggering some long-running action" -- yes, as my code does in `onHandleIntent()`. – CommonsWare Dec 27 '15 at 12:50
  • 1
    @CommonsWare "every method in a component is either a constructor/initializer, a lifecycle method, or something called from a lifecycle method" or (in the case of a Service) something called by something else that has bound to it, as is common for audio playback controls, for example. I'm not saying that calling it from lifecycle/`onHandleintent()` is wrong, but simply that it is not the only way. – Mark Dec 27 '15 at 16:43
  • 1
    @MarkCarter: "something called by something else that has bound to it, as is common for audio playback controls, for example" -- very true, I missed that. Using `startForeground()` from a method on the `Binder` should be safe as well. Again, the key is that `startForeground()` needs to be called by the service, on itself, after the service has been created. – CommonsWare Dec 27 '15 at 16:46
  • I am starting two services at once. Can I have them both 'startForeground'? Thanks so much! – Ruchir Baronia Feb 03 '16 at 04:46
  • @RuchirBaronia: I have no idea, sorry. – CommonsWare Feb 03 '16 at 11:47
2

You have to use Notifiation.Builder instead of the Notification's class constructor.

Here is an example of starting foreground service with actions:

private Notification getForegroundNotification() {      
    Intent showTaskIntent = MyActivity.createStartIntent(this, mContext);

    PendingIntent contentIntent = PendingIntent.getActivity(
            getApplicationContext(),
            0,
            showTaskIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);


    Notification.Builder builder = new Notification.Builder(this);

    builder.setContentTitle(getString(R.string.title))
        //.setContentText(getString(R.string.notification_text))
        .setSmallIcon(R.drawable.notification_white)
        .setContentIntent(contentIntent);



    Intent shutdownActionIntent = new Intent(this, MyService.class);
    shutdownActionIntent.setAction(SHUTDOWN_NOTIFICATION_ACTION);

    PendingIntent shutdownPendingIntent = PendingIntent.getService(
            getApplicationContext(),
            1,
            shutdownActionIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);

    builder.addAction(android.R.drawable.ic_lock_power_off, getString(R.string.shutdown_action), shutdownPendingIntent);

    if(isRunningJellybeanOrLater()) {
        builder.setPriority(Notification.PRIORITY_MAX);
        return builder.build();
    } else {
        return builder.getNotification();
    }
} 

This is how you start it:

void handleStartAction() {      
        Notification notification = getForegroundNotification();
        startForeground(FOREGROUND_NOTIFICATION_ID, notification);
    }
Georgy
  • 364
  • 2
  • 14
  • Yes, this fixes the creation of the Notification, but I still get the NPE when calling `startForeground`. (Sorry I think I updated my question at the same time you posted this answer) – vcapra1 Dec 26 '15 at 17:56