Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

JNI in Android (and a foreword of why JNI Sucks)

wall-smaller

Quick Preface: You do NOT need root to create and use a shared native library (.so) in Android from your Java application.

I had never tried Java Native Interface until today. And I must say, it completely sucks. Not only is the header generation/implementation nontrivial and tedious, JNI is inherently broken in that it is not architecture/platform nonspecific. So I proudly present to you my list of why JNI sucks.

  • You can't access arbitrary functions in arbitrary DLLs. JNI requires that you write a glue C/C++ layer to do whatever you want to do natively.
  • On Android, your glue layer must contain a JNI_OnLoad function that explicitly register every method that needs to be made available to the JVM. The JVM can't simply check the export tables and intelligently import it.
  • Since developers must write a glue layer, you must also compile and package that glue layer per platform you are targeting. This design is absurd because platforms can have different CPU architectures and different Operating Systems, and still support similar calls in the similar libraries. For example:
    • OpenGL ES (libGLES_CM). On Windows Mobile it is called libGLES_CM.dll. On Linux/Android it is called libGLES_CM.so. These libraries have the same "undecorated" library name, and also contain the same functions with the same signatures. Unfortunately due to how how terrible an implementation JNI is, the same Java application would not work on every platform.
    • Standard System Libraries on Linux or Windows (libc, libdl, libm, ole32, kernel32, etc) - Once again, need to compile libraries per architecture (ARM, x86, x64, MIPS, etc) to use the common/base functions that would be available on the platform (GetWindowEx, statfs, etc).
  • Write once, run anywhere. (Given that you are willing to do multiple cross compiles and embed multiple native binaries into a single package and unpack the appropriate one at runtime.)

For amusement's sake, let's look at what a C# PInvoke looks like versus JNI on Android. Suppose we want to clear the screen in OpenGL:

C#:

Just declare the method signature and the DLL. Simple! Call that like any other method in C#. (Note that the managed executable generated by this code works on both Android or Windows Mobile. Yes, I tested it.)

[DllImportAttribute("libGLES_CM")]
public static extern void glClearColor(float red, float green, float blue, float alpha);

JNI? Let's start out by declaring it.

public static native void glClearColor(float red, float green, float blue, float alpha);

Not done yet! Now the fun begins. Drop to a command prompt and go to the bin directory of your project: javah com.koushikdutta.JNITest.JNITestAcitivity. Now we have a generated header file, com_koushikdutta_jnitest_JNITestActivity.h, that looks like this:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_koushikdutta_jnitest_JNITestActivity */

#ifndef _Included_com_koushikdutta_jnitest_JNITestActivity
#define _Included_com_koushikdutta_jnitest_JNITestActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_koushikdutta_jnitest_JNITestActivity
 * Method:    glClearColor
 * Signature: (FFFF)V
 */
JNIEXPORT void JNICALL Java_com_koushikdutta_jnitest_JNITestActivity_glClearColor
  (JNIEnv *, jclass, jfloat, jfloat, jfloat, jfloat);

#ifdef __cplusplus
}
#endif
#endif

Ok, now let's implement the C code:

#include <GLES/gl.h>

JNIEXPORT void JNICALL Java_com_koushikdutta_jnitest_JNITestActivity_glClearColor
  (JNIEnv * env, jclass clazz, jfloat r, jfloat g, jfloat b, jfloat a)
{
    glClearColor(r, g, b, a);
}

You probably thought you were done, didn't you? Sun thinks otherwise. You need to register the function in C code as well. Don't screw up when copy pasting the cryptic method signature strings!

static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */

    {"glClearColor", "(FFFF)V", (void*)Java_com_koushikdutta_jnitest_JNITestActivity_glClearColor},
};

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    jniRegisterNativeMethods(env, "com/koushikdutta/JNITest/JNITestActivity", sMethods, 1);
    return JNI_VERSION_1_4;
}

To be completely fair, jniRegisterNativeMethods is actually a convenience function that is found in libnativehelper.so on Android; I imported that library for the sake of... convenience. Its implementation is as follows (JNIHelp.c in the source):

int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        return -1;
    }
    return 0;
}

You need to load the library in Java code in your class's static constructor.

System.load("/data/data/com.koushikdutta.JNITest/libjnitest.so");

On Android, you must use System.load and provide a full path instead of using System.loadLibrary. This is because loadLibrary only checks /system/lib; it does not search LD_LIBRARY_PATH or your current working directory. Unfortunately, applications can't write to the /system/lib directory (without root).

.NET's P/Invoke is so much easier/better that a third party company (God forbid Sun actually do something to better the Java language) implemented something quite similar for Java, named, *drumroll please*, J/Invoke. Too bad Android doesn't have that. That would be nice.

Also, on Android, you can't do a normal ARM/GCC cross compile to make shared libraries (I've discussed this in a previous blog post). The resultant .so files will not work with Android's linker due to different linker scripts. To create shared libraries for Android, I would highly recommend just creating a subproject in the Android Source code tree and building the tree. Alternatively you can use the handy script found on this page.

Also, remember that the shared library will need to be contained in your APK file and extracted to the file system so it can be accessed. For an example of how that can be done, check out the source code that does something very similar in the Android Mono port.

Incidentally, this learning experience came about as a result of figuring out how to run Mono side by side with Java/Dalvik in the same process.

Post 11: Android versus Windows Mobile Development

I wanted to refrain from blogging about this until I had at least somewhat delved into Android development. And although I am by no means a guru at the Android platform yet, I feel I can safely make some broad statements, step on some toes, and be able to sufficiently back up my opinions. [0] :)

 

The Byte Code

Google made some interesting architectural and strategic decisions regarding Android. The most notable being Java as the development language and Dalvik as the underlying bytecode. Dalvik, in short, is a byte code that is interpreted at runtime, similar to Sun's Java byte code or Microsoft's Intermediate Language (aka IL)[1]. I didn't really understand why Google would go and create another byte code specification for Java until I read a great article by Stefano Mazzochi. The long and short of it is, Sun has some nasty licensing around Java ME, and the "Open" Handset Alliance can't really have an Open platform with the possibility of some mega-corporation trying to wet their beaks at their expense. [2]

So, still begs the same question: why invent a whole new byte code when Intermediate Language has no such licensing (it is an ECMA approved standard)?  Maybe Google took a look at it and decided it wasn't suitable for a mobile platform, or maybe they didn't want to jump into bed with Microsoft. I'm guessing the latter.

 

The Language

At the end of the day, the byte code generated behind the scenes is about as important as the color of my socks. I generally don't think about it on a daily basis, and what really matters is the language you are writing in. I can understand that Google chose Java to ease their target developer base's transition the new platform, but still... I'm going to just go ahead and say it. Java sucks.

How much does Java suck? Let me count the ways.

  1. Terrible constraints around package/file/directory/class names.
  2. Lack of anonymous methods.
  3. Lack of closures.
  4. No lambda expressions (or LINQ).
  5. Lack of events/delegates.
  6. Lack of partial classes.
  7. Ridiculous implementation of enumerations.
  8. Terrible implementation of generics (type erasure).
  9. No user defined value-types: no "struct" and thus no capability of having objects that live purely on the stack. This is nasty on performance when it counts (such as in games).
  10. Strings are immutable, but not interned.
  11. No access to "unsafe" code.
  12. No get/set accessor methods.
  13. No operator overloading.
  14. No extension methods.
  15. Last but not least, stupid and unintuitive brace positioning.

How many ways? 13, 14, 15, Ha, Ha, Ha, Ha, Ha!

 

I'm not trying to be a nit-picky zealot here; everything I listed is a feature I use on a regular basis. I will admit though, that I am enjoying anonymous classes to some degree, but they would be mostly pointless with the availability of events/delegates. I realize that most of what I listed is just syntactic sugar, and neither Java or C# is more "powerful" than the other; they can each do anything you need in their own way. Except C# lets you do it more quickly, intuitively, and elegantly. [3]

 

The Development Environments

Eclipse is a giant steaming pile of... yeah. Yes, very capable, very flexible, but whoever is doing usability testing on this junk needs to get a job that involves testing the constricting force of a rope around their neck.

My biggest pet peeve is the fact that you can't simply click in the Expressions view and edit or add something inline. You have to right click it and click add/edit. That takes you to a vanilla text editor (which has no Intellisense/Content Assist), type in your expression, pray it is parsable, and then click ok. Otherwise you are having to go through that zillion step process yet again.

Earlier I gave some kudos to Java's inner class capabilities. As neat as they are, Eclipse makes them damn near unusable: you can't watch a value of a member variable in the parent class of an inner class. That makes debugging a giant pain: you need to load all those members into local variables if something is going wrong, otherwise it is impossible to find and fix even simple issues, such as a null reference. I'm not sure if this is a problem with Eclipse, Java, or Android though.

The auto-complete in Eclipse is painfully annoying. Auto-complete should be optional, and not forcibly inserted with no regard to what the developer wants. I ended up turning it all off, because what it was inserting was generally useless. And even though the auto-complete is off, Eclipse is still inserting parenthesis for me...

 

The SDKs 

Though I have been complaining a lot about developing on Android, I do want to point out that I haven't actually complained about Android. Basically every API I could think of was standardized and implemented.

My litmus test in this regard was a port of Klaxon to Android. It really helped underscore the huge differences between the two platforms.

For me, most notable was the APIs to access device sensors. Version 1 of the Android SDK exposes a simple SensorManager class that took me 5 minutes to figure out and access. Microsoft has had 6ish versions of Windows Mobile with no Sensor API in sight.

Secondly, scheduling processes to start on Android is infinitely easier than in Windows Mobile. Here's how that learning process worked for me on Windows Mobile:

  • First I started with CeRunAppAtTime. I write a gross PInvoke for this. This works for a while, then stops working completely. Others have had this issue as well. I need to switch to another mechanism.
  • Then I switched to CeSetUserNotificationEx. I write yet another gross PInvoke for this.
  • This involves filling out some elaborate UserNotificationTrigger structure. I eventually get this working.
  • Then I find that if the device is sleeping, Klaxon sometimes does not start properly.
  • I solve this issue by digging through some documentation and randomly figuring out the problem: I need to force the device to turn on or it goes back to sleep.
  • Klaxon works again for a while, until daylight savings occurs. On some phones, it is now firing an hour late. I have no idea why. I have given up.

Let's compare this to Android:

  • Access the AlarmManager.
  • Tell it to turn on and fire an intent at a certain time.
  • Handle the intent.

This just works, no hassle, no fuss. I must say, intents are easy to use, amazingly flexible, and equally powerful.

So, two of the "difficult" parts for creating Klaxon on Windows Mobile were not so difficult at all on Android. The final piece: UI implementation.

Windows Forms code versus Android's XML Layout. Windows Forms which works with absolute sizing and positioning, and Android which uses relative sizing and positioning. Windows "Static" Controls versus Android's Animatable Controls. There really is no comparison, so I won't even bother.

Simply put, the relevant Android SDK blows the Windows Mobile SDK out of the water (language and framework features aside).

Windows Mobile does have far better documentation (MSDN) than Android, but I consider that more of a teething issue that would occur with any new SDK.

 

The Platforms

Android is pretty heavily locked down from a security standpoint. Applications must register what capabilities they wish to have, and the user must approve of them. And though this security is all in all a positive thing, it does have it's downsides; for example, it is impossible to write an application that screen shots a screen other than its own, as this is considered a security risk. [4]

And though this is not really a fault of Android per se, the phones that are actually being released are not as "open" as the name Open Handset Alliance would suggest. Carriers always have and will continue to lock down phones so long as they subsidize them. For example, the user does not have access to the root account on the G1; this makes removal/tweaking of the standard Android applications impossible. But at least on Android you can tweak the standard applications due to it being open source (after getting root).

On the flip side of the coin, Windows Mobile doesn't have a VM hiding all the nitty gritty details from you. Developers can easily get low level access to anything, without having to worry about Microsoft filling the holes. For example, if Android were to have shipped without a Sensor API, there is no way (outside of manually installing native binaries through ADB) that a developer could implement it. And that is not a comforting feeling. For example: Android does not support streaming video through the MediaPlayer yet, and as far as I know, nothing can be done about that unless the native android binaries are patched through an OTA update.

Oh, and the Android Market. Not going to bother elaborating on this point, as the statement itself is pretty self explanatory.

 

Summary

I don't really have anything insightful to say here, and that may be because it is 4AM. I'll wrap it up by saying, I really enjoy developing for Windows Mobile. The language and tools are great. However, the platform and applications simply are not very usable. On the other hand, though I'm not completely discontent with Android development, the platform, applications, and general usability is fantastic; for once I don't have to fight my phone to do simple things like browse the web. So you can guess which phone is always in my pocket. And then you can infer which once I am more interested in developing in.

That said, I hope Microsoft can produce a usable platform in the near future; as that would make me a very happy user and developer. I can't believe I'm going to say this, but they should take a page out of Apple's book and just say "screw backwards compatibility, screw existing applications, we're going to develop a brand new platform that works". They've been catering to the backwards compatibility mantra for so long that it is impossible for them to move forward. And all the while, they are alienating the very market share they are so afraid to lose.

 

 

[0] I've released 2 applications on the Market, Telnet and Klaxon, so I feel I have a decent grasp of developing for the Android platform. Klaxon has really helped underscore the huge differences in the platform.

[1] Yes, I realize that IL is not interpreted on the Windows Platform, it is JIT compiled.

[2] As "evil" as Microsoft may be connoted to be, you don't see them on the news exercising their patent rights. Unlike Sun.

[3] I'm thinking about looking into Ruby.

[4] Maybe Google will implement this in the future.