Android: MonoDroid and Sybase UltraliteJ

May 3rd, 2011

Yes, it would be easier to use Java.  This is research, which means I’m not getting paid for it, so I can do what I like.

The main reason for using MonoDroid is that several of my OEMs know C# and have large code bases.  Providing a path to reuse the existing code is a big win.

My goal is to use the Sybase UltraliteJ (SQL Anywhere v12) with a MonoDroid application.  This will include Connecting, Querying, Adding records and syncing with a PC Database server.  This will show an ability to load data to the device from a Database Server, go offline, edit the data, reconnect and sync the data back to the Server.

This is useful for “airplane mode”, going into the field where there is limited to no connectivity, and data security.

Getting started

I have the Java, Android SDK, Visual Studio 2010, MonoDroid and Sybase 12.01 installed.

Create a MonoDroid Project, build it and make sure that it runs!  I’m using an Android Tablet for my testing, though an emulator would work fine.

Setup Sybase UltraliteJ

Copy the files from “C:\Program Files\SQL Anywhere 12\UltraLite\UltraLiteJ\Android” to my project folder.  Yes, I didn’t have to do this.  I want to have everything in the same place so I can stuff it into subversion.

Rename the ‘ARM’ folder to ‘armeabi’.  This is important!  By doing this, we inform the Mono compiler that the ABI for the shared libraries is armeabi.

Add a folder to the Visual Studio project named armabi, and add the shared object to it.

For each shared library, set the properties: ‘Build Action’ to AndroidNativeLibrary.

Add the UltraLiteJNI12.jar file to the project.  Set the properties: ‘Build Action’ to AndroidJavaSource.

Create a new Java file.  I named mine jni_helper.java.  Add this to the project and Set the properties: ‘Build Action’ to AndroidJavaSource.

Now we’re ready to start adding code.

Build the JNI Layer

At some point in time MonoDroid is going to have a tool for using arbitrary Jar files.  There isn’t one now, so we’ll have to use JNI to talk to UltraliteJ.  Greg Shackles has a good blog entry about this.  I used the same style (and shameless copied his code!), which is using class static methods.  My test application is database driven, so this is a good model.

To start, I created a Java class with connect, disconnect and isConnected static methods.

package monoultralite.helper;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.util.Log;
import com.ianywhere.ultralitejni12.*;

public class jni_helper {
    private static final String TAG = "jni_helper";
    private static Connection conn_ = null;

    private jni_helper()
    {}

    public static void connectDB(android.app.Activity act)
    {
        Log.v(TAG, "connectDB: E");     

        try {
            Log.v(TAG, "connectDB: DatabaseManager.createConfigurationFileAndroid");
            ConfigFileAndroid config = DatabaseManager.createConfigurationFileAndroid("MonoUltralite.udb", act);

            // Connect, or if the database does not exist, create it and connect
            try {
                Log.v(TAG, "connectDB: DatabaseManager.connect");
                conn_ = DatabaseManager.connect(config);
            } catch (ULjException e) {
                Log.v(TAG, "connectDB: DatabaseManager.createDatabase(config)");
                conn_ = DatabaseManager.createDatabase(config);
            }
        } catch (ULjException e) {
            conn_ = null;
            Log.v(TAG, "connectDB: **catch** cannot create DB");
        }

        Log.v(TAG, "connectDB: X");
    }

    public static void disconnectDB()
    {
        Log.v(TAG, "disconnectDB: E");
        if (null != conn_) {
            Log.v(TAG, "disconnectDB: DatabaseManager.release");
            try {
                DatabaseManager.release();
            } catch (ULjException e) {
                Log.v(TAG, "disconnectDB: **catch** DatabaseManager.release");
            }
            conn_ = null;
        }
        Log.v(TAG, "disconnectDB: X");
    }

    public static boolean isConnected()
    {
        Log.v(TAG, "isConnected: E");
        boolean fConnected = false;
        if (null != conn_) {
            fConnected = true;
            Log.v(TAG, "isConnected: IsConnected");
        }

        if(fConnected)
            Log.v(TAG, "isConnected: Return true");
        else
            Log.v(TAG, "isConnected: Return false");
        return fConnected;
    }
}

This will be used by this C# class:

public static class MonoUltraliteHelper
    {
        private static IntPtr _helperClass = JNIEnv.FindClass("monoultralite/helper/jni_helper");

        //  Generate Sigs with: javap -classpath "C:\Program Files (x86)\Android\android-sdk\platforms\android-8\android.jar;UltraLiteJNI12.jar" -s -p jni_helper
        public static void connectDB(Activity act)
        {
            IntPtr methodId = JNIEnv.GetStaticMethodID(_helperClass, "connectDB", "(Landroid/app/Activity;)V");
            if (null != methodId)
                JNIEnv.CallStaticVoidMethod(_helperClass, methodId, new JValue(act));
        }

        public static void disconnectDB()
        {
            IntPtr methodId = JNIEnv.GetStaticMethodID(_helperClass, "disconnectDB", "()V");
            if (null != methodId)
                JNIEnv.CallStaticVoidMethod(_helperClass, methodId);
        }

        public static Boolean isConnected()
        {
            Boolean ret = false;
            IntPtr methodId = JNIEnv.GetStaticMethodID(_helperClass, "isConnected", "()Z");
            if (null != methodId)
                ret = JNIEnv.CallStaticBooleanMethod(_helperClass, methodId);
            return ret;
        }
    }
 

The method signatures are generated via javap:

javac jni_helper.java
javap -s -p jni_helper
Compiled from "jni_helper.java"
public class monoultralite.helper.jni_helper extends java.lang.Object{
private static final java.lang.String TAG;
  Signature: Ljava/lang/String;
private static com.ianywhere.ultralitejni12.Connection conn_;
  Signature: Lcom/ianywhere/ultralitejni12/Connection;
private monoultralite.helper.jni_helper();
  Signature: ()V
public static void connectDB(android.app.Activity);
  Signature: (Landroid/app/Activity;)V
public static void disconnectDB();
  Signature: ()V
public static boolean isConnected();
  Signature: ()Z
static {};
  Signature: ()V
}

This keeps us from having “method not found” exceptions!

First GUI

I added a few buttons that call the static methods to test the App->JNI->JAR->Shared Object.

[Activity(Label = "MonoUltralite", MainLauncher = true, Icon = "@drawable/icon")]
    public class UltraliteActivity1 : Activity
    {
        int count = 1;
        JValue obj = JValue.Zero;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.MyButton);
            button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };

            Button buttonConnect = FindViewById<Button>(Resource.Id.MyButtonConnect);
            buttonConnect.Click += delegate
                {
                    if(!MonoUltraliteHelper.isConnected())
                        MonoUltraliteHelper.connectDB(this);
                };

            Button buttonDisconnect = FindViewById<Button>(Resource.Id.MyButtonDisconnect);
            buttonDisconnect.Click += delegate
                {
                    if (MonoUltraliteHelper.isConnected())
                        MonoUltraliteHelper.disconnectDB();
                };

            Button buttonSync = FindViewById<Button>(Resource.Id.MyButtonSync);
            buttonSync.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
        }
    }   

As you see, the Query and Sync buttons don’t do anything yet.

Building any running confirms that the C# code may easily call into Java code, which may use a native library.  It ended up being fairly simple to build this.

Update May 6:

The Disconnect Native method was not implemented in the 12.0.1 release of UltraliteJ, I’ve reported it to Sybase and they tell me it’ll be fixed.

The bigger news is that MonoDroids’ future may be in doubt.  The Novel sale has resulted in an unknown number of Mono developers being let-go.  This leaves the future of MonoDroid in doubt to me; until their is an official announcement I’m going to hold off on my MonoDroid research.

I have no doubt that I can get the UltraliteJ database syncing to a Sybase server and executing local queries and such.  The JNI part is the “magic” that makes this go.  Even that isn’t too challenging with the help of javap.

Entry Filed under: Uncategorized

Leave a Comment

Required

Required, hidden

Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackback this post  |  Subscribe to the comments via RSS Feed


Calendar

May 2011
M T W T F S S
« Mar   Jul »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Most Recent Posts