How To Use A Service Android Studio Media Player And Create Only One Instance Of It
Many Android apps accept features that take a long fourth dimension to run, like downloading content or updating a remote database. If developers don't handle these features properly, they slowly clutter the main thread, creating retentiveness leaks and inconsistencies in the results. This leads to a bad user experience.
Fortunately, there'southward a solution for handling these actions: Android Services. This is a special type of application component that's designed to handle repetitive and long running jobs.
In this tutorial, you'll learn:
- What Android Services are and how to declare them in the Manifest file.
- How to use a foreground service while displaying a notification to the user.
- What is it with background processing for circuitous piece of work that isn't related to the UI.
- How to use a jump service.
Note: This tutorial assumes intermediate knowledge of Kotlin, Android and Android Studio. If you're new to Android development or oasis't installed Android Studio, refer to our Beginning Android Development tutorials.
Getting Started
Download the starter projection by using the Download Materials push at the tiptop or bottom of this tutorial.
For this tutorial, you'll use the Memo app. Memo is a game that challenges the user to match cards in the shortest possible time. Additionally, the user can listen to audio tracks while they play.
The game has four levels: Beginner, Intermediate, Advanced and Expert. The higher the level, the more than cards to match. After the user finishes the expert level, they consummate the game. Practice yous dare to try your luck? :]
Currently, the app merely lets the user beginning a game and manipulate the audio tracks. Once you start a game, you'll exist able to quit, preview game cards and runway the time elapsed since the game started.
Open the project in Android Studio and go familiar with the files. When you lot're done, build and run information technology on a device/emulator to come across how the app looks.
Play the game past pressing PLAY BEGINNER LEVEL. At this point, you lot tin friction match the cards simply there's no timer to show how long information technology took you to win. That's the first thing yous'll implement in this tutorial.
First, nevertheless, you lot'll take a deeper expect at Android Services.
Introducing Android Services
An Android Service is a component that helps execute long-running processes, like updating a database or server, running a countdown and playing sound. Past default, Android Services run in the aforementioned procedure as the main application thread does. This kind of service is besides known as a local service.
For resource-intensive tasks, the service executes the work on the background thread. This lets the user run actions without being disturbed. For example, if the user wants to make a telephone call or check letters while updating a server, they can do so without interrupting or aborting the update action.
To do this, the developer needs to create a background thread and manually specify which tasks should run on information technology considering the service doesn't directly support the thread implementation.
Since an Android Service doesn't take a user interface, it isn't spring to the activeness'south lifecycle. That means you can run information technology fifty-fifty when the user isn't interacting with the app.
Additionally, other components can demark to the service and interact with it, or fifty-fifty do InterProcess Communication (IPC). You'll acquire more than about IPC afterward in this tutorial.
In the following sections, y'all'll implement each of the below types of Android Services in the Memo app to achieve the post-obit features:
- Foreground service: Add together
TimerService
, which contains logic to track time. - Jump service: Implement
MusicService
, which will allow you play, intermission, stop and shuffle the sound tracks.
It's time to get started!
Declaring a Service in the Manifest
Before using a service, you lot demand to declare it in AndroidManifest.xml. Afterwards all, it'south an Android component, simply similar Activities are. For simplicity, the starter project already contains the Service classes, but you notwithstanding demand to configure them properly.
Navigate to AndroidManifest.xml and add this code within application
:
<service android:proper name=".services.TimerService" android:enabled="true" android:exported="false" /> <service android:name=".services.MusicService" android:enabled="truthful" android:exported="false" />
In this lawmaking, you see the following:
- android:name: The name of the Service class.
- android:enabled: When set to true allows the system to instantiate the services.
- android:exported: When fix to false tells the arrangement that components of another application can't showtime these services.
The value of android:proper noun
is in red, but only ignore that for now. You'll set up this shortly.
For now, yous'll kickoff with the implementation.
Implementing the Service Interface
Every custom service class must exist a subclass of Service
and must implement and override a few basic callback methods connected to the service lifecycle.
The almost important of these are:
- onStartCommand(): Chosen by the arrangement when another component wants to first a service using
startService()
. In one case the arrangement callsonStartCommand()
, the service can run in the background for every bit long as it takes to complete. You need to call back to stop the service manually when the work is over past callingstopSelf()
orstopService()
. You lot don't need this implementation if you only want to bind to the service without an choice to outset it. - onBind(): You always need to implement this callback method. The arrangement invokes it when
bindService()
is chosen. Apply it to bind some other component to the service. It opens a communication, then you'll provide an interface for the clients to communicate past returningIBinder
. If y'all don't desire to use binding, just rendernull
. - onCreate(): Use this method to gear up up a service before it starts. The system invokes it only if the service isn't running however.
- onDestroy(): The system invoke this to destroy the service when information technology is not needed anymore. It'south the last call the service receives and information technology cleans up the resources or threads.
Creating a Foreground Service
A foreground service executes tasks that the user needs to see. It shows a status bar notification to alert the user that an app is performing an operation and consuming resources. The notification must accept a priority of PRIORITY_LOW
or higher and must be visible fifty-fifty when the user isn't interacting with the app.
The user can't dismiss the notification; information technology disappears when the service stops or leaves the foreground.
To encounter this in play, y'all'll implement TimerService
. Open TimerService.kt and add the following to make it a subclass of Service
:
class TimerService : Service(), CoroutineScope {
With this lawmaking, you implement the Service
and CoroutineScope
interface.
Implementing the Service Methods
Await closely and notice that Android Studio is showing an error because onBind()
is missing.
To set up this, add together the following methods to TimerService
:
// 1 override fun onBind(intent: Intent): IBinder? = cypher override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) // 2 intent?.extras?.run { when (getSerializable(SERVICE_COMMAND) as TimerState) { TimerState.Offset -> startTimer() TimerState.Pause -> pauseTimerService() TimerState.STOP -> endTimerService() else -> return START_NOT_STICKY } } // three render START_NOT_STICKY } override fun onDestroy() { super.onDestroy() // 4 handler.removeCallbacks(runnable) task.cancel() }
Here is a breakdown of the lawmaking:
- Since this is a foreground service, you don't need binding so you return
null
instead ofIBinder
. Also notation that you lot won't implementonCreate()
now since y'all don't need whatever specific setup for the service. - Here, you get through the
Intent
extras to become the value that the fundamentalSERVICE_COMMAND
saves. This value indicates which action the service should execute. - If the system kills the service because the memory runs out,
START_NOT_STICKY
tells the arrangement not to recreate the service with an undefinedIntent
. Alternative constants areSTART_STICKY
andSTART_REDELIVER_INTENT
. If you're interested in these, check out the official service constants documentation. - Remove all
Handler
callbacks for the ticking fourth dimension to continue the memory clean. Also, clean upwards the resource by canceling the whole timerJob
since yous'll destroy the service in the adjacent pace.
Adding Permissions
All apps that target Android 9 (API level 28) or college must request permission to use a foreground service. The system automatically grants that request because it'southward a blazon of normal permission. Failure to do so leads the app to throw a SecurityException.
In Project view, navigate to app ▸ manifest ▸ AndroidManifest.xml and add together the following code above the application
tag:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
This lets you lot run the foreground service in Memo.
Adjacent, y'all'll create a notification for the service.
Creating a Notification Channel
Every foreground service must notify the user that it has started. Y'all also need to define a Notification Manager, which notifies the user of events that happen and holds data well-nigh 1 or more than notifications.
Open NotificationHelper.kt and add this code at the top of the class:
individual val notificationManager past lazy { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
Hither, you lot're lazy initializing notificationManager
by getting the NOTIFICATION_SERVICE
organization service and casting it every bit NotificationManager
.
In Android 8 and later, the notification must be role of a notification aqueduct. To create one, add this code to NotificationHelper.kt:
@RequiresApi(Build.VERSION_CODES.O) private fun createChannel() = // i NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT ).utilise { // ii description = CHANNEL_DESCRIPTION setSound(null, null) }
In this code, you:
- Create a notification aqueduct with
CHANNEL_ID
as its ID,CHANNEL_NAME
equally its name and the default importance. - Ready the notification aqueduct'southward description to
CHANNEL_DESCRIPTION
and sound tonada
which just ways that there is no sound played when a notification is triggered in the channel.
The next step is to define a notification view.
Creating a Notification Builder
For the user to be aware of the service running, create a view that the user can run across in the status bar. At the top of NotificationHelper.kt, add together the notification code:
// 1 private val notificationBuilder: NotificationCompat.Architect by lazy { NotificationCompat.Architect(context, CHANNEL_ID) // ii .setContentTitle(context.getString(R.string.app_name)) .setSound(cipher) .setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_foreground) .setPriority(NotificationCompat.PRIORITY_HIGH) // 3 .setAutoCancel(true) }
If you become an error saying contentIntent
isn't defined, don't worry. You'll take intendance of information technology in the next steps.
For now, in this code:
- You're using
NotificationCompat.Architect
to create a status bar notification builder. - The builder has to incorporate information about some of the notification's options. Notwithstanding, y'all don't have to ascertain the notification options when you declare a architect. If you're missing information about the title, audio and and then on, y'all can prepare that information later, in the notification itself.
- This kind of notification should be prepare equally car-cancelable, which means that when the user clicks it, it's automatically dismissed.
Note: Keep in heed that the mutual mistake developers make is forgetting to assign a small icon to the notification. This results in a subconscious notification.
Edifice a Notification
At present, information technology's time to build the notification itself. Add the following to NotificationHelper.kt:
fun getNotification(): Notification { // ane if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationManager.createNotificationChannel(createChannel()) } // 2 return notificationBuilder.build() }
Hither'south what's happening higher up:
- If the version of Android is equal to or greater than Android viii, the arrangement creates a notification channel and returns
Notification
. Unfortunately, the Support Library for versions before Android 8 doesn't provide notification aqueduct APIs. Read more about this issue in the official notification channel documentation. - Return a
Notification
subsequently invokingbuild()
on the builder.
Notification can be dynamically updated. In this case, you'll update the text if the user closes the app while the service is running.
Below getNotification()
, add the following:
fun updateNotification(notificationText: String? = zippo) { // 1 notificationText?.let { notificationBuilder.setContentText(it) } // ii notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) }
Here'due south what's happening:
- You update the text in the notification published in the status bar via the notificationBuilder
- Then notify the Notification Manager about which notification to update. To do that, you employ a unique
NOTIFICATION_ID
.
Finally, you'll define what happens when a user clicks on the notification. Add this to the meridian of NotificationHelper.kt:
private val contentIntent past lazy { PendingIntent.getActivity( context, 0, Intent(context, MainActivity::form.java), PendingIntent.FLAG_UPDATE_CURRENT ) }
Here, y'all're doing a lazy initialization of PendingIntent
, which launches MainActivity
when the user presses the notification.
Great task! You've done a lot of work already. Now, information technology's fourth dimension to really run the foreground service.
Starting and Stopping the Service
To kickoff the service in the foreground, use startForeground()
. This method needs two parameters: the unique positive integer ID of the notification and Notificaton
.
Open TimerService.kt and add the below to startTimer()
method:
startForeground(NotificationHelper.NOTIFICATION_ID, helper.getNotification())
This method makes the service run in the foreground and posts the notification with the ID of NOTIFICATION_ID
to the status bar. You'll notice that helper
is highlighted in red because you lot haven't divers that variable all the same.
To fix that, create an instance of NotificationHelper
class at the top of TimerService
:
private val helper by lazy { NotificationHelper(this) }
To try starting the service from another component, open MainActivity.kt, discover sendCommandToForegroundService()
and add:
ContextCompat.startForegroundService(this, getServiceIntent(timerState))
Here, yous first the service from Activity. Yous laissez passer its Context and the Intent to get-go the service.
Everything that starts must end, and that includes Service
. Open TimerService.kt and add the post-obit to stopService()
:
// one stopForeground(truthful) // ii stopSelf()
In this code block:
- This call tells the system that it should remove this service from foreground state. The passed boolean statement corresponds to removing the notification when ready to
true
- Since a service tin kickoff itself, it must handle stopping itself, as well.
stopSelf()
handles that use case.
Updating the Notification When the Timer Changes
TimerService
contains a coroutine that ticks every second while the level is running. You'll use the timer value to update the notification view.
Await for broadcastUpdate()
within the TimerService.kt, and add this lawmaking inside the if
:
// 1 sendBroadcast( Intent(TIMER_ACTION) .putExtra(NOTIFICATION_TEXT, elapsedTime) ) // 2 helper.updateNotification( getString(R.string.time_is_running, elapsedTime.secondsToTime()) )
Here is what this code cake does:
- Here, you transport a broadcast with the elapsed time to
MainActivity
. With it,MainActivity
tin update the fourth dimension in theTextView
below the card's view. - This helper method updates the status bar notification you posted to a higher place.
At this point, you've implemented everything the user sees while playing the game. Merely what happens if the user kills the app while the timer is running?
To handle that, add the following line to broadcastUpdate()
in the else if
:
helper.updateNotification(getString(R.string.get_back))
This line updates the notification text to call the user back to the game.
Awesome work! Build and run to test the timer. First a level and notice how the timer changes at the bottom. Pull downwardly the condition bar to see how the timer changes within the notification. Try exiting the application to test the bulletin text update in the notification.
Using Background Processing for Complex Piece of work
Sometimes, the user doesn't need to know most certain actions happening, e.g. if you store the user's action on the server for developing purposes, you don't accept to display a notification. Place this kind of continuous piece of work and similar in the background.
To reach that, yous tin can use:
- Custom Groundwork Service – past default, the service runs on the UI thread. To avoid blocking information technology, create a Service with a chore processing on the groundwork thread.
- IntentService – a subclass of Service that executes requests sequentially by using worker thread. Since Android 8, information technology'due south usage is not recommended. Also, IntentService is deprecated from Android 11.
- JobIntentService – a replacement for IntentService. Instead of service, it uses JobScheduler for executing jobs. In earlier versions than Android 8, it will human action but similar IntentService. For more info, read the official JobIntentService documentation.
- Background work with WorkManager – this is a general concept for doing groundwork piece of work in Android. Utilize it when you want to execute a periodical job in the future, or when you have some job constraints.
Explaining Background Execution Limits
Android viii and newer versions accept restrictions when using Android Services on the background thread:
- The organization will destroy the service afterwards some time if the app runs the service in the background. In one case it goes to the background, information technology has a window of a few minutes in which it can interact with services. After that, the system kills all running services.
- The system will throw an exception if the service was started when the app is in the background and outside of the time window.
Find more in Background execution limits documentation.
Now, information technology's time to larn near the last kind of service.
Creating a Bound Service
A spring service is an implementation of Service
. It'southward a component that other components demark to, interacting with it and performing interprocess communication (IPC). The bound component can be an Activity, some other service, a broadcast receiver or a content provider.
The lifecycle of the bound service depends on some other application component. When the component unbinds, the leap service destroys itself. Consequently, information technology tin can't run in the background forever.
However, it doesn't necessarily need a bound component — it can offset and cease itself as well. In that case, there's an selection to run indefinitely.
To try this out, yous'll give Memo the ability to play audio.
Converting MusicService into actual Service
To implement the audio playing characteristic, yous'll utilize MusicService
. Firstly, modify it to accept admission to basic service methods.
Open MusicService.kt and brand it extend Service
:
class MusicService : Service() {
Equally its type says, some other component will use this service to demark to it. You already learned almost the mandatory method you need for binding to service, so add it to MusicService
:
override fun onBind(intent: Intent?): IBinder = folder
The deviation between the last implementation of onBind()
and this 1 is, obviously, the return value. Since you're binding to service here, you need to provide the IBinder value.
IBinder is a programming interface of Folder that clients use to interact with the service. Its methods permit you lot to send a call to an IBinder object and receive a telephone call coming in to a Binder object. To learn more, bank check out IBinder'southward documentation.
Android Studio shows an error because y'all oasis't defined binder
variable. To set this, add the following to the top of the grade:
private val binder by lazy { MusicBinder() }
Here, you're doing a lazy initialization of binder
, which is a type of MusicBinder
. Implementation of MusicBinder
is your side by side step.
Defining a Binder
This service can't be accessed exterior of this application and doesn't accept its ain process. Therefore, you take to create an interface that will take access to the service'due south context. Do that by extending the Binder course. This way all components can use all public methods from Binder or the Service.
Now, to define the interface, add these lines at the bottom of the class:
inner form MusicBinder : Binder() { fun getService(): MusicService = this@MusicService }
MusicBinder
is nested inside another grade and it tin can access all methods from it. It tin can use all of Binder's methods as well. Within of it, you create a method for retrieving a service context.
Defining Service Methods for Managing Audio
This service allows the user to interact with the audio icons for playing, pausing, stopping and shuffling the music. For simplicity, the methods for managing audio are already divers. However, a music actor isn't.
To instantiate the music actor, utilize this lawmaking inside initializeMediaPlayer()
inside MusicService.kt:
musicMediaPlayer = MediaPlayer.create(this, randomSongs.first()).utilise { isLooping = truthful }
Here, you use MediaPlayer
to run a continuously repeating sound of the first song in randomSongs
.
Another fun characteristic is that this bound service tin provide the name of the track that's playing. Inside MusicService
, add:
fun getNameOfSong(): Cord = resource.getResourceEntryName(randomSongs.first()) .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.English) else it.toString() }.replace("_", " ")
In this method, you're reading a track name from the resources and changing the result String
to be more readable to the user.
All done! Now, you'll use these methods from another component.
Creating a Service Connection Callback
To use MusicService
inside other classes, you have to create its example. Open up MainActivity.kt and add the following at the peak of the class:
individual var musicService: MusicService? = nada
Hither, y'all're declaring a service variable that holds a service instance. For now, the service is nix. You lot'll assign a new value when the activity connects to the service past using the Binder interface. To catch the connection state changes, create a connection callback.
Add the lawmaking below the musicService
initialization:
// 1 individual val boundServiceConnection = object : ServiceConnection { // 2 override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder: MusicService.MusicBinder = service as MusicService.MusicBinder musicService = binder.getService() mainViewModel.isMusicServiceBound = truthful } // iii override fun onServiceDisconnected(arg0: ComponentName) { musicService?.runAction(MusicState.Finish) musicService = null mainViewModel.isMusicServiceBound = faux } }
That code is easy to assimilate:
- This is a callback for the service connection state.
- When activeness connects to service, the system uses
MusicBinder
example andgetService()
to give a reference tomusicService
. - When service disconnects, the audio will end if the service reference isn't already
null
, immigration the service reference.
This sets a flag, isMusicServiceBound
, that checks a service-spring land in both methods according to the parameter you provided. This is important to avoid DeadObjectException exceptions, which remote methods throw when the connection breaks.
Binding to the Service
Your next step is to bind the service when MainActivity
starts. Find onStart()
and add:
if (!mainViewModel.isMusicServiceBound) bindToMusicService()
Here, yous're checking whether the service has a bounden already. If non, you call a binding method.
The binding method doesn't exist yet, and so add its code beneath onDestroy()
:
private fun bindToMusicService() { // 1 Intent(this, MusicService::class.coffee).as well { // 2 bindService(it, boundServiceConnection, Context.BIND_AUTO_CREATE) } }
In this code, yous:
- Declare an intent to start
MusicService
. - Provide the
Intent
to the service along with the connection callback and a flag that automatically creates the service if the bounden exists.
To avert memory leaks or bugs, y'all need to add code to unbind the service. Add together the following to unbindMusicService()
:
unbindService(boundServiceConnection)
With this, you tell the service to execute onServiceDisconnected()
in the boundServiceConnection
callback.
Using Service Methods
Once the service unbindes, y'all need to finish the music. Add the following lawmaking above the unbindService()
line you just added:
musicService?.runAction(MusicState.STOP)
Here, y'all employ the service instance to terminate the audio. Remember that you weren't able to call methods from TimerService
. So, yous needed to provide flags through Intent
. However, here you have a connexion — a binding — to the service, and so you're able to call its methods and receive a response.
To trigger an audio action, use sendCommandToBoundService()
. Now, create a generic call for changing the action to reduce a number of code lines. Add this line to sendCommandToBoundService()
:
musicService?.runAction(state)
With this, y'all tell the service to execute an action that matches the sendCommandToBoundService()
parameter.
There is 1 more than thing to do! Once the song starts to play, the service can provide info about the song'south proper noun. Before you attempt that out, you demand to set the role with receiving a event.
Within getNameOfSong()
, supersede the line which returns "Unknown" text with:
musicService?.getNameOfSong() ?: getString(R.string.unknown)
Hither, you telephone call a method from the service that checks which audio track is currently playing and returns an optional Cord
result. If the issue is zero
, y'all use a resource text instead.
Build and run, so printing the Play icon to outset the audio. Press GET SONG Name to encounter the running song name in a Toast message. Finally, you lot can enjoy sound while playing your game!
Interprocess Communication
Every Android app runs in its own process with a unique procedure ID. When you start an app, the Android system generates a process and runs a main activity on the chief thread. This has the advantage that the app lives in an isolated surround and other apps can't interrupt information technology.
Based on this, Android allows you to run components in a dissimilar process that isn't used to start an app. To exercise this, you need to employ the process tag inside AndroidManifest.xml. The procedure tin take a random name, like myNewProcess.
Multiprocessing gives your app better security and memory direction. If y'all use it, nevertheless, you lot demand to detect a style to communicate between different processes.
You but need to include i programming interface — IBinder — but Android provides 3 ways of defining information technology:
- Extending Binder
- Using a Messenger
- Using AIDL
You lot already implemented the first approach in this tutorial, simply feel complimentary to investigate the other 2.
Where to Go From Here?
Download the terminal project using the Download Materials button at the peak or lesser of this tutorial.
You've learned a lot of new things about Android Services and how to implement them in different ways. Services are useful for app optimization and improving the user experience.
Here are some links yous can refer to:
- Android Background Processing video tutorial
- WorkManager Tutorial for Android
- Android Notifications
Android 12 will bring some changes to foreground notifications. Check them out in the official foreground services documentation.
If yous take any questions or comments, feel costless to bring together the forum word below.
How To Use A Service Android Studio Media Player And Create Only One Instance Of It,
Source: https://www.raywenderlich.com/20123726-android-services-getting-started
Posted by: fosterretion1985.blogspot.com
0 Response to "How To Use A Service Android Studio Media Player And Create Only One Instance Of It"
Post a Comment