Sie sind auf Seite 1von 56

Extending

your Qt Android application using JNI


Dev Days, 2014

Presented by BogDan Vatra


Material based on Qt 5.3, created on November 13, 2014

Extending your application using JNI

Extending your application


using JNI

Extending your application using JNI

Extending your application using JNI

JNI Crash Course

JNI Crash Course

What is JNI and why do you need it ?


JNI is the Java Native Interface . It is needed to do calls to/from Java world from/to native (C/C++)
world.
It is impossible for Qt to implement all Android features, so to use them you'll need JNI to access
them.

JNI Crash Course

Missing Qt APIs
listening for Android O.S. notications:
sd card notications: bad removal, eject, mounted, unmounted, etc.
network notications: network up/down.
battery level and charging state.
etc.
accessing Android O.S. features:
telephony (Initiate calls, MMS, SMS, etc.)
contacts
speech (TTS and Speech Recognizer)
system accounts
system preferences
NFC
USB
printing (WARNING: needs API-19+)
create own Android Activities and Services

JNI Crash Course

JNI Crash Course

Use case 1: call a Java function


from C/C++

Use case 1: call a Java function from C/C++

Create the Java function


1
2
3
4
5
6
7
8
9
10
11
12
13

// java le android/src/com/kdab/training/MyJavaClass.java
package com.kdab.training;
public class MyJavaClass
{
// this method will be called from C/C++
public static int bonacci(int n)
{
if (n < 2)
return n;
return bonacci(n-1) + bonacci(n-2);
}
}

Demo android/JNIIntro

Use case 1: call a Java function from C/C++

Call a Java method from C/C++, the Qt way


Let's see what bonacci function call looks like using the Qt androidextras module.
1
2
3
4
1
2
3
4
5
6
7
8
9
10

# Changes to your .pro le


# ....
QT += androidextras
# ....
// C++ code
#include <QAndroidJniObject>
int bonacci(int n)
{
return QAndroidJniObject::callStaticMethod<jint>
("com/kdab/training/MyJavaClass" // java class name
, "bonacci" // method name
, "(I)I" // signature
, n);
}

Yes, that's all folks !

Use case 1: call a Java function from C/C++

JNI Crash Course

Use case 2: callback a C/C++


function from Java

Use case 2: callback a C/C++ function from Java

Your job
declare a native method in Java using native keyword (see slide 11)
register native method in C/C++ (see slide 12, slide 15 )
do the actual call (see slide 11)

Use case 2: callback a C/C++ function from Java

Declare and invoke the Java native methods


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// java le android/src/com/kdab/training/MyJavaClass.java
package com.kdab.training;
class MyJavaNatives
{
// declare the native method
public static native void sendFibonaciResult(int n);
}
public class MyJavaClass
{
// this method will be called from C/C++
public static int bonacci(int n)
{
if (n < 2)
return n;
return bonacci(n-1) + bonacci(n-2);
}
public static void compute_bonacci(int n)
{
// callback the native method with the computed result.
MyJavaNatives.sendFibonaciResult(bonacci(n));
}
}

Use case 2: callback a C/C++ function from Java

C/C++ register native methods


Registering functions using Java_Fully_Qualied_Class_Name_MethodName template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// C++ code
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL
Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv */*env*/,
jobject /*obj*/,
jint n)
{
qDebug() << "Computed bonacci is:" << n;
}
#ifdef __cplusplus
}
#endif

Use case 2: callback a C/C++ function from Java

Using Java_Fully_Qualied_Class_Name_MethodName.
Pro:
easier to declare , you don't need to specify the function signature
easier to register , you don't need to explicitly register it

Con:
the function names are huge :
Java_com_kdab_training_MyJavaNatives_sendFibonaciResult
the library will export lots of functions
unsafer , there is no way for the VM to check the function signature

Use case 2: callback a C/C++ function from Java

Use JNIEnv::RegisterNatives to register native functions.


Step 4 call JNIEnv::RegisterNatives(java_class_ID, methods_vector, n_methods)
Step 3 nd the ID of java class that declares these methods using JniEnv::FindClass
Step 2 create a vector with all C/C++ methods that you want to register
Step 1 get JNIEnv pointer by dening
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) .
You can dene it (once per .so le) in any .cpp le you like

Use case 2: callback a C/C++ function from Java

Use JNIEnv::RegisterNatives
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// C++ code
#include <jni.h>
// dene our native method
static void sendFibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n)
{
qDebug() << "Computed bonacci is:" << n;
}
// step 2
// create a vector with all our JNINativeMethod(s)
static JNINativeMethod methods[] = {
{ "sendFibonaciResult", // const char* function name;
"(I)V", // const char* function signature
(void *)sendFibonaciResult // function pointer }
};

Use case 2: callback a C/C++ function from Java

Use JNIEnv::RegisterNatives
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// step 1
// this method is called automatically by Java after the .so le is loaded
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
JNIEnv* env;
// get the JNIEnv pointer.
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
// step 3
// search for Java class which declares the native methods
jclass javaClass = env->FindClass("com/kdab/training/MyJavaNatives");
if (!javaClass)
return JNI_ERR;
// step 4
// register our native methods
if (env->RegisterNatives(javaClass, methods,
sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}

Use case 2: callback a C/C++ function from Java

Use JNIEnv::RegisterNatives
Pro:
the native function can have any name you want
the library needs to export only one function (JNI_OnLoad) .
safer , because the VM checks the declared signature

Con:
is slightly harder to use

Use case 2: callback a C/C++ function from Java

Extending your application using JNI

SD Card Notifications Example

SD Card Notications Example

Game plan
Plumbing
understanding Android les
how to use an external IDE to manage the Java part
import existing Android les
debug Java

Architecture
how to extend the Java part of your Qt Application
how to do safe calls from Qt thread to Android UI thread
how to do safe calls from Android UI thread to Qt thread

SD Card Notications Example

SD Card Notications Example

Understanding Android files

Understanding Android les

Copy missing Android les


copy all les from <qt_install_path>/src/android/java/ to <your_src>/android/ folder
make sure your .pro le has ANDROID_PACKAGE_SOURCE_DIR property set
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android

Understanding Android les

Meaning of the Android les


AndroidManifest.xml - is the same le copied by Qt Creator when you click on Create
AndroidManifest.xml button.
version.xml - is used by Qt Creator to update your existing Android les
res/values/libs.xml - this le contains the information about the needed libs
res/values(-*)/strings.xml - these les contain the (translated) strings needed by Java part
src/org/kde/necessitas/ministro/ *.aidl - these les are used to connect to Ministro service
src/org/qtproject/qt5/android/bindings/QtApplication.java - this class contains all the logic needed to
forward all the activity calls to the activity delegate
src/org/qtproject/qt5/android/bindings/QtActivity.java - this is the application activity class, it
contains the logic to connect to Ministro or to unpack the bundled Qt libs. It also forwards all calls to
the activity delegate.

Understanding Android les

SD Card Notications Example

Using an external IDE for Java


part

Using an external IDE for Java part

Using an external IDE to extend the Java part


Sadly, the Java support in Qt Creator is close to zero, so we need to use an external IDE to easily extend
the Java part. Android provides two powerful IDEs:
Eclipse
Android Studio (this is very good and stable even though it is labeled as beta).

Using an external IDE for Java part

Eclipse users
If you are not using ADT bundle, then make sure you have all ADT plugins installed in Eclipse.

Using an external IDE for Java part

Using an external IDE


The external IDE will be used to:
import existing Android les
create and edit Java les
debug Java part

The external IDE will NOT be used to run the application, we still need QtCreator for that job!

Using an external IDE for Java part

This presentation will teach you only the very basic usage of both of them. If you want to learn more
please check their own documentation.
Also it's up to you which one you choose to use.

Using an external IDE for Java part

Using an external IDE for Java part

Import existing Android files in


Eclipse

Import existing Android les in Eclipse

WARNING
Before you start to import the project make sure the Build Automatically feature is turned OFF !
Otherwise when you start to build your project you'll get lots of mysterious build problems.
Uncheck Project ->Build Automatically

Import existing Android les in Eclipse

Importing in Eclipse
File ->New ->Project ...

Import existing Android les in Eclipse

Importing in Eclipse
Choose Android Project from Existing Code wizard

Import existing Android les in Eclipse

Importing in Eclipse
Select the root folder of your Android les (it should be your_qt_src/android ) and press Finish button.

Import existing Android les in Eclipse

Using an external IDE for Java part

Import existing Android files in


Android Studio

Import existing Android les in Android Studio

Importing in Android Studio


File ->Import project ... does all the magic, you just need to select the root folder of your Android les
(it should be your_qt_src/android ) and continue the wizard until it is nished.

Import existing Android les in Android Studio

Using an external IDE for Java part

Debugging Java part

Debugging Java part

Debug with Eclipse


set the break points
switch to DDMS perspective (Window ->Open perspective ->DDMS )
start the application (using QtCreator)
wait for application to start
select the application
click the debug icon

Debugging Java part

Debug with Android Studio


set the break points
attach debugger (Run ->Attach debugger to Android process )
start the application (using QtCreator)
wait for application to start
select the application
click the OK button

Debugging Java part

Debugging tips and tricks


The problem comes when we need to start the debugging very early. To do that we are going to use the
old fashioned sleep trick. This is how our custom onCreate function looks:
1
2
3
4
5
6
7
8
9
10

@Override
public void onCreate(Bundle savedInstanceState)
{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
....
}

Debugging Java part

Architecture diagram

SD Card Notications Example

SD Card Notications Example

Extending Java part

Extending Java part

Extending, not changing


You should not change any of the existing .java les, instead you should extend them
There is no need to extend the QtApplication application class
You should extend only the QtActivity class. The Activity object is very important, you'll need it to
instantiate most of the Android classes
Demo android/SD_Card_Notications

Extending Java part

Extending QtActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// src/com/kdab/training/MyActivity.java
package com.kdab.training;
import android.os.Bundle;
import org.qtproject.qt5.android.bindings.QtActivity;
public class MyActivity extends QtActivity
{
// we'll need it in BroadcastReceiver
public static MyActivity s_activity = null;
// every time you override a method, always make sure you
// then call super method as well
@Override
public void onCreate(Bundle savedInstanceState)
{
s_activity = this;
super.onCreate(savedInstanceState);
}
@Override
protected void onDestroy()
{
super.onDestroy();
s_activity = null;
}
}

Extending Java part

Extending Java part


Change default activity to AndroidManifest.xml, from:
1 <activity ...
2 android:name="org.qtproject.qt5.android.bindings.QtActivity"
3 ... >;

To:
1 <activity ...
2 android:name="com.kdab.training.MyActivity"
3 ... >;

Extending Java part

NativeFunctions.java part 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class NativeFunctions {


// declare the native functions
// these functions will be called by the BroadcastReceiver object
// when it receives a new notication
public static native void onReceiveNativeMounted();
public static native void onReceiveNativeUnmounted();
// this static method is called by C/C++ to register the BroadcastReceiver. (step 1)
public static void registerBroadcastReceiver() {
if (MyActivity.s_activity != null) {
// Qt is running on a dierent thread than Android. (step 2)
// In order to register the receiver we need to execute it in the UI thread
MyActivity.s_activity.runOnUiThread( new RegisterReceiverRunnable());
}
}
}

Extending Java part

NativeFunctions.java part 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14

class RegisterReceiverRunnable implements Runnable


{
// Step 3
// this method is called on Android Ui Thread
@Override
public void run() {
IntentFilter lter = new IntentFilter();
lter.addAction(Intent.ACTION_MEDIA_MOUNTED);
lter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
lter.addDataScheme("le");
// this method must be called on Android Ui Thread
MyActivity.s_activity.registerReceiver(new SDCardReceiver(), lter);
}
}

Extending Java part

NativeFunctions.java part 3
1
2
3
4
5
6
7
8
9
10
11

class SDCardReceiver extends BroadcastReceiver {


@Override
public void onReceive(Context context, Intent intent) {
// Step 4
// call the native method when it receives a new notication
if (intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED))
NativeFunctions.onReceiveNativeMounted();
else if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED))
NativeFunctions.onReceiveNativeUnmounted();
}
}

Extending Java part

Architecture diagram, Java part

Extending Java part

SD Card Notications Example

Extending C/C++ part

Extending C/C++ part

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include "mainwindow.h"
#include <QApplication>
#include <QAndroidJniObject>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Step 1.
// call registerBroadcastReceiver to register the broadcast receiver
QAndroidJniObject::callStaticMethod<void>("com/kdab/training/NativeFunctions"
, "registerBroadcastReceiver"
, "()V");
MainWindow::instance().show();
return a.exec();
}

Extending C/C++ part

mainwindow.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class MainWindow : public QMainWindow


{
Q_OBJECT
public:
static MainWindow &instance(QWidget *parent = 0);
public slots:
void onReceiveMounted();
void onReceiveUnmounted();
private:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};

Extending C/C++ part

mainwindow.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

MainWindow &MainWindow::instance(QWidget *parent)


{
static MainWindow mainWindow(parent);
return mainWindow;
}
// Step 6
// Callback in Qt thread
void MainWindow::onReceiveMounted()
{
ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_MOUNTED"));
}
void MainWindow::onReceiveUnmounted()
{
ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_UNMOUNTED"));
}

Extending C/C++ part

native.cpp part 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// Step 4. callback from java in Android thread


// dene our native methods
static void onReceiveNativeMounted(JNIEnv */*env*/, jobject /*obj*/)
{
// Step 5. Delegate the call to Qt thread.
QMetaObject::invokeMethod(&MainWindow::instance(), "onReceiveMounted"
, Qt::BlockingQueuedConnection);
}
static void onReceiveNativeUnmounted(JNIEnv */*env*/, jobject /*obj*/)
{
// Step 5. Delegate the call to Qt thread.
QMetaObject::invokeMethod(&MainWindow::instance(), "onReceiveUnmounted"
, Qt::BlockingQueuedConnection);
}

Extending C/C++ part

native.cpp part 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

//create a vector with all our JNINativeMethod(s)


static JNINativeMethod methods[] = {
{"onReceiveNativeMounted", "()V", (void *)onReceiveNativeMounted},
{"onReceiveNativeUnmounted", "()V", (void *)onReceiveNativeUnmounted},
};
// this method is called automatically by Java after the .so le is loaded
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
JNIEnv* env; // get the JNIEnv pointer.
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
// search for Java class which declares the native methods
jclass javaClass = env->FindClass("com/kdab/training/NativeFunctions");
if (!javaClass)
return JNI_ERR;
// register our native methods
if (env->RegisterNatives(javaClass, methods,
sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}

Extending C/C++ part

Architecture diagram, Qt JNI part

Extending C/C++ part

Architecture diagram, summary

Extending C/C++ part

Questions ?
Thank you for your time!
Contact us:
http://www.kdab.com
qtonandroid@kdab.com
info@kdab.com
training@kdab.com
bogdan@kdab.com

Das könnte Ihnen auch gefallen