Bound Services and Handlers in Android

Time in the background
  1. Create boolean to save current mode
  2. Start a SpecialService. (Call stopSelf()in Service)
  3. Bind to it
  4. Call SpecialService.startSpecialMode()
  5. In ActivityonDestroy() unbind
  6. In onResume(), check if isSpecialMode. (If true, bind to service).
  7. Stop mode on button click. (call SpecialService.stopSpecialMode())
  8. Test

Enable Special mode

We need a boolean isSpecialMode that will remain also when device rotates. There are three options for this:

  1. Using onSaveInstanceState:
String  mode_key = "special_mode";@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState!=null) {
isSpecialMode = savedInstanceState
.getBoolean( mode_key, false);
}else {
isSpecialMode = false;
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
savedInstanceState.putBoolean(mode_key, isSpecialMode);
super.onSaveInstanceState(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
isSpecialMode = PreferenceManager
.getDefaultSharedPreferences(this)
.getBoolean( mode_key, false);
}

void startMode(){
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putBoolean(mode_key,true)
.apply();
}

void stopMode(){
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putBoolean(mode_key,false)
.apply();
}
public class MainViewModel extends AndroidViewModel {


boolean isSpecialMode;

public MainViewModel(Application application){
super(application);
isSpecialMode = false;
}


public boolean isSpecialMode() {
return isSpecialMode;
}

public void setSpecialMode(boolean isSpecialMode) {
this.isSpecialMode= isSpecialMode;
}
}
public class MainActivity extends AppCompatActivity {MainViewModel mainViewModel; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mainViewModel = new ViewModelProvider(this)
.get(MainViewModel.class);
}
void startMode(){
if (!mainViewModel.isSyncMode()){
mainViewModel.setSyncMode(true);
}
}

void stopMode(){
mainViewModel.setSyncMode(false);
}

2. Start a SpecialService

We need to create and start our SpecialService. Starting it is crucial if you want your service to persist when Activity is destroyed. Otherwise, a bound service is destroyed when no more clients are bound to it, i.e. every time your activity rotates.

public class SpecialService extends Service {

Handler handler = new Handler(Looper.getMainLooper());

private final IBinder mBinder = new LocalBinder();

public SpecialService() {
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

//simple binder
public class LocalBinder extends Binder {
public SpecialService getService() {
return SpecialService.this;
}
}

public void startSpecialMode(){
handler.post(task);
}

public void stopSpecialMode(){
//stop task on handler.
handler.removeCallbacksAndMessages(null);
stopSelf();
}

Runnable task = new Runnable() {
@Override
public void run() {
Log.d("Service","running task");
//run again in 10 seconds
handler.postDelayed(this,10000);
}
};

}
void startMode(){
...//here is your isSpecialMode boolean
Intent boundServiceIntent = new Intent(
getApplicationContext(),
SpecialService.class);
startService(boundServiceIntent);
}

Bind the service

In order to bind the service, we need a ServiceConnection. Let’s create a pretty basic one and add to it later:

private final ServiceConnection serviceConnection = 
new ServiceConnection() {

@Override
public void onServiceConnected(
ComponentName componentName,
IBinder service) {
boundService = ((SpecialService.LocalBinder) service)
.getService();

}

@Override
public void onServiceDisconnected(ComponentName componentName) {
boundService = null;
}
};
void startMode(){
...
Intent boundServiceIntent = new Intent(
getApplicationContext(),
SpecialService.class);
startService(boundServiceIntent);
getApplicationContext()
.bindService(boundServiceIntent,
serviceConnection, BIND_AUTO_CREATE);

}

Start Special Mode

The best place to start our mode is in the ServiceConnection:

SpecialService boundService;@Override
public void onServiceConnected(
ComponentName componentName,
IBinder service) {
boundService = ((SpecialService.LocalBinder) service)
.getService();
boundService.startSpecialMode();//here

}
boolean isSpecialMode = false;public void startSpecialMode(){
if (!isSpecialMode){
handler.post(task);
isSpecialMode = true;
}
}

public void stopSpecialMode(){
//stop task on handler.
isSpecialMode = false;
handler.removeCallbacksAndMessages(null);
stopSelf();
}

MainActivity: onDestroy()

If we rotate the app when we are bound to the service, we should unbind so we can rebind in onResume():

@Override
protected void onDestroy() {
super.onDestroy();
getApplicationContext().unbindService(serviceConnection);
}
Error: Android java.lang.IllegalArgumentException: Service not registered
boolean isBound = false;public void startMode(View view){
if (!isSpecialMode) {
isSpecialMode = true;
...
isBound = true;
mode.setText("Stop Mode");
}
...

}
void stopMode(){
isSpecialMode = false;
...
getApplicationContext().unbindService(serviceConnection);
isBound = false;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
getApplicationContext().unbindService(serviceConnection);
}
}

Rebind in onResume()

After a rotation, we want to bind to the same service. So we check our isSpecialMode boolean, and if it’s true we bind to the same service:

@Override
protected void onResume() {
super.onResume();
isSpecialMode =
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(mode_key,false);
if (isSpecialMode){
Intent boundServiceIntent = new Intent(
getApplicationContext(),
SpecialService.class);
getApplicationContext()
.bindService(boundServiceIntent,
serviceConnection, BIND_AUTO_CREATE);
isBound = true;
}
}

Activate and deactivate mode

We’ve got our startMode() and stopMode() functions, but nothing is calling them. Let’s call them on a button click. You can create two buttons, one to start and one to stop, but it’s cleaner and more user friendly to create one button whose text changes according to the current mode (which we keep in isSpecialMode). So let’s add the button in activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="48sp"
android:text="Start Mode"
android:id="@+id/mode_button"
android:onClick="startMode"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Button mode;@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mode = findViewById(R.id.mode_button);
}
@Override
protected void onResume() {
super.onResume();
isSpecialMode = PreferenceManager
.getDefaultSharedPreferences(this)
.getBoolean(mode_key,false);
if (isSpecialMode){
Intent boundServiceIntent = new Intent(
getApplicationContext(),
SpecialService.class);
getApplicationContext().bindService(boundServiceIntent, serviceConnection, BIND_AUTO_CREATE);
mode.setText("Stop Mode");
}
}
public void startMode(View view){ //first check which mode we're in
if (!isSpecialMode) {//start special mode
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putBoolean(mode_key, true)
.apply();
Intent boundServiceIntent = new Intent(
getApplicationContext(),
SpecialService.class);
startService(boundServiceIntent);
getApplicationContext()
.bindService(boundServiceIntent,
serviceConnection, BIND_AUTO_CREATE);
mode.setText("Stop Mode");
}
else { //stop special mode
mode.setText("Start Mode");
stopMode();
}


}

Test

Now we need to test it :) Look at the logcat to make sure that the action is called every 10 seconds regardless of rotation, and stops when user presses stop.

Logcat showing running task every 10 seconds regardless of rotate
Logcat of application
Application in landscape layout
Stop mode — Landscape

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Danielle H

Danielle H

I started programming in LabVIEW and Matlab, and quickly expanded to include Android, Swift, Flutter, Web(PHP, HTML, Javascript), Arduino and Processing.