Lab 3. Android Services

Objectives

  • Learn the basics of how to perform background work in an Android app
  • Understand the concept of Service as an Android component
  • Use and differentiate between Started, Bound and Intent services
  • Understand the services' lifecycle in relation with the app's and its activities' lifecycle

Services

Services are the application components that execute logic even in background, when the activities of the app are not visible. Services do not have an UI and run on the main thread of the hosting process.

:!: Throughout this lab we refer to background (not visible) and foreground (visible) in terms of components' lifecycle not in terms of threading.

Types:

    • it will continue to live even if no other component is bound to it
    • we just call startService
    • bounds a component to a service. Binding creates a connection between these components and they can use it for communication.
    • a bound service that is not started will “die” when no component is bound to it
    • calling bindService binds it but does not start it. If the service doesn't exist this call will also create it.
    • used for executing a certain task on a worker thread, in background.
    • a service that is considered running in the foreground, with the user being aware of it.
    • such a service is less likely to be killed by the system
    • it requires a notification to be permanently shown to the user.
    • since API Level 26 services cannot be started in the background using the startService method, they need to be started as foreground services

A service can be started, bound or both started and bound. Such a service runs on the application's main thread. We can also declare services to run in a different process by using a flag. Then that service needs to be bound, in order to have inter-process communication. An Intent service runs on a different thread (worker-thread) and is created when receiving an intent and it is destroyed after finishing the job given by the intent.

Not only activities, but also services can bind to other services.

Services must be unbound in onDestroy or an earlier callback. If a stopped bound service still has ServiceConnection objects bound to it, the system will not destroy it until all of the bindings are removed.

Running in background concerns:

Since Android 8 (Oreo, API level 26), the system provides more limitations regarding background work and also the phone vendors have become more aggressive regarding the resources the apps use, killing apps such as Spotify when they are running in background as part of their “battery optimizations”. It is important when designing the app to consider its resource consumption while in background.

A “trick” to run a service in background and be less likely to be killed is to start the service in foreground (startForeground). Because of the notification required for it, the app is considered in foreground. The downside is that the notification needs to stay in the notification bar and can have a negative impact on the user experience.

Also related to background work are the broadcast receivers registered by the app. If they are declared in the manifest, since Oreo, they cannot register for most of the implicit intents (see their list). To receive implicit intents the receiver must be registered and unregistered at runtime, as discussed in Lab 2. We also recommend this way of using broadcast receivers, since it provides higher control of what can run in background.

Do not use services for CPU intensive or blocking operations because they run on the UI thread. In the next lab we will discuss more about components running in background and about threading choices.

Security concerns:

  • Having an app running a foreground service is something the user should consent for, and, since Android 9.0 (Pie, API level 28), this requirement is in place: the users needs to accept the FOREGROUND_SERVICE permission.
  • The Android components can have the exported argument declared in the manifest. If true, it allows other apps to access it. In the case of a service, make sure it is false (default value if no intent filters are declared) if you plan to use it only inside your app. See here more details about this and a complete list of service parameters.

Using services

The following code snippets are based on an example consisting of an activity that starts a service for acquiring periodic location updates.

Create a service

  • Extend the Service class
  • Declare it in the manifest file
  • Perform actions based on the Intent when started → override onStartCommand
    • return START_STICKY if you want it to be recreated after the system killed it, START_NOT_STICKY if not
    • this method will be called for each startService received from other components, so a service can be created once, and then react using the onStartCommand method to intents received from other components.
public class LocationService extends Service {
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Started service");
        // If the process is killed with no remaining start commands to deliver, then the
        // service will be stopped instead of restarted. It can be recreated later with an explicit
        // startService call
        return START_NOT_STICKY;
    }
}

Start a service

private void startLocationService() {
 
    Intent intent = LocationService.getIntent(this, "hello"); 
 
    if (isOreoOrHigher()) {
            startForegroundService(intent);
    } else {
        startService(intent);
    }
}
 
private Boolean isOreoOrHigher() {
    return Build.VERSION.SDK_INT >= O;
}

As design we put the logic for creating the intent in the service:

private static final String EXTRA_INFO = "locationservice.info";
 
public static Intent getIntent(Context context, String info) {
    Intent intent = new Intent(context, LocationService.class);
    intent.putExtra(EXTRA_INFO, info);
    return intent;
}

:!: If your project is configured for versions higher than Oreo, there's no need to perform the check for Oreo.

Start foreground service

  • Create a persistent notification like in the links provided above.
  • Call startForeground
// In LocationService
public void startLocationTracking(int interval) {
    Log.d(TAG, "Location tracking is active");
    Notification notification = buildNotification();
    setServiceForeground(true, notification);
}
 
private void setServiceForeground(Boolean serviceIsForeground, Notification notification) {
    if (this.serviceIsForeground != serviceIsForeground) {
        this.serviceIsForeground = serviceIsForeground;
        if (serviceIsForeground) {
            startForeground(LOCATION_NOTIFICATION_ID, notification);
        } else {
            stopForeground(true);
        }
    }
}

Tasks details

Create a new project with a basic MainActivity, supporting versions > Oreo. You will use this project for all the tasks in this lab.

For better code readability and to avoid errors caused by typos please define all the names of the intent actions and their parameters in constant fields. Place the constants in the classes they are related to (e.g. intent actions for a component should be declared in that class/file if in Kotlin). Provide static methods for obtaining a component's starting intent.

In the following tasks observe the lifecycle of the components involved. See what happens when the activity is paused, destroyed, when the app is destroyed.

Task 1 StartedService (2p)

In this task we will create a Foreground Started Service which prints a message using a Toast.

  • Add a new Service, and name it MyStartedService.
  • In MyStartedService override onCreate, onStartCommand, onBind and onDestroy and add a debug log message with the name of the method and the name of the current thread
  • Start the service as START_NOT_STICKY
  • In onStartCommand add call to a method which prints a Toast with a message of this format: Bits saved: 973. The number needs to be randomly generated.
  • In MainActivity add a button to start the service and one to stop the service

Bind to a service

  • Implement a connection callback
  • Call bindService
  • Override the onBind method in the service
  • Call unbindService when you no longer need to be bound to the service
// In MainActivity
 
private void bindToService() {
    if (locationService == null) {
        Intent intent = new Intent(this, LocationService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }
}
 
private ServiceConnection serviceConnection =  new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        locationService = ((LocationService.LocationServiceBinder) iBinder).get();
        locationService.startLocationTracking(INTERVAL_SECONDS);
    }
 
    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        locationService = null;
    }
};
// In LocationService
 
private LocationServiceBinder binder = new LocationServiceBinder();
 
class LocationServiceBinder extends Binder {
    private LocationService locationService = LocationService.this;
    public LocationService get() {
        return locationService;
    }
}
 
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return binder;
}

Task 2 BoundService (3p)

In this task we will create a BoundService that the activity interacts with to show the current date in a Toast message.

  • Add a new Service, and name it DateBoundService.
  • In the service override onCreate, onBind, onUnbind and onDestroy and add a debug log message with the name of the method and the name of the current thread in each of them.
  • In the service write a method getCurrentDate() that will return a string with the current date
  • In MainActivity bind the service in onCreate and unbind it in onDestroy
  • In MainActivity add a button that will call service.getCurrentDate()
  • Show the date using a Toast message

Using Intent Services

  • Extend the IntentService class
  • Implement the onHandleIntent method
  • in the component that wants to run the service:
    • create an intent: Intent intent = new Intent(context, MyIntentService.class);
    • start the service: startService(intent)
  • If we want to send a response from the IntentService to the activity, we can broadcast the result using intents and register a broadcast receiver for them.

Task 3 LuckyIntentService (2p)

  • From the MainActivity start an intent service that gives you money :)
  • The intent service generates a random number representing the money and logs it
    • you can use ThreadLocalRandom for generating a number within two limits (e.g. between 10 and 10000).
    • validate the intent's action (not null, the action is the one we expect)
  • Log the thread running the service

Task 4 Send a message from service (3p)

  • Show the user the result of the LuckyIntentService (the money they won)
  • Define a local BroadcastReceiver that shows a toast to the user “You won x euro”, where x is the number received in the intent.
  • The MainActivity registers and unregisters the receiver
  • The LuckyIntentService sends a broadcast with a given Intent
  • The broadcast receiver receives the intent and shows the toast
  • Use LocalBroadcastManager to register, unregister and send broadcasts, as presented in Lab 2
  • validate the intent's action (not null, the action is the one we expect)
smd/laboratoare/03.txt · Last modified: 2021/04/08 16:57 by adriana.draghici
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0