Hosemeyer
Hosemeyer

Reputation: 1274

db4o on Android 3.0+ Issue

I'm having an issue with db4o on Android 3.0+ because it turns out that on the creation of the db4o database, it uses some of the network apis by default. (I stumbled upon this post: http://mavistechchannel.wordpress.com/2011/11/18/db4o-at-honeycomb-and-ice-cream-sandwich/ about it)

However, I've attempted to make the db creation requests async, but I think I'm running into an issue of calling the db before it's fully created as it locks the DB. (And I now get a locking error) Is there any way I can do this synchronous? or, at a minimum wait until it's been finished? Here's my db4o helper:

public class Db4oHelperAsync implements Constants{

private static final String USE_INTERNAL_MEMORY_FOR_DATABASE = "USE_INTERNAL_MEMORY_FOR_DATABASE";

private static ObjectContainer oc = null;
private Context context; 

/**       
 * @param ctx
 */

public Db4oHelperAsync(Context ctx) {
    context = ctx;
}

/**
 * Create, open and close the database
 */
public ObjectContainer db() {


    if (oc == null || oc.ext().isClosed()) {

        if (Utilities.getPreferences(context).getBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true)) {

            new GetDbFromInternalMemory().execute();

        } else {
            new GetDbFromSDCard().execute();
        }
        return oc;


    } else {

        return oc;

    }

}
/**
 * Configure the behavior of the database
 */

private EmbeddedConfiguration dbConfig() throws IOException {
    EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();

    configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).objectField("name").indexed(true);
    configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnUpdate(true);
    configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnActivate(true);
    configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnDelete(true);

    configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).objectField("name").indexed(true);
    configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnUpdate(true);
    configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnActivate(true);

    return configuration;
}

/**
 * Returns the path for the database location
 */

private String db4oDBFullPathInternal(Context ctx) {
    return ctx.getDir("data", 0) + "/" + "testapp.db4o";
}

private String db4oDBFullPathSdCard(Context ctx) {
    File path = new File(Environment.getExternalStorageDirectory(), ".testapp");
    if (!path.exists()) {
        path.mkdir();
    }
    return path + "/" + "testapp.db4o";
}

/**
 * Closes the database
 */

public void close() {
    if (oc != null)
        oc.close();
}

private class GetDbFromInternalMemory extends AsyncTask<Void, Void, ObjectContainer>{

    @Override
    protected ObjectContainer doInBackground(Void... params) {
        try {
            ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathInternal(context));
            CLog.v("USING INTERNAL MEMORY FOR DATABASE");
            return obj;

        } catch (Exception ie) {
            ie.printStackTrace();
            CLog.e(Db4oHelper.class.getName(), ie.toString());
            return null;
        }
    }

    @Override
    protected void onPostExecute(ObjectContainer result)
    {
        oc = result;
    }
}

private class GetDbFromSDCard extends AsyncTask<Void, Void, ObjectContainer>{

    @Override
    protected ObjectContainer doInBackground(Void... params) {
        try {

            ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathSdCard(context)); 
            CLog.v("USING SDCARD FOR DATABASE");
            SharedPreferences.Editor edit = Utilities.getPreferencesEditor(context);
            edit.putBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true);
            edit.commit();

            return obj;

        } catch (Exception ie) {
            ie.printStackTrace();
            CLog.e(Db4oHelper.class.getName(), ie.toString());
            return null;
        }
    }

    @Override
    protected void onPostExecute(ObjectContainer result)
    {
        oc = result;
    }
}
}

Upvotes: 3

Views: 812

Answers (2)

Gamlor
Gamlor

Reputation: 13258

Update: This db4o bug has been fixed. If you get the newest 8.1 bits the error should not occur and the workaround is obsolute:

You get a file-locked exception when trying to get the database? Right.

Well the issue is that you are not waiting for the async task to finish and just start a new one in case the Db4oHelperAsync.oc is null. You basically have to wait until the initialization has finished and only then use the Db4oHelperAsync.oc variable. So your in Java synchronization land.

For example you can do this: Synchronize the Db4oHelperAsync.oc access. When requesting the database wait until the variable is set. Now unfortunately I don't know the exact behavior of the async task. My guess is that it will run the .onPostExecute() method back on the main activity. That also means that you cannot just wait for it, because it would mean that you block the Activity-Thread and .onPostExecute() will never be executed.

Here's my draft of what I would try to do. I never executed nor compiled it. And it probably has synchronization issues. For example when the initialization fail it will just hang your applicaition on the .db() call, because it waits forever. So be very careful and try to improve it:

public class Db4oHelperAsync implements Constants{

    private static final String USE_INTERNAL_MEMORY_FOR_DATABASE = "USE_INTERNAL_MEMORY_FOR_DATABASE";

    private static ObjectContainer oc = null;
    private static final Object lock = new Object();
    private Context context; 

    /**       
     * @param ctx
     */

    public Db4oHelperAsync(Context ctx) {
        context = ctx;
    }

    /**
     * Create, open and close the database
     */
    public ObjectContainer db() {       
        synchronized(lock){
            if (oc == null || oc.ext().isClosed()) {
                if (Utilities.getPreferences(context).getBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true)) {
                    new GetDbFromInternalMemory().start();
                } else {
                    new GetDbFromSDCard().start();
                }
                while(oc==null){
                    this.wait()
                }
                return oc;
            } else {
                return oc;
            }
        }
    }
    /**
     * Configure the behavior of the database
     */

    private EmbeddedConfiguration dbConfig() throws IOException {
        EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();

        configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).objectField("name").indexed(true);
        configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnUpdate(true);
        configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnActivate(true);
        configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnDelete(true);

        configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).objectField("name").indexed(true);
        configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnUpdate(true);
        configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnActivate(true);

        return configuration;
    }

    /**
     * Returns the path for the database location
     */

    private String db4oDBFullPathInternal(Context ctx) {
        return ctx.getDir("data", 0) + "/" + "testapp.db4o";
    }

    private String db4oDBFullPathSdCard(Context ctx) {
        File path = new File(Environment.getExternalStorageDirectory(), ".testapp");
        if (!path.exists()) {
            path.mkdir();
        }
        return path + "/" + "testapp.db4o";
    }

    /**
     * Closes the database
     */

    public void close() {

        synchronized(lock){
            if (oc != null)
                oc.close();
        }
    }

    private class GetDbFromInternalMemory extends Thread{

        @Override
        protected void run() {
            try {
                ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathInternal(context));
                CLog.v("USING INTERNAL MEMORY FOR DATABASE");

                synchronized(Db4oHelperAsync.lock){
                    Db4oHelperAsync.oc = obj;
                    Db4oHelperAsync.lock.notifyAll()
                }
            } catch (Exception ie) {
                ie.printStackTrace();
                CLog.e(Db4oHelper.class.getName(), ie.toString());
            }
        }
    }

    private class GetDbFromSDCard extends Thread{

        @Override
        protected void run() {
            try {
                ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathSdCard(context)); 
                CLog.v("USING SDCARD FOR DATABASE");
                SharedPreferences.Editor edit = Utilities.getPreferencesEditor(context);
                edit.putBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true);
                edit.commit();

                synchronized(Db4oHelperAsync.lock){
                    Db4oHelperAsync.oc = obj;
                    Db4oHelperAsync.lock.notifyAll()
                }
            } catch (Exception ie) {
                ie.printStackTrace();
                CLog.e(Db4oHelper.class.getName(), ie.toString());
            }
        }
    }
}

P.S. Added this problem as a bug to db4o: http://tracker.db4o.com/browse/COR-2269

Upvotes: 2

Carl Rosenberger
Carl Rosenberger

Reputation: 910

Thanks for posting this issue, this is a serious fun-spoiler on Android. When a new db4o database file is created, db4o generates it's unique internal signature by calling java.net.InetAddress.getLocalHost().getHostName(). Exceptions are not caught in this call. We will find a workaround for Android and post back here and to our forums when this is fixed.

Update Feb 9 2012: The issue has been fixed and new builds are online. http://community.versant.com/Blogs/db4o/tabid/197/entryid/1057/Default.aspx

Upvotes: 1

Related Questions