Binding Services in Android
bound service is a server in a client-server interface. It allows components (such as activities) to bind to the service, send requests, receive responses, and perform interprocess communication (IPC).
When it comes to Services in Android, it basically comes down to two categories
- Started Services
- Bound Services
In this article, we will discuss Bound services and we will also build a demo application using MVVM architecture showing how we can implement a bound service.
For all the people who are unfamiliar with the services on Android here’s a quick overview of what they are and typically what they are used for.
A service is an application component that can perform long — running operations in the background with no user interface.
“no user interface” part is extremely important that means you can leverage services to execute long-running operations without displaying anything on the screen, so if the application is sent to the background, you can design a service to continue running even though the app is not visible to the user that brings me to the next property of services they have their own lifecycle independent of the activity or the fragment that they are created in. Essentially this is the property that gives the services the ability to continue running even if the application is not visible to the user. By default, services are unaffected by activity or fragment lifecycle, events such as onDestroy, onPause and so on.
Here are some of the things you need to watch for while using services in your projects.
First, by default services do not run on a background thread this is a very common misconception, even I was confused with this earlier. The android documentation describes services like this.
“A Service is an application component that can perform long-running operations in the background with no user interaction”
Second, how to determine which kind of service to use Bound service or Started service. Technically a bound service can also be a started service, but a started service cannot be a bound service. We will see this in detail when we will build our sample app.
Started service is tarted by calling “startService” or “startForgoundService”
Bound service can be started in two ways.
1- When some other component binds to it. The literal act of something binding to it.
2- using startService or startForgroundService and THEN bind to it.
So now when we have a short overview of the service, you must be wondering what exactly is a bound service and why should we care about it.
The best way to understand a bound service is to compare it with a Server when you start a server with the intention to send or receive data from a source called as a client. At its core, the bound service is also like a server-client relation. The service acts as a server and some other components (activity/fragment) or some other application acts as a client.
Q — So when would you should use a bound service in your project.?
A — Personally I like to use bound services when I know they're some kind of consistent or frequent communication between service and activity/fragment
for example, Service is streaming a piece of music, information like playback/progress needs to be constantly sent to an activity so that the users know what is going on.
The real advantage of the service lies when we need to communicate with it on consistent bases. By binding to it you can communicate with the service very easily.
Okay, enough of talk lets write some code.
So first we need to add lifecycle dependency in our build.gradle file.
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'Once the project is synced. Open the activity_main.xml file and create the Layout for the application
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/progress_bar"
android:layout_marginTop="50dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progress_bar"
android:layout_centerHorizontal="true"
android:id="@+id/text_view"
android:textSize="20sp"
android:layout_marginTop="10dp"
android:textColor="#000"
android:text="0%" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/toggle_updates"
android:layout_below="@+id/text_view"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="Start"/>
</RelativeLayout>And then let's open our MainActivity.java and initialize all the views from xml file and set an onClicklistener on the button.
private ProgressBar mProgressbar;
private TextView mTextView;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgressbar = findViewById(R.id.progress_bar);
mTextView = findViewById(R.id.text_view);
mButton = findViewById(R.id.toggle_updates);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { }
});
Next, let's build our service class. I will name it as MyService and extend this class with service. We also need to implement the onBind method. This is the only method which is required when implementing a service. And we need to add this service to our manifest file.
public class MyService extends Service {
private static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}Next thing we need to build is a custom class that extends binder class. And create a constructor which will return a myService status. Simply returning an instance of this service or some might call it singleton(a reference of this service)
So what is this Binder.? The purpose of this binder class is to provide an access point for retrieving a service instance. Basically all you want to know is It is used to get the instance of the service inside whatever you want to bind to it. In the case of this example, we are going to be binding to it using the main activity. So MainActivity will be a client and MyService will be a server.
The purpose of this service is to stimulate some kind of long-running operation. I will use a runnable and handler to increment an integer value which is mProgress. And once the value reaches the maxValue it will stop.
mBinder will help us communicate between mainActivity and the service. So what happens when we bind mainActivity to the service is it that it returns this mBinder object and then we can use that to get a reference to the service inside mainActivty
public class MyBinder extends Binder { private IBinder mBinder = new MyBinder();
private Handler mHandler;
private int mProgress, mMaxValue;
private Boolean mIsPaused; MyService getService() {
return MyService.this;
}
}
Next will insert the onCreate method, all services have an onCreate method. This is being called when the service is first started. Where we can initialise all our objects
@Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
mProgress = 0;
mIsPaused = false;
mMaxValue = 5000;
}Now we will build the method for pretending the background operation
public void startPretendLongRunningTask() {
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (mProgress >= mMaxValue || mIsPaused) {
Log.d(TAG,"run: removing callbacks.");
mHandler.removeCallbacks(this);
pausePretendLongRunningTask(); // method for pausing
} else {
Log.d(TAG,"run: progress: " + mProgress);
mProgress += 100;
mHandler.postDelayed(this,100);
}
}
};
mHandler.postDelayed(runnable,100);
}Now let's build for pausing and unpausing the method. Before we move to the next part we will need to add a couple of more methods that we will use to retrieve values from the service
public void pausePretendLongRunningTask() {
mIsPaused = true;
}
public void unPausePretendLongRunningTask() {
mIsPaused = false;
startPretendLongRunningTask();
}public Boolean getIsPaused() {
return mIsPaused;
}
public int getProgress() {
return mProgress;
}
public int getmMaxValue() {
return mMaxValue;
}
public void resetTask() {
mProgress = 0;
}
Next method that we add to our MyService.class and this is a very important method.
@Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
stopSelf();
}This method will be called when the application is removed from the recently used application list.
So Binding service will continue to run until the client has unbind from the service. In case the user has removed the application from the recently used application list the bounded service will still continue to run in the background. The stopself() method stops the bounded service when the onTaskRemoved() method is called.
Now let's Build our ViewModel Class, I will not go deep into the ViewModel basics, but will explain some of the service part implemented in ViewModel.
So one thing you might have noticed that I’m declaring the object as MutableLiveData “private MutableLiveData<Boolean> mIsProgressUpdating = new MutableLiveData<>();”.
But I’m returning the object as LiveData
“public LiveData<Boolean> getIsProgressUpdating() {
return mIsProgressUpdating;
}”
This is because LiveData method cannot be changed, they do not have a setter method
ServiceConnection object is important in services. As the name implements this is responsible to facilitate the connection to the client(Activity or Fragment or any other application).
onServiceConnected() method have two parameter componentName and Ibinder. ComponentName is the reference of the client which could be Activity or a fragment
In Ibinder we will pass the reference of mBinder instance that we have in our MyService.class. Both of these variables will be passed from activity that will be binding to this service. We will look this when we will implement our MainActivity class.
public class MainActivityViewModel extends ViewModel {
private static final String TAG = "MainActivityViewModel";
private MutableLiveData<Boolean> mIsProgressUpdating = new MutableLiveData<>();
private MutableLiveData<MyService.MyBinder> mBinder = new MutableLiveData<>();
private ServiceConnection serviceConnection = new ServiceConnection() { @Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected: conected to service");
MyService.MyBinder binder = (MyService.MyBinder) iBinder;
mBinder.postValue(binder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBinder.postValue(null);
}
};public LiveData<Boolean> getIsProgressUpdating() {
return mIsProgressUpdating;
}
public LiveData<MyService.MyBinder> getBinder() {
return mBinder;
} public ServiceConnection getServiceConnection() {
return serviceConnection;
}
public void setIsUpdating(Boolean isUpdating) {
mIsProgressUpdating.postValue(isUpdating);
}
}
Time to work on MainActivity. let's make two new objects MyService and MainActivityViewModel. finish initializing the ViewModel and other objects. Then let's write code to start the service and call that method in onResume() method of activity. And stop and unbind the service in onPause() method of the activity.
private void startService() {
Intent serviceIntent = new Intent(this, MyService.class);
startService(serviceIntent);
bindService();
}Next method that we will create is bind service. So the flag used here BIND_AUTO_CREATE, will create the service if it is not created when this activity or fragment binds to it.
private void bindService() {
Intent intentService = new Intent(this, MyService.class);
bindService(intentService, mViewModel.getServiceConnection(), Context.BIND_AUTO_CREATE);
}So we have the methods for starting a service and binding the service. But we do not have any reference to the service, we have bound to it, but there is no reference to the service in the main activity.
To implement this, we need an Observer.
mViewModel.getBinder().observe(this, new Observer<MyService.MyBinder>() {
@Override
public void onChanged(MyService.MyBinder myBinder) {
if (myBinder != null) {
Log.d(TAG, "onChanged: connected to service");
mService = myBinder.getService();
} else {
Log.d(TAG, "onChanged: unbound from service.");
mService = null;
}
}
});So in MainActivity, we need to call a new method called toggleUpdates()
private void toggleUpdates() {
if (mService != null) {
if (mService.getProgress() == mService.getmMaxValue()) {
mService.resetTask();
mButton.setText("Start");
} else {
if (mService.getIsPaused()) {
mService.unPausePretendLongRunningTask();
mViewModel.setIsUpdating(true);
} else {
mService.pausePretendLongRunningTask();
mViewModel.setIsUpdating(false);
}
}
}
}So to update the UI we need another Observer which we will use to update the Button text
mViewModel.getIsProgressUpdating().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(final Boolean aBoolean) {
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (aBoolean) {
if (mViewModel.getBinder().getValue() != null) {
if (mService.getProgress() == mService.getmMaxValue()) {
mViewModel.setIsUpdating(false);
}
mProgressbar.setProgress(mService.getProgress());
mProgressbar.setMax(mService.getmMaxValue());
String progress = String.valueOf(100 * mService.getProgress() / mService.getmMaxValue()) + "%";
mTextView.setText(progress);
handler.postDelayed(this, 100);
}
} else {
handler.removeCallbacks(this);
}
}
};
if (aBoolean) {
mButton.setText("Pause");
handler.postDelayed(runnable, 100);
} else {
if (mService.getProgress() == mService.getmMaxValue()) {
mButton.setText("Restart");
} else {
mButton.setText("Start");
}
}
}
});You find the complete code for the project at https://github.com/deepakgahlot98/boundserivce
Hope this article would have helped you in understanding the bound services. If any part of this article needs further explanation please leave your comment and I will update the same.
Thanks, Keep coding.
