com.j256.ormlite.android.apptools.OpenHelperManager Maven / Gradle / Ivy
Show all versions of ormlite-android Show documentation
package com.j256.ormlite.android.apptools;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import android.content.Context;
import android.content.res.Resources;
import android.database.sqlite.SQLiteOpenHelper;
import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
/**
* This helps organize and access database connections to optimize connection sharing. There are several schemes to
* manage the database connections in an Android app, but as an app gets more complicated, there are many potential
* places where database locks can occur. This class allows database connection sharing between multiple threads in a
* single app.
*
* This gets injected or called with the {@link OrmLiteSqliteOpenHelper} class that is used to manage the database
* connection. The helper instance will be kept in a static field and only released once its internal usage count goes
* to 0.
*
* The {@link SQLiteOpenHelper} and database classes maintain one connection under the hood, and prevent locks in the
* java code. Creating multiple connections can potentially be a source of trouble. This class shares the same
* connection instance between multiple clients, which will allow multiple activities and services to run at the same
* time.
*
* Every time you use the helper, you should call {@link #getHelper(Context)} or {@link #getHelper(Context, Class)}.
* When you are done with the helper you should call {@link #releaseHelper()}.
*
* @author graywatson, kevingalligan
*/
public class OpenHelperManager {
private static final String HELPER_CLASS_RESOURCE_NAME = "open_helper_classname";
private static Logger logger = LoggerFactory.getLogger(OpenHelperManager.class);
private static Class extends OrmLiteSqliteOpenHelper> helperClass = null;
private static volatile OrmLiteSqliteOpenHelper helper = null;
private static boolean wasClosed = false;
private static int instanceCount = 0;
/**
* If you are _not_ using the {@link OrmLiteBaseActivity} type classes then you will need to call this in a static
* method in your code.
*/
public static synchronized void setOpenHelperClass(Class extends OrmLiteSqliteOpenHelper> openHelperClass) {
if (openHelperClass == null) {
helperClass = null;
} else {
innerSetHelperClass(openHelperClass);
}
}
/**
* Set the helper for the manager. This is most likely used for testing purposes and should only be called if you
* _really_ know what you are doing. If you do use it then it should be in a static {} initializing block to make
* sure you have one helper instance for your application.
*/
public static synchronized void setHelper(OrmLiteSqliteOpenHelper helper) {
OpenHelperManager.helper = helper;
}
/**
* Create a static instance of our open helper from the helper class. This has a usage counter on it so make sure
* all calls to this method have an associated call to {@link #releaseHelper()}. This should be called during an
* onCreate() type of method when the application or service is starting. The caller should then keep the helper
* around until it is shutting down when {@link #releaseHelper()} should be called.
*/
public static synchronized T getHelper(Context context, Class openHelperClass) {
if (openHelperClass == null) {
throw new IllegalArgumentException("openHelperClass argument is null");
}
innerSetHelperClass(openHelperClass);
return loadHelper(context, openHelperClass);
}
/**
* Similar to {@link #getHelper(Context, Class)} (which is recommended) except we have to find the helper class
* through other means. This method requires that the Context be a class that extends one of ORMLite's Android base
* classes such as {@link OrmLiteBaseActivity}. Either that or the helper class needs to be set in the strings.xml.
*
*
* To find the helper class, this does the following:
* 1) If the class has been set with a call to {@link #setOpenHelperClass(Class)}, it will be used to construct a
* helper.
* 2) If the resource class name is configured in the strings.xml file it will be used.
* 3) The context class hierarchy is walked looking at the generic parameters for a class extending
* OrmLiteSqliteOpenHelper. This is used by the {@link OrmLiteBaseActivity} and other base classes.
* 4) An exception is thrown saying that it was not able to set the helper class.
*
*
* @deprecated Should use {@link #getHelper(Context, Class)}
*/
@Deprecated
public static synchronized OrmLiteSqliteOpenHelper getHelper(Context context) {
if (helperClass == null) {
if (context == null) {
throw new IllegalArgumentException("context argument is null");
}
Context appContext = context.getApplicationContext();
innerSetHelperClass(lookupHelperClass(appContext, context.getClass()));
}
return loadHelper(context, helperClass);
}
/**
* @deprecated This has been renamed to be {@link #releaseHelper()}.
*/
@Deprecated
public static void release() {
releaseHelper();
}
/**
* Release the helper that was previously returned by a call {@link #getHelper(Context)} or
* {@link #getHelper(Context, Class)}. This will decrement the usage counter and close the helper if the counter is
* 0.
*
*
* WARNING: This should be called in an onDestroy() type of method when your application or service is
* terminating or if your code is no longer going to use the helper or derived DAOs in any way. _Don't_ call this
* method if you expect to call {@link #getHelper(Context)} again before the application terminates.
*
*/
public static synchronized void releaseHelper() {
instanceCount--;
logger.trace("releasing helper {}, instance count = {}", helper, instanceCount);
if (instanceCount <= 0) {
if (helper != null) {
logger.trace("zero instances, closing helper {}", helper);
helper.close();
helper = null;
wasClosed = true;
}
if (instanceCount < 0) {
logger.error("too many calls to release helper, instance count = {}", instanceCount);
}
}
}
/**
* Set the helper class and make sure we aren't changing it to another class.
*/
private static void innerSetHelperClass(Class extends OrmLiteSqliteOpenHelper> openHelperClass) {
// make sure if that there are not 2 helper classes in an application
if (openHelperClass == null) {
throw new IllegalStateException("Helper class was trying to be reset to null");
} else if (helperClass == null) {
helperClass = openHelperClass;
} else if (helperClass != openHelperClass) {
throw new IllegalStateException("Helper class was " + helperClass + " but is trying to be reset to "
+ openHelperClass);
}
}
private static T loadHelper(Context context, Class openHelperClass) {
if (helper == null) {
if (wasClosed) {
// this can happen if you are calling get/release and then get again
logger.info("helper was already closed and is being re-opened");
}
if (context == null) {
throw new IllegalArgumentException("context argument is null");
}
Context appContext = context.getApplicationContext();
helper = constructHelper(appContext, openHelperClass);
logger.trace("zero instances, created helper {}", helper);
/*
* Filipe Leandro and I worked on this bug for like 10 hours straight. It's a doosey.
*
* Each ForeignCollection has internal DAO objects that are holding a ConnectionSource. Each Android
* ConnectionSource is tied to a particular database connection. What Filipe was seeing was that when all of
* his views we closed (onDestroy), but his application WAS NOT FULLY KILLED, the first View.onCreate()
* method would open a new connection to the database. Fine. But because he application was still in memory,
* the static BaseDaoImpl default cache had not been cleared and was containing cached objects with
* ForeignCollections. The ForeignCollections still had references to the DAOs that had been opened with old
* ConnectionSource objects and therefore the old database connection. Using those cached collections would
* cause exceptions saying that you were trying to work with a database that had already been close.
*
* Now, whenever we create a new helper object, we must make sure that the internal object caches have been
* fully cleared. This is a good lesson for anyone that is holding objects around after they have closed
* connections to the database or re-created the DAOs on a different connection somehow.
*/
BaseDaoImpl.clearAllInternalObjectCaches();
/*
* Might as well do this also since if the helper changes then the ConnectionSource will change so no one is
* going to have a cache hit on the old DAOs anyway. All they are doing is holding memory.
*
* NOTE: we don't want to clear the config map.
*/
DaoManager.clearDaoCache();
instanceCount = 0;
}
instanceCount++;
logger.trace("returning helper {}, instance count = {} ", helper, instanceCount);
@SuppressWarnings("unchecked")
T castHelper = (T) helper;
return castHelper;
}
/**
* Call the constructor on our helper class.
*/
private static OrmLiteSqliteOpenHelper constructHelper(Context context,
Class extends OrmLiteSqliteOpenHelper> openHelperClass) {
Constructor> constructor;
try {
constructor = openHelperClass.getConstructor(Context.class);
} catch (Exception e) {
throw new IllegalStateException(
"Could not find public constructor that has a single (Context) argument for helper class "
+ openHelperClass, e);
}
try {
return (OrmLiteSqliteOpenHelper) constructor.newInstance(context);
} catch (Exception e) {
throw new IllegalStateException("Could not construct instance of helper class " + openHelperClass, e);
}
}
/**
* Lookup the helper class either from the resource string or by looking for a generic parameter.
*/
private static Class extends OrmLiteSqliteOpenHelper> lookupHelperClass(Context context, Class> componentClass) {
// see if we have the magic resource class name set
Resources resources = context.getResources();
int resourceId = resources.getIdentifier(HELPER_CLASS_RESOURCE_NAME, "string", context.getPackageName());
if (resourceId != 0) {
String className = resources.getString(resourceId);
try {
@SuppressWarnings("unchecked")
Class extends OrmLiteSqliteOpenHelper> castClass =
(Class extends OrmLiteSqliteOpenHelper>) Class.forName(className);
return castClass;
} catch (Exception e) {
throw new IllegalStateException("Could not create helper instance for class " + className, e);
}
}
// try walking the context class to see if we can get the OrmLiteSqliteOpenHelper from a generic parameter
for (Class> componentClassWalk = componentClass; componentClassWalk != null; componentClassWalk =
componentClassWalk.getSuperclass()) {
Type superType = componentClassWalk.getGenericSuperclass();
if (superType == null || !(superType instanceof ParameterizedType)) {
continue;
}
// get the generic type arguments
Type[] types = ((ParameterizedType) superType).getActualTypeArguments();
// defense
if (types == null || types.length == 0) {
continue;
}
for (Type type : types) {
// defense
if (!(type instanceof Class)) {
continue;
}
Class> clazz = (Class>) type;
if (OrmLiteSqliteOpenHelper.class.isAssignableFrom(clazz)) {
@SuppressWarnings("unchecked")
Class extends OrmLiteSqliteOpenHelper> castOpenHelperClass =
(Class extends OrmLiteSqliteOpenHelper>) clazz;
return castOpenHelperClass;
}
}
}
throw new IllegalStateException(
"Could not find OpenHelperClass because none of the generic parameters of class " + componentClass
+ " extends OrmLiteSqliteOpenHelper. You should use getHelper(Context, Class) instead.");
}
}