This shows you the differences between two versions of the page.
smd:laboratoare:03 [2020/01/14 20:53] adriana.draghici [Resources] |
smd:laboratoare:03 [2021/04/08 16:57] (current) adriana.draghici [Using Intent Services] |
||
---|---|---|---|
Line 2: | Line 2: | ||
===== Objectives ==== | ===== Objectives ==== | ||
- | * Learn the basics of how to perform background work in an Android app | + | * Learn the basics of how to //perform background work// in an Android app |
- | * Understand the concept of Service as an Android component | + | * Understand the concept of **Service** as an //Android component// |
- | * Use and differentiate between Started, Bound and Intent services | + | * Use and differentiate between **Started**, **Bound** and **Intent services** |
- | * Understand the services' lifecycle in relation with the app's and its activities' lifecycle | + | * Understand the **services' lifecycle** in relation //with the app's and its activities' lifecycle// |
===== Services ===== | ===== Services ===== | ||
- | [[https://developer.android.com/guide/components/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. | + | [[https://developer.android.com/guide/components/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 and foreground in terms of components' lifecycle not in terms of threading. | + | :!: Throughout this lab we refer to **background** (not visible) and **foreground** (visible) in terms of components' lifecycle not in terms of threading. |
**Types:** | **Types:** | ||
Line 18: | Line 18: | ||
* we just call [[https://developer.android.com/reference/android/content/Context.html#startService(android.content.Intent)|startService]] | * we just call [[https://developer.android.com/reference/android/content/Context.html#startService(android.content.Intent)|startService]] | ||
* [[https://developer.android.com/guide/components/bound-services|Bound services]] | * [[https://developer.android.com/guide/components/bound-services|Bound services]] | ||
- | * bounds a component to a service. Binding creates a connection between these components and they can use it for communication. | + | * //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 | * a bound service that is not started will "die" when no component is bound to it | ||
- | * calling [[https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)|bindService]] binds it but does not start it. If the service this call will also create it. | + | * calling [[https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)|bindService]] binds it but does not start it. If the service doesn't exist this call will also create it. |
* [[https://developer.android.com/reference/android/app/IntentService|Intent services]] | * [[https://developer.android.com/reference/android/app/IntentService|Intent services]] | ||
- | * used for executing a certain task on a worker thread, in background. | + | * used for executing a certain task **on a worker thread**, in background. |
* [[https://developer.android.com/guide/components/services.html#Foreground|Foreground services]] | * [[https://developer.android.com/guide/components/services.html#Foreground|Foreground services]] | ||
Line 29: | Line 29: | ||
* such a service is less likely to be killed by the system | * such a service is less likely to be killed by the system | ||
* it requires a [[https://developer.android.com/guide/topics/ui/notifiers/notifications#foreground-service|notification]] to be permanently shown to the user. | * it requires a [[https://developer.android.com/guide/topics/ui/notifiers/notifications#foreground-service|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 | + | * since API Level 26 services cannot be started in the background using the //startService// method, they need to be //started as foreground services// |
Line 36: | Line 36: | ||
Not only activities, but also services can bind to other services. | 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. | + | 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. |
Line 44: | Line 44: | ||
It is important when designing the app to consider its resource consumption while in background. | 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 ([[https://developer.android.com/reference/android/app/Service.html#startForeground(int,%20android.app.Notification)|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. | + | A "trick" to run a service in background and be less likely to be killed is to **start the service in foreground** ([[https://developer.android.com/reference/android/app/Service.html#startForeground(int,%20android.app.Notification)|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 [[https://developer.android.com/guide/components/broadcast-exceptions.html|their list]]). To receive implicit intents the receiver must be registered and unregistered at runtime, as discussed in [[smd:laboratoare:02#broadcast_receivers|Lab 2]]. We also recommend this way of using broadcast receivers, since it provides higher control of what can run in background. | 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 [[https://developer.android.com/guide/components/broadcast-exceptions.html|their list]]). To receive implicit intents the receiver must be registered and unregistered at runtime, as discussed in [[smd:laboratoare:02#broadcast_receivers|Lab 2]]. We also recommend this way of using broadcast receivers, since it provides higher control of what can run in background. | ||
Line 51: | Line 51: | ||
- | **Security concerns:** | + | <note warning>**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 need to accept the [[https://developer.android.com/reference/android/Manifest.permission.html#FOREGROUND_SERVICE|FOREGROUND_SERVICE permission]]. | + | * **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 [[https://developer.android.com/reference/android/Manifest.permission.html#FOREGROUND_SERVICE|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 [[https://developer.android.com/guide/topics/manifest/service-element.html|here]] more details about this and a complete list of service parameters. | + | * 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 [[https://developer.android.com/guide/topics/manifest/service-element.html|here]] more details about this and a complete list of service parameters. |
+ | </note> | ||
Line 64: | Line 64: | ||
* Extend the Service class | * Extend the Service class | ||
* Declare it in the manifest file | * Declare it in the manifest file | ||
- | * Perform actions based on the Intent when started -> override onStartCommand | + | * 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 | + | * 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. | + | * 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. |
<code Java> | <code Java> | ||
Line 83: | Line 83: | ||
====Start a service==== | ====Start a service==== | ||
+ | |||
<code java> | <code java> | ||
Line 113: | Line 114: | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | :!: If your project is configured for versions higher than Oreo, there's no need to perform the check for Oreo. | ||
+ | |||
+ | |||
+ | ====Start foreground service==== | ||
+ | * Examples: [[https://developer.android.com/guide/components/foreground-services#java|documentation]], [[https://androidwave.com/foreground-service-android-example/|tutorial]] | ||
+ | * Create a persistent notification like in the links provided above. | ||
+ | * Call ''startForeground'' | ||
+ | |||
+ | <code java> | ||
+ | // 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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <note tip>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.</note> | ||
+ | |||
+ | === 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==== | ====Bind to a service==== | ||
- | * implement a connection callback | + | * Implement a **connection callback** |
- | * call ''bindService'' | + | * Call ''bindService'' |
- | * override the ''onBind'' method in the service | + | * Override the ''onBind'' method in the service |
- | * call ''unbindService'' when you no longer need to be bound to the service | + | * Call ''unbindService'' when you no longer need to be bound to the service |
<code java> | <code java> | ||
Line 163: | Line 209: | ||
</code> | </code> | ||
- | ====Start foreground service==== | + | === Task 2 BoundService (3p) === |
- | * create a persistent notification (see [[https://proandroiddev.com/deep-dive-into-android-services-4830b8c9a09|example]]) | + | |
- | * call ''startForeground'' | + | |
- | <code java> | + | In this task we will create a BoundService that the activity interacts with to show the current date in a Toast message. |
- | // In LocationService | + | * Add a new //Service//, and name it //DateBoundService.// |
- | public void startLocationTracking(int interval) { | + | * 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. |
- | Log.d(TAG, "blabla"); | + | * In the service write a method ''getCurrentDate()'' that will return a string with the current date |
- | Notification notification = buildNotification(); | + | * In //MainActivity// bind the service in ''onCreate'' and unbind it in ''onDestroy'' |
- | setServiceForeground(true, notification); | + | * In //MainActivity// add a button that will call ''service.getCurrentDate()'' |
- | } | + | * Show the date using a Toast message |
- | private void setServiceForeground(Boolean serviceIsForeground, Notification notification) { | ||
- | if (this.serviceIsForeground != serviceIsForeground) { | ||
- | this.serviceIsForeground = serviceIsForeground; | ||
- | if (serviceIsForeground) { | ||
- | startForeground(LOCATION_NOTIFICATION_ID, notification); | ||
- | } else { | ||
- | stopForeground(true); | ||
- | } | ||
- | } | ||
- | } | ||
- | </code> | ||
==== Using Intent Services==== | ==== Using Intent Services==== | ||
- | * extend the ''IntentService'' class | + | * Extend the ''IntentService'' class |
- | * implement the [[https://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)|onHandleIntent]] method | + | * Implement the [[https://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)|onHandleIntent]] method |
* in the component that wants to run the service: | * in the component that wants to run the service: | ||
* create an intent: ''Intent intent = new Intent(context, MyIntentService.class);'' | * create an intent: ''Intent intent = new Intent(context, MyIntentService.class);'' | ||
* start the service: ''startService(intent)'' | * 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. | + | * 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. |
- | ===== Tasks ====== | ||
- | 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. Not doing this can lead to lower grades for those tasks. | + | /* |
+ | TODO INTENT SERVICE IS DEPRECATED, LEAVE THE TEXT< ADD A WARNING AND REMOVE THE EXERCISE | ||
+ | */ | ||
+ | === Task 3 LuckyIntentService (2p) === | ||
- | <note tip>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.</note> | + | * From the MainActivity start an intent service that gives you money :) |
- | + | ||
- | ==== Task 1 StartedService (2p) ==== | + | |
- | + | ||
- | In this task we will create a StartedService which prints a message using a Toast. | + | |
- | * Create a new project and 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 | + | |
- | + | ||
- | ==== Task 2 BoundService (3p) ==== | + | |
- | + | ||
- | In this task we will create a BoundService from which will take a value and print it with a Toast. | + | |
- | * Add a new Service, and name it //MyBoundService.// | + | |
- | * 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 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()'' | + | |
- | * Print the date using a Toast message | + | |
- | + | ||
- | ==== Task 3 LuckyIntentService (2p) ==== | + | |
- | + | ||
- | * From the main activity start an intent service that gives you money :) | + | |
* The intent service generates a random number representing the money and logs it | * The intent service generates a random number representing the money and logs it | ||
- | * you can use [[https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html|ThreadLocalRandom]] for generating a number within two limits (e.g. between 10 and 10000). | + | * you can use [[https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/concurrent/ThreadLocalRandom.html|ThreadLocalRandom]] for generating a number within two limits (e.g. between 10 and 10000). |
- | * perform the null and action checks for the intent received by the service | + | * validate the intent's action (not null, the action is the one we expect) |
* Log the thread running the service | * Log the thread running the service | ||
- | + | === Task 4 Send a message from service (3p) === | |
- | + | ||
- | + | ||
- | ==== Task 4 Send message from service (3p) ==== | + | |
* Show the user the result of the ''LuckyIntentService'' (the money they won) | * Show the user the result of the ''LuckyIntentService'' (the money they won) | ||
- | * Define a BroadcastReceiver that shows a toast to the user "You won x euro", where x is the number received in the intent. | + | * 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 main activity registers and unregisters the receiver | + | * The ''MainActivity'' registers and unregisters the receiver |
* The ''LuckyIntentService'' sends a broadcast with a given Intent | * The ''LuckyIntentService'' sends a broadcast with a given Intent | ||
* The broadcast receiver receives the intent and shows the toast | * The broadcast receiver receives the intent and shows the toast | ||
* Use ''LocalBroadcastManager'' to register, unregister and send broadcasts, as presented in [[smd:laboratoare:02#broadcast_receivers|Lab 2]] | * Use ''LocalBroadcastManager'' to register, unregister and send broadcasts, as presented in [[smd:laboratoare:02#broadcast_receivers|Lab 2]] | ||
- | * Perform the null and action checks for the intent received by the broadcast receiver | + | * validate the intent's action (not null, the action is the one we expect) |
- | ===== Resources ===== | ||