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:
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.
The following code snippets are based on an example consisting of an activity that starts a service for acquiring periodic location updates.
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; } }
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.
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); } } }
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 this task we will create a Foreground Started Service which prints a message using a Toast.
onCreate
, onStartCommand
, onBind
and onDestroy
and add a debug log message with the name of the method and the name of the current threadonStartCommand
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.bindService
onBind
method in the serviceunbindService
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; }
In this task we will create a BoundService that the activity interacts with to show the current date in a Toast message.
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.getCurrentDate()
that will return a string with the current dateonCreate
and unbind it in onDestroy
service.getCurrentDate()
IntentService
classIntent intent = new Intent(context, MyIntentService.class);
startService(intent)
LuckyIntentService
(the money they won)MainActivity
registers and unregisters the receiverLuckyIntentService
sends a broadcast with a given IntentLocalBroadcastManager
to register, unregister and send broadcasts, as presented in Lab 2