Skip to content

front-line-tech/background-service-lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 

Repository files navigation

Download

background-service-lib

Essential classes for reliable background Services.

Change log

Version 2.0 to 2.1

  • Greater visibility of parameters inside bound and messenger services.
  • Support for notification channels, for newer versions of Android.
  • Republish to bintray under front-line-tech organisation.
  • Rebuild for latest gradle, use bintray plugin for improved publishing.

Versions 1.2 to 1.8

  • Define separate service classes: "Messenger Services" and "Bound Services".
  • New Service classes: AbstractBackgroundBindingService, AbstractBackgroundMessengerService.
  • Provide abstract activites that can utilise each: AbstractMessengerServiceBoundAppCompatActivity, AbstractServiceBoundAppCompatActivity.
  • Abstract away shared components into: AbstractBackgroundService, AbstractPermissionExtensionAppCompatActivity.
  • Provide MessengerServiceConnection to communicate across process boundaries from any Context.
  • Add visbility of bound status for MessengerServiceConnection.
  • Update sample code in documentation.

Version 1.1

  • Add support for storing and restoring from SharedPreferences.
  • Add support for Messenger Services that can receive inter-process communications.
  • Distinguish between Messenger Services and Binding Services.
  • Update the Sample app to illustrate both Service types, and demonstration communication with them.

Version 1.0

Initial release with support for persistent background Binding Services.

What is servicelib?

Servicelib provide you with a number of classes to support you to quickly build a Binding Service into your app that runs persistently in the background. It also provides some classes that will help you build Activities that can easily bind and communicate with your Service.

Additionally, Servicelib also supports interprocess-communication through Messenger Services, allowing you to establish a Service in one app, and communicate with it from any others.

Android can destroy any Service or Activity at any time to relieve memory constraints. Servicelib helps you to cope with this in the following ways:

  • Services based on Servicelib can be configured to hint strongly that the user has an interest in their uninterrupted operation. You should think hard about whether you need this feature: Services that run indefinitely consume resources that could otherwise be freed up to keep the device responsive and make room for other apps.
  • Servicelib Services can be set up to run regardless of any activities (bound to it or not), and can be easily set up to start on boot.
  • If a Service is halted due to memory constraints, or any other reason, it will restart as soon as conditions become favourable again.

Nothing is going to make Android app Services simple or easy to build and use - but this library will help you cut out a lot of the routine boilerplate code.

What are Binding Services?

Binding Services allow Activities (and other contextual objects) in your app to bind to them, and then present an interface that can be called by the methods on the interface. This Service has an indefinite lifetime, and so will survive the destruction of any given Activity or Context.

What are Messenger Services?

Messenger Services are able to receive inter-process communications through a Messenger interface. They benefit from the same long-life as Binding Services, but can provide their functionality to any number of applications.

How can an Activity bind to a Binding Service?

Servicelib provides an abstract class called AbstractServiceBoundAppCompatActivity that automatically binds to a Service whenever it is active, and unbinds when it is not. This gives you access to the Service from your Activity as if it were just another POJO - but only when the activity is running in the foreground. The Activity automatically disconnects when it is not in use (ie. when not visible to the user).

Your Activities are also able to request permissions for your app, and helper methods can help you to determine if all the required permissions are granted before taking an action.

Installation through Gradle

Standard

This project is available through jCenter. To import this project using Gradle, add the following to the dependencies of your Gradle build file for the module:

implementation 'com.flt.servicelib:background-service-lib:2.1'

Fallback

To import this project using Gradle, add the following to your dependencies:

implementation 'com.flt.servicelib:background-service-lib:2.1'

Also, ensure that there's an entry for the maven repository in bintray listed in the root build.gradle for your Project:

allprojects {
    repositories {
        ...
        maven {
            url  "https://dl.bintray.com/front-line-tech/android-libs"
        }
    }
}

You are welcome to clone and fork this repository to your heart's content.

How do I use it?

In order to build a reliable Binding Service, there are a few things you must do. Servicelib provides you with the tools you'll need:

  • Create an interface for your Service - with all the methods you expect any bound activities to call.
  • Subclass the appropriate abstract Service class, and implement all your abstract Service methods.
  • Provide the Service with a basic configuration.
  • Subclass the AbstractBootReceiver, and register it in your manifest file to receive the BOOT_COMPLETED intent.
  • Override the default Application class, and use it to start the Service when the Application starts.
  • Set this Application class as the android:name for your Application in the manifest file.
  • Build an Activity that subclasses AbstractServiceBoundAppCompatActivity or AbstractMessengerServiceBoundAppCompatActivity, and implement the various abstract methods provided.
  • Ensure that the required permissions specified in getRequiredPermissions in your activities and Service are also present in the manifest file.
  • Test!

All of these steps are demonstrated in the accompanying app - which serves to show a pair of working Services (one Binding Service and one Messenger Service), and an Activity that can call each.

How do I set up a Binding Service?

Subclass the AbstractBackgroundBindingService and provide it with details of the interface it implements:

public class DemonstrationService extends AbstractBackgroundBindingService<DemonstrationServiceInterface> implements DemonstrationServiceInterface {

How do I set up a Messenger Service for inter-process communcation?

Subclass the AbstractBackgroundMessengerService - you'll note there is no interface to implement. The Service uses a Messenger for inter-process communication, and Messengers communicate by means of an integer message id, and a Bundle.

When registering the Service in your manifest file, provide a process name:

<service
  android:name=".service.SharedService"
  android:enabled="true"
  android:exported="true"
  android:process=":my_process">
</service>

You'll need to implement the onMessageReceived method in your new service to interpret the Messages the Messenger may receive, eg.

@Override
protected void onMessageReceived(Message message) {
  switch (message.what) {
    case SharedConstants.MESSAGE_TYPE_1:
      Bundle data = message.getData();
      // TODO: do something with this message and data
      break;
    default:
      informUser("Received unrecognised Message.");
      break;
  }
}

You might want to switch on the msg.what - which is the message id. The Message also contains a Bundle in which you can put some other data. (NB. Bundles can contain Parcels. I have found the Parceler library extremely useful here for bundling up more complex objects and getting them transferred across process boundaries. Don't forget to provide the appropriate ClassLoader to your Parceler at the other end!)

See below for more information on how to communicate with your Service.

What are the various abstract methods in common between both Service types, and how should I implement them?

There are a small number of abstract methods to implement - each of which handles a specific permissions-related event:

  • configure - Allows you to configure the Service, providing it with details to build a notification.
  • getRequiredPermissions - Allows you to specify the permissions this Service can check for.
  • storeTo - Allows you to store the state of your Service to some private SharedPreferences.
  • restoreFrom - Allows you to restore your Service from some private SharedPreferences stored for your application.

The other methods you should provide are those from the Service's interface - which can then be called by any bound Activities.

How do I communicate with my Messenger Service?

The easiest way is to subclass the AbstractMessengerServiceBoundAppCompatActivity. Then, simply override the createServiceComponentName() method to indicate which Service you are trying to connect to. (NB. We use a ComponentName to indicate the service to help apps that do not share classes with your Service to connect without referencing your Service.class.)

_In theory you can connect to any service that implements a Messenger-based ServiceConnection in place of its Binder. I'd recommend using a service that extends AbstractBackgroundMessengerService as this is a little simpler to use, and provides support for 2-way communication.

To communicate with the Service, use the connection.send(int messageId, Bundle data) method. The connection object also provides you with a method to check the state of the connection: isBound().

To receive responses, this abstract Activity class provides a method to implement: onMessageReceived(Message message) which provides you with any new messages received from the Service it is connected to - provide 2-way communication. The Service can choose to reply to any message you send to it through the connection object - as the connection object also maintains a Messenger to be used to receive messages, and posts this in each Message.replyTo field.

And how do I communicate with my Messenger Service from any other context?

Alternatively, you'll need to bind an instance of a MessengerServiceConnection to it - and you can do that with an Android intent. In the following example example, we are communicating from an ordinary Activity, so this is a Context:

messaging_service_connection = new MessagingServiceConnection();
Intent intent = new Intent(this, DemonstrationMessagingService.class);
bindService(intent, messaging_service_connection, Context.BIND_AUTO_CREATE);

To do it with a ComponentName instead of the Service.class:

messaging_service_connection = new MessagingServiceConnection();
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.flt.sampleservice", "com.flt.sampleservice.DemonstrationMessagingService"));
bindService(intent, messaging_service_connection, Context.BIND_AUTO_CREATE);

Once done, you can communicate with it through a MessagingServiceConnection:

int messageType = SOME_CONSTANT;
Bundle bundle = new Bundle(); // you can put primitives and Parcelables into Bundles
messaging_service_connection.send(messageType, bundle);

The MessagingServiceConnection allows you to create and set a Listener object to help monitor the connection:

public interface Listener {
  void onConnected(MessagingServiceConnection source);
  void onDisconnected(MessagingServiceConnection source);
  void onMessageReceived(Message message);
}

As you can see, this also allows you to receive responses from the Service through the onMessageReceived method. As mentioned above, the MessagingServiceConnection maintains a Messenger object to receive replies, and inserts it into each Message.replyTo field that it sends.

Why do I need to store and restore from SharedPreferences?

Whilst this library will help you to set up a Service that Android really doesn't want to destroy, there are no guarantees! Android may be forced to destroy your service due to low memory, and under those circumstances it is important that your Service store its state, so that it can restore itself as soon as memory becomes available again.

Why do I need a notification for my Service?

An Android Service can use a notification to become a Foreground Service. In the foreground, Android considers that the user has an interest in the service (which is why there is a visual notification) - and so prioritises it very highly. As a result, the Service is far less likely to be destroyed due to memory constraints until it really has to be.

Of course, Android reserves the right to destroy any Service or Activity, so you must still write resilient code, but this increases the likelihood that the Service will run for as much time as possible - and allows you to provide a visual indicator to your user to reassure them that the Service is running.

How do I configure the notification in my Service?

Configure your Service's notification using the configure method in your Service class. You may make modifications to (or provide an entirely new) BackgroundServiceConfig object and return the result:

@Override
protected BackgroundServiceConfig configure(BackgroundServiceConfig config) {
  config.setNotification(
      getString(R.string.service_notification_title),
      getString(R.string.service_notification_content),
      getString(R.string.service_notification_ticker),
      R.mipmap.ic_service,
      MainActivity.class);

  return config;
}

What if my service is stopped by Android?

The purpose of this library is to help you build a Service that Android will try to preserve - however, when memory is tight and all else has failed, your service can still be stop until conditions are favourable again.

In this case, your Service should stop gracefully - and record its state (using the storeTo method). It follows then, that your Service should also check for and learn its stored state when it resumes (in the restoreFrom method) - so as to be able to pick up where it left off. As of version 1.1, the library provides these methods and calls them during onCreate and destroy events.

Why do I need to subclass Application?

When an Activity unbinds from a Service, the Service checks how many bindings it has. If those bindings have dropped to zero and the Service was not started by any other means then the Service is stopped. If you have already started the Service separately - ie. through the Application object (or the BOOT_COMPLETED BroadcastReceiver), then it will continue to run.

We override the Application class as it is the perfect place to ensure that the Service is started without a binding, no matter which Activity the user goes to after that.

public class DemonstrationApp extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    Intent i = new Intent(this, DemonstrationService.class);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      startForegroundService(i);
    } else {
      startService(i);
    }
  }
}

Tell me some more about AbstractServiceBoundAppCompatActivity

The AbstractServiceBoundAppCompatActivity class attempts to bind to your Binding Service as soon as it is in use, and unbinds whenever it is not. It then provides access to your service in the service field.

The Activity also provides a number of methods and member variables of note:

  • You can test to see if the activity has succesfully bound to your Service with the boolean bound member variable.
  • Implement the onBoundChanged method to update your UI and hide or show features based on whether the Service is available.
  • Make use of the requestAllPermissions method to request permissions from your user.
  • Define the permissions you need by implementing the String[] getRequiredPermissions method.
  • Two methods will be called for events related to permissions: onPermissionsGranted, onNotAllPermissionsGranted.
  • A helper method called informUser creates a Toast for the user safely (even if not called from the UI thread).
  • A helper method called setTitleBarToVersionWith can update the title bar of the Activity with the version name of the app.

Remember that all permissions requested by any part of the app must also be listed in the app's manifest, with uses-permission tags.

What's all this about permissions?

Your Service won't be able to ask for its own permissions - so you'll need help from an Activity to get these granted by the user. The Android methods for requesting permissions are ok, but the detail is a pain to have to implement over and over. The AbstractServiceBoundAppCompatActivity has the capability to request permissions from the user, and will trigger one or more of the (relatively simple) abstract event methods on completion of this task.

How do I request permissions?

In your subclass of AbstractServiceBoundAppCompatActivity, implement getRequiredPermissions to provide a String[] of permissions to request, and then from your UI code make a call to requestAllPermissions:

@Override
protected String[] getRequiredPermissions() {
  return new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE };
}

btn_get_permissions.setOnClickListener(new View.OnClickListener() {
  @your
  public void onClick(View v) {
    requestAllPermissions();
  }
});

The outcome will result in a call to one of the following abstract methods:

@Override
protected void onPermissionsGranted() {
  informUser(R.string.toast_permissions_granted);
  updateUI();
}

@Override
protected void onNotAllPermissionsGranted() {
  informUser(R.string.toast_permissions_not_granted);
  updateUI();
}

Make sure that your manifest file contains the same permissions as uses-permission tags:

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

What about those overlay permission methods?

You probably don't need the overlay permissions - they're used for very specific things, eg. a messenger app that's going to pop up a chat bubble over the top of whatever the user is currently doing - even if not in your app.

The overlay permission is special, different, and contentious! (Not least because the ability to draw over any other apps carries with it significant risk of abuse.) Right now it is granted automatically to apps installed from the Play Store because reasons. That may be changing with Android O, and it's possible a new method for requesting that permission will come into play. In the meantime, you can use hasOverlayPermission and requestOverlayPermission should you need to.

I recommend checking and not assuming you have the overlay permission it (if you need it), as the rules are predicted to change.

You will also need a line in the manifest file to request this permission, which is unusually named:

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

NB. The permissions and window types for this may be changing in Android O. See the Android O changes documentation for more information.

If you are not using the overlay permissions, then simply implement empty instances of the related abstract methods on your Activity.

How do I ensure my Service starts when the phone is powered on?

This is where you need a BroadcastReceiver. Subclass the AbstractBootReceiver, and implement the getServiceClass method to provide it with the class for your Service:

public class DemonstrationBootReceiver extends AbstractBootReceiver<DemonstrationService> {
  @Override
  protected Class<DemonstrationService> getServiceClass() {
    return DemonstrationService.class;
  }
}

Your receiver will also need an entry in the manifest file, with an intent filter to ensure it receives the BOOT_COMPLETED intents:

    <receiver
        android:name=".DemonstrationBootReceiver"
        android:enabled="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
            <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
    </receiver>

Don't forget to ensure that you register for the RECEIVE_BOOT_COMPLETED permission inside the manifest, too:

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

Troubleshooting

There are a number of things to consider if your app does not seem to receive these intents during boot up:

  • Have you registered for RECEIVE_BOOT_COMPLETED permission in your manifest?
  • Have you registered your receiver for the known boot completion intents?
  • Has your user got battery optimisations for your app in the phone's settings?
  • Have you exported your receiver? (Not recommended.)

In tests, some devices take several minutes before they'll decide that boot has actually completed and so broadcast the intent to your app.

Credits

Licensing

Do as you please - commercially or otherwise, but if you significantly improve this project, I'd invite you to let me know what you did through a comment or a pull request. Thanks!