manski's blog

Accessing Java Classes from Mono for Android via JNI

Mono for Android let’s you write Android applications in a .NET language (for example, C#). It comes with wrappers for almost the entire Android API so building “standard” apps is easy enough. However, sometimes you may need to work with third party Java classes that aren’t part of Android itself. These classes usually come as a .jar file. Fortunately, Mono for Android provides a way to access those Java classes. Unfortunately, this way is a “little” bit tricky and documentation on this is (currently) quite limited.

This article will show some basic example of how to call a method of a Java class from Mono for Android. We will use Visual Studio 2010 in this article, as it is (in my opinion) currently superior to Mono Develop.

Update: Mono for Android v4.2 and higher provides an automatic way for doing the things described in this article. This is a lot easier than the manual way and thus the preferred way.

One last word before we start:

  • There are currently some limitation tied to JNI (which we will use to access Java classes). Specifically you can’t subclass existing Java classes or implement existing Java interfaces from Mono. This limitation can be circumvented by creating your own Java classes for third party and calling them from Mono. Update: According to Binding Android Types this is possible.
  • Also, I’ve been told that we can expect better support for third party jars in the next few months with some alpha code presented here.

Prerequisites

For this article I’ll assume you know your way around in Java and C#, and have installed:

  • Visual Studio 2010 (other version may work as well). Note that you can not use the Express Edition as it doesn’t support Add-Ins (specifically the “Mono for Android” Add-In here).
  • Mono for Android. This includes the Android SDK and the Visual Studio Add-In. You don’t need a paid version for this article. The evaluation version will do just fine.
  • Eclipse with ADT. We’ll use this to create a simple .jar file that depends on some Android APIs.

Native Java Library

To demonstrate accessing a Java library, let’s create a very simple one.

Create a new Android project called “JarLibForMonoDroid” (without an Activity). Then open the project settings and under Android activate Is Library.

Note: A working project is provided in the example source code available in the Download section below.

Activated "Is Library" in the project's Android settings

Create a class called JniTest in package com.mayastudios.jnitest. Here’s its contents:

package com.mayastudios.jnitest;

public class JniTest {
  public JniTest() { 
    // Force the user to create an instance
  }
  
  public String getGreetingText() {
    return "Hello, " + android.os.Build.MODEL;
  }
}

Save the Java file. The ADT should now have created a .jar file in the bin directory. (If it’s not there, make sure you’ve activated Is Library in the Android project settings.) That’s it.

Location of the automatically generated .jar file

Note: Normally, you don’t need an Android project but could create a pure Java project as well. In this example, we use an Android project to demonstrate the ability to access the Android API from within the Java classes and to have an easy way to create a .jar file for our project.

Accessing the Java Class from Mono

Create a simple Mono for Android project in Visual Studio.

Add jar to the project

The first step is to add the .jar file that was created in the previous section to the project (via Add existing item). For ease of use you should add this file as a link (by clicking on the down arrow at the Add button) like shown in the screenshot. If you don’t do this, the .jar file will be copied into the project directory and you need to re-copy it every time it changes.

Adding the .jar file as a link

You should now have an entry for the .jar file in your Solution Explorer pane. Note that there’s a small arrow in the jar’s icon indicating that it’s a link.

Linked jar file in the Solution Explorer

The last thing you need to do is to open up the jar’s Properties (via context menu or Alt+Return) and set the Build Action to AndroidJavaLibrary. This makes the classes contained in the jar accessible to JNI.

Setting the jar's "Build Action" to "AndroidJavaLibrary"

Important: You need to rebuild the project every time the .jar file has changed. The Mono for Android Add-In currently doesn’t detect the change automatically.

Add JNI code

Now, let’s access the Java class. For this, in Mono for Android you use a Mono version of JNI. With JNI in Java native libraries written in C++ can access Java objects. So, with JNI in Mono .NET classes can access Java objects.

To get started, in the Activity1 class, create a new method (called GetGreetingText) and change button’s Click event handler to set the button’s text with the return value of the method. (This all assumes that Xamarin didn’t change the default project example code when you read this. Otherwise you need to adapt the code.) The code should now look similar to this:

[Activity(Label = "MonoJniExample", MainLauncher = true, Icon = "@drawable/icon")]
public class Activity1 : Activity {
  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 += (s, e) => {
      button.Text = GetGreetingText();
    };
  }

  string GetGreetingText() {
    // Code to be added here
  }
}

We will now add the necessary JNI code to GetGreetingText. In the end it’ll return “Hello, <Your Device Name here>”. The code should be pretty much self-explanatory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
string GetGreetingText() {
  // Get a pointer to the Java class.
  IntPtr nativeJavaClass = JNIEnv.FindClass("com/mayastudios/jnitest/JniTest");
  // Find the parameterless constructor.
  IntPtr defaultConstructor = JNIEnv.GetMethodID(nativeJavaClass, 
                                                 "<init>", 
                                                 "()V");
  // Create a new instance of the class.
  IntPtr instance = JNIEnv.NewObject(nativeJavaClass, defaultConstructor);
 
  // Find our example method.
  IntPtr method = JNIEnv.GetMethodID(nativeJavaClass, 
                                     "getGreetingText", 
                                     "()Ljava/lang/String;");
  // Call the method.
  IntPtr resultPtr = JNIEnv.CallObjectMethod(instance, method);
  // Convert pointer to return value into a usable object (here a string).
  Java.Lang.String nativeResult 
    = new Java.Lang.Object(resultPtr, JniHandleOwnership.TransferLocalRef)
                          .JavaCast<Java.Lang.String>();
 
  // Convert the Java string into a .NET string and return it.
  return nativeResult.ToString();
}

There are a few that should be noted here:

  • The method JNIEnv.GetMethodID() takes the method name as second parameter and the method signature as third parameter.

    • The method name for a constructor is always <init>.
    • The signature is composed of the parameter list followed by the return type of the method. It adheres the JNI rules for specifying the signature. I suggest you use this JNI Cheat Sheet for trying to understand the syntax.
  • Some rudimentary description of JNI’s methods can be found in the JNI specification.
  • The JniHandleOwnership enumeration (line 19) hasn’t been documented yet. So I can’t really say anything about it. I’m currently waiting for an answer about this.

You should now be able to run the app and when you tap on the button it should change its text.

Download the Project Files

I’ve compiled a small .zip file containing the source code and all project files.

projectfiles.zip

4 comments

  1. Rodrigo Rodrigues said:

    Can you provide us other examples like this, but with different method signatures, including parameters on it, such as:

    ” (/*Integer, Integer;*/) /*Integer*/ Ljava/lang/String;”);

    I’d really appreciate it, thanks!

  2. John said:

    I am getting a compile error on the sample. “Java exited with code 1.” I got this when I ran build in vs2010. What do I need to do?

  3. Manski (post author) said:

    Just tried it again myself. Works without any problem for me. What Mono for Android version are you using? And can you compile and run other MfA apps?

  4. Bone said:

    thanks for you in the first,but I have a another problem.
    How can I import the *.jar file in the visual studio 2010 with the “using XXXX”.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.