Backgroud
A common problem with android applications I write is that there is no reliable way to close application level resources. The activity life cycle has onPause and onResume but this isn’t useful for resources that need to be maintained from one activity to another.
The best solution that I have found is to create a service that counts whenever an activity binds to it. When the count reaches 0 it waits a certain amount of time then times out running the close code. Then each activity can bind to this service.
I have written this as a super class which can be reused in many applications. This comes out of the question http://stackoverflow.com/questions/23499044/android-application-close-c… on stack exchange and I am posting this in the hope that more expert android developers can evaluate my solution and let me know if there is another more optimum way.
BoundSelfStoppingService class
This class extends Service and is meant to be a base class for services created using this pattern. It accepts an inactivity timeout parameter which specifies how long to wait after the last client disconnects to terminate. It also provides a processing loop (similar to a game loop) where regular processing can be preformed. This is uses the standard Handler and Runnable classes. The loop is run on the main service thread.
-
package metcarob.com.common.android.services;
-
-
import android.app.Service;
-
import android.content.Intent;
-
import android.os.Binder;
-
import android.os.Handler;
-
import android.os.IBinder;
-
-
/*
-
* This is a service class that runs a process on a periodic basis
-
* it counts bound connections
-
* when there are 0 bound methods and a set time has elapsed
-
* it will terminate itself
-
* Classes can be derived from it which will override
-
* - Constructor (so set interval and inactivityTimeout
-
* - startLoop
-
* - LoopIteration
-
* - endLoop
-
* to function
-
*
-
* Clients should bind to this
-
* and in onServiceConnected call clientConnected
-
* and in onServiceDisconnected call clientDisconnected
-
*/
-
public class BoundSelfStoppingService extends Service {
-
private final IBinder mbinder = new LocalBinder();
-
private boolean started = false;
-
private boolean finished = false; //set when service is finished running
-
private long lastSeenClient = -1;
-
private long inactivityTimeout = 10000; //Milliseconds
-
private long loopInterval;
-
-
public BoundSelfStoppingService(long p_inactivityTimeout, long p_loopInterval) {
-
inactivityTimeout = p_inactivityTimeout;
-
loopInterval = p_loopInterval;
-
}
-
-
private int numClientsConnected = 0;
-
public synchronized void clientConnect() {
-
numClientsConnected++;
-
if (!started) {
-
-
Intent i = new Intent(this,this.getClass());
-
this.startService(i);
-
started = true;
-
m_handler = new Handler();
-
m_handler.postDelayed(m_runnable, loopInterval);
-
try {
-
loopStart();
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
}
-
}
-
public synchronized void clientDisconnect() {
-
numClientsConnected--;
-
}
-
public synchronized int getConnectionCount() {
-
return numClientsConnected;
-
}
-
-
private Handler m_handler = null;
-
@Override
-
public void run() {
-
if (getConnectionCount()>0) {
-
} else {
-
//we have no clients
-
if ((thisLoop-lastSeenClient)>inactivityTimeout) {
-
try {
-
finished = true;
-
BoundSelfStoppingService.this.loopEnd();
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
BoundSelfStoppingService.this.stopSelf();
-
}
-
}
-
try {
-
if (finished) return;
-
BoundSelfStoppingService.this.loopIteration(thisLoop);
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
-
m_handler.postDelayed(this, loopInterval);
-
}
-
};
-
-
@Override
-
public IBinder onBind(Intent intent) {
-
return mbinder;
-
}
-
-
public class LocalBinder extends Binder {
-
public BoundSelfStoppingService getService() {
-
return BoundSelfStoppingService.this;
-
}
-
}
-
-
-
//To be overridden on termination
-
}
-
-
//To be overridden to run the processing
-
}
-
-
//To be overridden on termination
-
}
-
}
Example Derived Class
This is a simple example of a class that extends the BoundSelfStoppingService class.
-
package metcarob.com.dev.android.test.andtoidtest;
-
-
import android.widget.Toast;
-
import metcarob.com.common.android.services.BoundSelfStoppingService;
-
-
public class TestService extends BoundSelfStoppingService {
-
-
public TestService() {
-
super(1000, 500);
-
msg("BS CONSTRUCTOR");
-
}
-
-
//Toast.makeText(getApplicationContext(), (String)p_msg, Toast.LENGTH_LONG).show();
-
-
}
-
-
@Override
-
msg("BS LOOP START");
-
}
-
-
int m_loopCounter = 0;
-
-
@Override
-
msg("BS LOOP LOOP " + m_loopCounter + " at " + p_loopTime + " (" + getConnectionCount() + " conns)");
-
-
m_loopCounter++;
-
}
-
-
@Override
-
msg("BS LOOP END");
-
}
-
}
This simple test class demos the functionality and I used it in a test application. It overrider the loopStart/Iteration and End functions of BoundSelfStoppingService to simply provide messages. The log viewer can be user to determine that the class is performing as desired.
Example Activity Class
Finally this is an example of an activity class that uses the Service. It has a lot of standard code I will go through the specific code for using the service below.
-
package metcarob.com.dev.android.test.andtoidtest;
-
-
import metcarob.com.common.android.services.BoundSelfStoppingService.LocalBinder;
-
import android.support.v7.app.ActionBarActivity;
-
import android.support.v7.app.ActionBar;
-
import android.support.v4.app.Fragment;
-
import android.content.ComponentName;
-
import android.content.Context;
-
import android.content.Intent;
-
import android.content.ServiceConnection;
-
import android.os.Bundle;
-
import android.os.IBinder;
-
import android.view.LayoutInflater;
-
import android.view.Menu;
-
import android.view.MenuItem;
-
import android.view.View;
-
import android.view.ViewGroup;
-
import android.widget.Toast;
-
import android.os.Build;
-
-
public class SecondActivity extends ActionBarActivity {
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_second);
-
-
if (savedInstanceState == null) {
-
getSupportFragmentManager().beginTransaction()
-
.add(R.id.container, new PlaceholderFragment()).commit();
-
}
-
}
-
-
@Override
-
-
// Inflate the menu; this adds items to the action bar if it is present.
-
getMenuInflater().inflate(R.menu.second, menu);
-
return true;
-
}
-
-
@Override
-
// Handle action bar item clicks here. The action bar will
-
// automatically handle clicks on the Home/Up button, so long
-
// as you specify a parent activity in AndroidManifest.xml.
-
int id = item.getItemId();
-
if (id == R.id.action_settings) {
-
return true;
-
}
-
return super.onOptionsItemSelected(item);
-
}
-
-
/**
-
* A placeholder fragment containing a simple view.
-
*/
-
public static class PlaceholderFragment extends Fragment {
-
-
public PlaceholderFragment() {
-
}
-
-
@Override
-
Bundle savedInstanceState) {
-
container, false);
-
return rootView;
-
}
-
}
-
-
-
@Override
-
protected void onPause() {
-
super.onPause();
-
bs.clientDisconnect();
-
bs = null;
-
unbindService(sc);
-
msg("onPause");
-
}
-
-
@Override
-
protected void onResume() {
-
super.onResume();
-
Intent i = new Intent(this,metcarob.com.dev.android.test.andtoidtest.TestService.class);
-
msg("onResume");
-
}
-
-
}
-
TestService bs;
-
private ServiceConnection sc = new ServiceConnection() {
-
@Override
-
public void onServiceDisconnected(ComponentName name) {
-
if (bs!=null) {
-
bs.clientDisconnect();
-
bs = null;
-
};
-
msg("service disconnected");
-
}
-
@Override
-
public void onServiceConnected(ComponentName name, IBinder service) {
-
LocalBinder binder = (LocalBinder)service;
-
bs = (TestService) binder.getService();
-
bs.clientConnect();
-
msg("service connected");
-
}
-
};
-
}
onResume is used to create an initial binding to the TestService class defined earlier. If the service is not running it is started, otherwise the existing service is bound to.
The onPause method disconnects and unbinds the service.
TestService bs variable is created so we can interface with the service.
A ServiceConnection class is creates with onServiceDisconnected and onServiceConnected handlers implementd. These call the code to allow the service to count it’s connections.
Outstanding Issues
Although the code as is works in my test application. The service runs successfully between activities and loopEnd is only called once when the application has exited I have found that the onServiceDisconnected code is never run. This is why the onPause activity has to explicitly call clientDisconnect. There is a possible problem here since this if onServiceDisconnected is called the connection counter would be wrong. I can solve this by either working out why onServiceDisconnected is not called and fixing that or by implementing a robust service connection counter. (Give each client a random token and when it calls disconnect it can supply that token. If the service maintains a list of these tokens I can prevent double counting disconnects.)