Necessitas/JNI

From KDE Community Wiki

Using JNI

The Java Native Interface (JNI) is a programming framework that enables Java code running in a Java Virtual Machine (JVM) to call, and to be called by, native applications (programs specific to a hardware and operating system platform) and libraries written in other languages such as C, C++ and assembly (quote from wikipedia) .

In this page we are not going to teach you JNI, Java or C/C++, instead we'll try to explain how to use JNI from a Qt application. If you want to lean JNI, then you should start with by reading the following documentations Google's JNI page Google's blogs.

First this you need to know is that you don't need to load your application manually (e.g. don't use System.loadLibrary), the second thing you also need to know is that your Qt application is running on a different thread than main Android Activity, so if you want to use Activity you should use runOnUiThread.

In this example we'll create a simple Audio player using Android's MediaPlayer. Here you can download the full example.

The .pro file

Edit your profile and add the following lines for Android platform.

android {
    SOURCES += androidmediaplayer.cpp
    HEADERS += androidmediaplayer.h
}

The QSimpleAudioPlayer.java file

Create QSimpleAudioPlayer.java to your project_root/android/src/org/kde/necessitas/origo/

package org.kde.necessitas.origo;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.util.Log;

public class QSimpleAudioPlayer {
    MediaPlayer m_mediaPlayer;

    public QSimpleAudioPlayer() {
        m_mediaPlayer = new MediaPlayer();
    }

    boolean setUrl(String path)
    {
        m_mediaPlayer.reset();
        try {
            m_mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            m_mediaPlayer.setDataSource(path);
            m_mediaPlayer.prepare();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        Log.i("Qt", "addTwoNumbers returned "+addTwoNumbers(1,2));
        return true;
    }

    boolean play()
    {
        try {
            m_mediaPlayer.start();
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    boolean pause()
    {
        try {
            m_mediaPlayer.pause();
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    boolean stop()
    {
        try {
            m_mediaPlayer.stop();
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    boolean release()
    {
        try {
            m_mediaPlayer.release();
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public static native int addTwoNumbers(int a, int b);
}

the androidmediaplayer.cpp file

#include <QDebug>

#include "androidmediaplayer.h"

static JavaVM* s_javaVM = 0;
static jclass s_audioPlayerClassID = 0;
static jmethodID s_audioPlayerConstructorMethodID=0;
static jmethodID s_audioPlayerSetUrlMethodID=0;
static jmethodID s_audioPlayerPlayMethodID=0;
static jmethodID s_audioPlayerPauseMethodID=0;
static jmethodID s_audioPlayerStopMethodID=0;
static jmethodID s_audioPlayerReleaseMethodID=0;

SimpleAndroidMediaPlayer::SimpleAndroidMediaPlayer()
{
    JNIEnv* env;
    // Qt is running in a different thread than Java UI, so you always Java VM *MUST* be attached to current thread
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return;
    }

    // Create a new instance of QSimpleAudioPlayer
    m_audioPlayerObject = env->NewGlobalRef(env->NewObject(s_audioPlayerClassID, s_audioPlayerConstructorMethodID));
    if (!m_audioPlayerObject)
    {
        qCritical()<<"Can't create the player";
        return;
    }

    // Don't forget to detach from current thread
    s_javaVM->DetachCurrentThread();
}

SimpleAndroidMediaPlayer::~SimpleAndroidMediaPlayer()
{
    if (!m_audioPlayerObject)
        return;

    JNIEnv* env;
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return;
    }

    if (!env->CallBooleanMethod(m_audioPlayerObject, s_audioPlayerReleaseMethodID))
        qCritical()<<"Releasing media player object failed";

    s_javaVM->DetachCurrentThread();
}

bool SimpleAndroidMediaPlayer::setUrl(const QString &url)
{
    if (!m_audioPlayerObject)
        return false;

    JNIEnv* env;
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return false;
    }
    jstring str = env->NewString(reinterpret_cast<const jchar*>(url.constData()), url.length());
    jboolean res = env->CallBooleanMethod(m_audioPlayerObject, s_audioPlayerSetUrlMethodID, str);
    env->DeleteLocalRef(str);
    s_javaVM->DetachCurrentThread();
    return res;
}

bool SimpleAndroidMediaPlayer::play()
{
    if (!m_audioPlayerObject)
        return false;

    JNIEnv* env;
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return false;
    }
    jboolean res = env->CallBooleanMethod(m_audioPlayerObject, s_audioPlayerPlayMethodID);
    s_javaVM->DetachCurrentThread();
    return res;
}

bool SimpleAndroidMediaPlayer::pause()
{
    if (!m_audioPlayerObject)
        return false;

    JNIEnv* env;
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return false;
    }
    jboolean res = env->CallBooleanMethod(m_audioPlayerObject, s_audioPlayerPauseMethodID);
    s_javaVM->DetachCurrentThread();
    return res;
}

bool SimpleAndroidMediaPlayer::stop()
{
    if (!m_audioPlayerObject)
        return false;

    JNIEnv* env;
    if (s_javaVM->AttachCurrentThread(&env, NULL)<0)
    {
        qCritical()<<"AttachCurrentThread failed";
        return false;
    }
    jboolean res = env->CallBooleanMethod(m_audioPlayerObject, s_audioPlayerStopMethodID);
    s_javaVM->DetachCurrentThread();
    return res;
}

// our native method, it is called by the java code above
static int addTwoNumbers(JNIEnv * /*env*/, jobject /*thiz*/,int a, int b)
{
    return a+b;
}

static JNINativeMethod methods[] = {
    {"addTwoNumbers", "(II)I", (void *)addTwoNumbers}
};

// this method is called immediately after the module is load
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        qCritical()<<"Can't get the enviroument";
        return -1;
    }

    s_javaVM = vm;
    // search for our class
    jclass clazz=env->FindClass("org/kde/necessitas/origo/QSimpleAudioPlayer");
    if (!clazz)
    {
        qCritical()<<"Can't find QSimpleAudioPlayer class";
        return -1;
    }
    // keep a global reference to it
    s_audioPlayerClassID = (jclass)env->NewGlobalRef(clazz);

    // search for its contructor
    s_audioPlayerConstructorMethodID = env->GetMethodID(s_audioPlayerClassID, "<init>", "()V");
    if (!s_audioPlayerConstructorMethodID)
    {
        qCritical()<<"Can't find QSimpleAudioPlayer class contructor";
        return -1;
    }

    // search for setUrl method
    s_audioPlayerSetUrlMethodID = env->GetMethodID(s_audioPlayerClassID, "setUrl", "(Ljava/lang/String;)Z");
    if (!s_audioPlayerSetUrlMethodID)
    {
        qCritical()<<"Can't find setUrl method";
        return -1;
    }

    // search for play method
    s_audioPlayerPlayMethodID = env->GetMethodID(s_audioPlayerClassID, "play", "()Z");
    if (!s_audioPlayerPlayMethodID)
    {
        qCritical()<<"Can't find start method";
        return -1;
    }

    // search for play method
    s_audioPlayerPauseMethodID = env->GetMethodID(s_audioPlayerClassID, "pause", "()Z");
    if (!s_audioPlayerPauseMethodID)
    {
        qCritical()<<"Can't find start method";
        return -1;
    }

    // search for stop method
    s_audioPlayerStopMethodID = env->GetMethodID(s_audioPlayerClassID, "stop", "()Z");
    if (!s_audioPlayerStopMethodID)
    {
        qCritical()<<"Can't find stop method";
        return -1;
    }

    // search for release method
    s_audioPlayerReleaseMethodID = env->GetMethodID(s_audioPlayerClassID, "release", "()Z");
    if (!s_audioPlayerReleaseMethodID)
    {
        qCritical()<<"Can't find release method";
        return -1;
    }

    // register our native methods
    if (env->RegisterNatives(s_audioPlayerClassID, methods, sizeof(methods) / sizeof(methods[0])) < 0)
    {
        qCritical()<<"RegisterNatives failed";
        return -1;
    }

    qDebug()<<"Yahooo !";
    return JNI_VERSION_1_6;
}

the androidmediaplayer.h file

#ifndef ANDROIDMEDIAPLAYER_H
#define ANDROIDMEDIAPLAYER_H
#include <jni.h>

class SimpleAndroidMediaPlayer
{
public:
    SimpleAndroidMediaPlayer();
    ~SimpleAndroidMediaPlayer();
    bool setUrl(const QString &url);
    bool play();
    bool pause();
    bool stop();

private:
    jobject m_audioPlayerObject;

};

#endif // ANDROIDMEDIAPLAYER_H