SingletonService.java
package net.zer0bandwidth.android.lib.services;
import android.app.Service ;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import net.zer0bandwidth.android.lib.content.IntentUtils;
import java.util.HashMap;
/**
* This service provides "singleton" instances of any specified class. This is
* done as an Android {@link Service}, instead of the normal Java singleton
* model, because Android tends to assign multiple class loaders during the life
* cycle of a single app, and thus static values (like singletons) set in one
* {@link android.app.Activity Activity} might not be visible in another. By
* registering singletons with a {@code Service}, multiple {@code Activity}s
* can be more confident about getting their singletons from the same source.
* @since zer0bandwidth-net/android 0.0.1 (#1)
*/
public class SingletonService
extends Service
{
/** A logging tag. */
protected static final String LOG_TAG =
SingletonService.class.getSimpleName() ;
protected static final String ACTION_KICKOFF =
"net.zer0bandwidth.android.actions.services.SingletonService.KICKOFF" ;
public static void kickoff( Context ctx )
{
ctx.startService(
IntentUtils.getBoundIntent( ctx, SingletonService.class )
.setAction( ACTION_KICKOFF ) ) ;
}
/**
* An implementation of the {@link android.os.Binder} class which simply
* returns this instance of the service.
* @since zer0bandwidth-net/android 0.0.1 (#1)
*/
public class Binder extends android.os.Binder
implements SimpleServiceConnection.InstanceBinder<SingletonService>
{
@Override
public SingletonService getServiceInstance()
{ return SingletonService.this ; }
}
/** The mapping of classes to singleton instances of those classes. */
protected HashMap<Class<?>,Object> m_mapSingletons ;
/** A constant binder instance. */
protected final SingletonService.Binder m_bind =
new SingletonService.Binder() ;
@Override
public void onCreate()
{
super.onCreate() ;
m_mapSingletons = new HashMap<>() ;
}
@Override
public SingletonService.Binder onBind( Intent sig )
{ return m_bind ; }
@Override
public int onStartCommand( Intent sig, int zFlags, int nStartID )
{
super.onStartCommand( sig, zFlags, nStartID ) ;
Log.i( LOG_TAG, "Started service." ) ;
return Service.START_STICKY ;
}
/**
* Returns the singleton instance of the specified class. If no singleton
* has been created yet, then {@code null} is returned. If a casting error
* occurs internally, then the method returns {@code null} but also logs a
* warning.
* @param cls the class for which a singleton should be returned
* @param <T> the class for which a singleton should be returned
* @return the singleton instance of the class, or {@code null} if none
* can be obtained
*/
@SuppressWarnings("unchecked") // We explicitly catch ClassCastException.
public synchronized <T> T get( Class<T> cls )
{
T oInstance = null ;
try { oInstance = (T)(this.m_mapSingletons.get(cls)) ; }
catch( ClassCastException xCast )
{ Log.w( LOG_TAG, "Cannot cast singleton fetched from map." ) ; }
return oInstance ;
}
/**
* Specifies the singleton instance of a given class. If no previous
* singleton has been set, then {@code null} is returned. If a casting
* error occurs internally while trying to pass back the previous instance,
* then {@code null} is still returned, but an error is also written to the
* logs.
* @param cls the class for which a singleton should be set
* @param oInstance the instance to be proclaimed as the singleton instance
* @param <T> the class for which a singleton should be set
* @return any previously-set singleton instance for that class, or
* {@code null} if none can be obtained
*/
@SuppressWarnings("unchecked") // We explicitly catch ClassCastException.
public synchronized <T> T put( Class<T> cls, T oInstance )
{
T oPrevious = null ;
try { oPrevious = (T)(this.m_mapSingletons.put(cls, oInstance)) ; }
catch( ClassCastException xCast )
{ Log.w( LOG_TAG, "Caught exception when casting previous instance." ) ; }
return oPrevious ;
}
/**
* Gets the current singleton instance for the specified class, or stores
* the supplied alternative as the singleton if none was previously set.
* @param cls the class for which a singleton should be returned
* @param oAlternative an instance to set if no previous instance is found
* @param <T> the class for which a singleton should be returned
* @return the singleton instance of the class, or the supplied alternative
* if none can be obtained
*/
public synchronized <T> T getOrPut( Class<T> cls, T oAlternative )
{
if( m_mapSingletons.containsKey(cls) )
return this.get(cls) ;
else
{
this.put( cls, oAlternative ) ;
return oAlternative ;
}
}
/**
* Indicates whether a singleton instance has been set for the specified
* class.
* @param cls the class for which a singleton might be set
* @param <T> the class for which a singleton might be set
* @return {@code true} if an instance has been set for the class
*/
public synchronized <T> boolean hasInstanceOf( Class<T> cls )
{ return m_mapSingletons.containsKey(cls) ; }
}