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
p.1
ExtendingyourQtAndroidapplicationusingJNI
DevDays,2014
PresentedbyBogDanVatra
MaterialbasedonQt5.3,createdonNovember13,2014
p.2
ExtendingyourapplicationusingJNI
ExtendingyourapplicationusingJNI
ExtendingyourapplicationusingJNI
p.3
ExtendingyourapplicationusingJNI
JNICrashCourse
JNICrashCourse
p.4
WhatisJNIandwhydoyouneedit?
JNIistheJavaNativeInterfaceJavaNativeInterface.Itisneededtodocallsto/fromJavaworldfrom/tonative(C/C++)world.
ItisimpossibleforQttoimplementallAndroidfeatures,sotousethemyou'llneedJNItoaccessthem.
JNICrashCourse
p.5
MissingQtAPIs
listeningforAndroidO.S.notifications:
sdcardnotifications:badremoval,eject,mounted,unmounted,etc.
networknotifications:networkup/down.
batterylevelandchargingstate.
etc.
accessingAndroidO.S.features:
telephony(Initiatecalls,MMS,SMS,etc.)
contacts
speech(TTSandSpeechRecognizer)
systemaccounts
systempreferences
NFC
USB
printing(WARNING:needsAPI-19+)
createownAndroidActivitiesandServices
JNICrashCourse
p.6
JNICrashCourse
Usecase1:callaJavafunctionfromC/C++
Usecase1:callaJavafunctionfromC/C++
p.7
CreatetheJavafunction
1 //javafileandroid/src/com/kdab/training/MyJavaClass.java2 packagecom.kdab.training;34 publicclassMyJavaClass5 {6 //thismethodwillbecalledfromC/C++7 publicstaticintfibonacci(intn)8 {9 if(n<2)10 returnn;11 returnfibonacci(n-1)+fibonacci(n-2);12 }13 }
Demoandroid/JNIIntroDemoandroid/JNIIntro
Usecase1:callaJavafunctionfromC/C++
p.8
CallaJavamethodfromC/C++,theQtway
Let'sseewhatfibonaccifibonaccifunctioncalllookslikeusingtheQtandroidextrasmodule.
1 #Changestoyour.profile2 #....3 QT+=androidextras4 #....
1 //C++code2 #include<QAndroidJniObject>3 intfibonacci(intn)4 {5 returnQAndroidJniObject::callStaticMethod<jint>6 ("com/kdab/training/MyJavaClass"//javaclassname7 ,"fibonacci"//methodname8 ,"(I)I"//signature9 ,n);10 }
Yes,that'sallfolks!
Usecase1:callaJavafunctionfromC/C++
p.9
JNICrashCourse
Usecase2:callbackaC/C++functionfromJava
Usecase2:callbackaC/C++functionfromJava
p.10
Yourjob
declareanativemethodinJavausingnativekeyword(seeslide11)
registernativemethodinC/C++(seeslide12,slide15)
dotheactualcall(seeslide11)
Usecase2:callbackaC/C++functionfromJava
p.11
DeclareandinvoketheJavanativemethods
1 //javafileandroid/src/com/kdab/training/MyJavaClass.java2 packagecom.kdab.training;34 classMyJavaNatives5 {6 //declarethenativemethod7 publicstaticnativevoidsendFibonaciResult(intn);8 }910 publicclassMyJavaClass11 {12 //thismethodwillbecalledfromC/C++13 publicstaticintfibonacci(intn)14 {15 if(n<2)16 returnn;17 returnfibonacci(n-1)+fibonacci(n-2);18 }1920 publicstaticvoidcompute_fibonacci(intn)21 {22 //callbackthenativemethodwiththecomputedresult.23 MyJavaNatives.sendFibonaciResult(fibonacci(n));24 }25 }
Usecase2:callbackaC/C++functionfromJava
p.12
C/C++registernativemethods
RegisteringfunctionsusingJava_Fully_Qualified_Class_Name_MethodNameJava_Fully_Qualified_Class_Name_MethodNametemplate.
1 //C++code2 #include<jni.h>34 #ifdef__cplusplus5 extern"C"{6 #endif78 JNIEXPORTvoidJNICALL9 Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv*/*env*/,10 jobject/*obj*/,11 jintn)12 {13 qDebug()<<"Computedfibonacciis:"<<n;14 }1516 #ifdef__cplusplus17 }18 #endif
Usecase2:callbackaC/C++functionfromJava
p.13
UsingJava_Fully_Qualified_Class_Name_MethodNameJava_Fully_Qualified_Class_Name_MethodName.
Pro:
easiertodeclareeasiertodeclare,youdon'tneedtospecifythefunctionsignature
easiertoregistereasiertoregister,youdon'tneedtoexplicitlyregisterit
Con:
thefunctionnamesarehugehuge:Java_com_kdab_training_MyJavaNatives_sendFibonaciResult
thelibrarywillexportlotsoffunctions
unsaferunsafer,thereisnowayfortheVMtocheckthefunctionsignature
Usecase2:callbackaC/C++functionfromJava
p.14
UseJNIEnv::RegisterNativesJNIEnv::RegisterNativestoregisternativefunctions.
Step4Step4callJNIEnv::RegisterNatives(java_class_ID,methods_vector,n_methods)
Step3Step3findtheIDofjavaclassthatdeclaresthesemethodsusingJniEnv::FindClass
Step2Step2createavectorwithallC/C++methodsthatyouwanttoregister
Step1Step1getJNIEnvpointerbydefiningJNIEXPORTjintJNI_OnLoad(JavaVM*vm,void*/*reserved*/)JNIEXPORTjintJNI_OnLoad(JavaVM*vm,void*/*reserved*/).Youcandefineit(onceper.sofile)(onceper.sofile)inany.cppfileyoulike
Usecase2:callbackaC/C++functionfromJava
p.15
UseJNIEnv::RegisterNatives
1 //C++code2 #include<jni.h>34 //defineournativemethod5 staticvoidsendFibonaciResult(JNIEnv*/*env*/,jobject/*obj*/,jintn)6 {7 qDebug()<<"Computedfibonacciis:"<<n;8 }910 //step211 //createavectorwithallourJNINativeMethod(s)12 staticJNINativeMethodmethods[]={13 {"sendFibonaciResult",//constchar*functionname;14 "(I)V",//constchar*functionsignature15 (void*)sendFibonaciResult//functionpointer}16 };
Usecase2:callbackaC/C++functionfromJava
p.16
UseJNIEnv::RegisterNatives
1 //step12 //thismethodiscalledautomaticallybyJavaafterthe.sofileisloaded3 JNIEXPORTjintJNI_OnLoad(JavaVM*vm,void*/*reserved*/)4 {5 JNIEnv*env;6 //gettheJNIEnvpointer.7 if(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK)8 returnJNI_ERR;910 //step311 //searchforJavaclasswhichdeclaresthenativemethods12 jclassjavaClass=env->FindClass("com/kdab/training/MyJavaNatives");13 if(!javaClass)14 returnJNI_ERR;1516 //step417 //registerournativemethods18 if(env->RegisterNatives(javaClass,methods,19 sizeof(methods)/sizeof(methods[0]))<0){20 returnJNI_ERR;21 }22 returnJNI_VERSION_1_6;23 }
Usecase2:callbackaC/C++functionfromJava
p.17
UseJNIEnv::RegisterNatives
Pro:
thenativefunctioncanhaveanynameyouwant
thelibraryneedstoexportonlyonefunction(JNI_OnLoad)(JNI_OnLoad).
safersafer,becausetheVMchecksthedeclaredsignature
Con:
isslightlyhardertouse
Usecase2:callbackaC/C++functionfromJava
p.18
ExtendingyourapplicationusingJNI
SDCardNotificationsExample
SDCardNotificationsExample
p.19
Gameplan
PlumbingPlumbing
understandingAndroidfiles
howtouseanexternalIDEtomanagetheJavapart
importexistingAndroidfiles
debugJava
ArchitectureArchitecture
howtoextendtheJavapartofyourQtApplication
howtodosafecallsfromQtthreadtoAndroidUIthread
howtodosafecallsfromAndroidUIthreadtoQtthread
SDCardNotificationsExample
p.20
SDCardNotificationsExample
UnderstandingAndroidfiles
UnderstandingAndroidfiles
p.21
CopymissingAndroidfiles
copyallfilesfrom<qt_install_path>/src/android/java/to<your_src>/android/folder
makesureyour.profilehasANDROID_PACKAGE_SOURCE_DIRANDROID_PACKAGE_SOURCE_DIRpropertyset
ANDROID_PACKAGE_SOURCE_DIR=$$PWD/android
UnderstandingAndroidfiles
p.22
MeaningoftheAndroidfiles
AndroidManifest.xml-isthesamefilecopiedbyQtCreatorwhenyouclickonCreateCreateAndroidManifest.xmlAndroidManifest.xmlbutton.
version.xml-isusedbyQtCreatortoupdateyourexistingAndroidfiles
res/values/libs.xml-thisfilecontainstheinformationabouttheneededlibs
res/values(-*)/strings.xml-thesefilescontainthe(translated)stringsneededbyJavapart
src/org/kde/necessitas/ministro/*.aidl-thesefilesareusedtoconnecttoMinistroservice
src/org/qtproject/qt5/android/bindings/QtApplication.java-thisclasscontainsallthelogicneededtoforwardalltheactivitycallstotheactivitydelegate
src/org/qtproject/qt5/android/bindings/QtActivity.java-thisistheapplicationactivityclass,itcontainsthelogictoconnecttoMinistroortounpackthebundledQtlibs.Italsoforwardsallcallstotheactivitydelegate.
UnderstandingAndroidfiles
p.23
SDCardNotificationsExample
UsinganexternalIDEforJavapart
UsinganexternalIDEforJavapart
p.24
UsinganexternalIDEtoextendtheJavapart
Sadly,theJavasupportinQtCreatorisclosetozero,soweneedtouseanexternalIDEtoeasilyextendtheJavapart.AndroidprovidestwopowerfulIDEs:
Eclipse
AndroidStudio(thisisverygoodandstableeventhoughitislabeledasbeta).
UsinganexternalIDEforJavapart
p.25
Eclipseusers
IfyouarenotusingADTbundle,thenmakesureyouhaveallADTpluginsinstalledinEclipse.
UsinganexternalIDEforJavapart
p.26
UsinganexternalIDE
TheexternalIDEwillbeusedto:
importexistingAndroidfiles
createandeditJavafiles
debugJavapart
TheexternalIDEwillNOTNOTbeusedtoruntheapplication,westillneedQtCreatorforthatjob!
UsinganexternalIDEforJavapart
p.27
Thispresentationwillteachyouonlytheverybasicusageofbothofthem.Ifyouwanttolearnmorepleasechecktheirowndocumentation.Alsoit'suptoyouwhichoneyouchoosetouse.
UsinganexternalIDEforJavapart
p.28
UsinganexternalIDEforJavapart
ImportexistingAndroidfilesinEclipse
ImportexistingAndroidfilesinEclipse
p.29
WARNING
BeforeyoustarttoimporttheprojectmakesuremakesuretheBuildAutomaticallyBuildAutomaticallyfeatureisturnedOFFisturnedOFF!Otherwisewhenyoustarttobuildyourprojectyou'llgetlotsofmysteriousbuildproblems.UncheckProjectProject->BuildAutomaticallyBuildAutomatically
ImportexistingAndroidfilesinEclipse
p.30
ImportinginEclipse
FileFile->NewNew->Project...Project...
ImportexistingAndroidfilesinEclipse
p.31
ImportinginEclipse
ChooseAndroidProjectfromExistingCodeAndroidProjectfromExistingCodewizard
ImportexistingAndroidfilesinEclipse
p.32
ImportinginEclipse
SelecttherootfolderofyourAndroidfiles(itshouldbeyour_qt_src/androidyour_qt_src/android)andpressFinishFinishbutton.
ImportexistingAndroidfilesinEclipse
p.33
UsinganexternalIDEforJavapart
ImportexistingAndroidfilesinAndroidStudio
ImportexistingAndroidfilesinAndroidStudio
p.34
ImportinginAndroidStudio
FileFile->Importproject...Importproject... doesallthemagic,youjustneedtoselecttherootfolderofyourAndroidfiles(itshouldbeyour_qt_src/androidyour_qt_src/android)andcontinuethewizarduntilitisfinished.
ImportexistingAndroidfilesinAndroidStudio
p.35
UsinganexternalIDEforJavapart
DebuggingJavapart
DebuggingJavapart
p.36
DebugwithEclipse
setthebreakpoints
switchtoDDMSperspective(WindowWindow->OpenperspectiveOpenperspective->DDMSDDMS)
starttheapplication(usingQtCreator)
waitforapplicationtostart
selecttheapplication
clickthedebugicon
DebuggingJavapart
p.37
DebugwithAndroidStudio
setthebreakpoints
attachdebugger(RunRun->AttachdebuggertoAndroidprocessAttachdebuggertoAndroidprocess)
starttheapplication(usingQtCreator)
waitforapplicationtostart
selecttheapplication
clicktheOKOKbutton
DebuggingJavapart
p.38
Debuggingtipsandtricks
Theproblemcomeswhenweneedtostartthedebuggingveryearly.Todothatwearegoingtousetheoldfashionedsleeptrick.ThisishowourcustomcustomonCreateonCreatefunctionlooks:
1 @Override2 publicvoidonCreate(BundlesavedInstanceState)3 {4 try{5 Thread.sleep(10000);6 }catch(InterruptedExceptione){7 e.printStackTrace();8 }9 ....10 }
DebuggingJavapart
p.39
Architecturediagram
SDCardNotificationsExample
p.40
SDCardNotificationsExample
ExtendingJavapart
ExtendingJavapart
p.41
Extending,notchanging
Youshouldnotchangeanyoftheexisting.javafiles,insteadyoushouldextendthem
ThereisnoneedtoextendtheQtApplicationapplicationclass
YoushouldextendonlytheQtActivityclass.TheActivityobjectisveryimportant,you'llneedittoinstantiatemostoftheAndroidclasses
Demoandroid/SD_Card_NotificationsDemoandroid/SD_Card_Notifications
ExtendingJavapart
p.42
ExtendingQtActivity
1 //src/com/kdab/training/MyActivity.java2 packagecom.kdab.training;34 importandroid.os.Bundle;5 importorg.qtproject.qt5.android.bindings.QtActivity;67 publicclassMyActivityextendsQtActivity8 {9 //we'llneeditinBroadcastReceiver10 publicstaticMyActivitys_activity=null;1112 //everytimeyouoverrideamethod,alwaysmakesureyou13 //thencallsupermethodaswell14 @Override15 publicvoidonCreate(BundlesavedInstanceState)16 {17 s_activity=this;18 super.onCreate(savedInstanceState);19 }2021 @Override22 protectedvoidonDestroy()23 {24 super.onDestroy();25 s_activity=null;26 }27 }
ExtendingJavapart
p.43
ExtendingJavapart
ChangedefaultactivitytoAndroidManifest.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 ...>;
ExtendingJavapart
p.44
NativeFunctions.javapart1
1 publicclassNativeFunctions{2 //declarethenativefunctions3 //thesefunctionswillbecalledbytheBroadcastReceiverobject4 //whenitreceivesanewnotification5 publicstaticnativevoidonReceiveNativeMounted();6 publicstaticnativevoidonReceiveNativeUnmounted();78 //thisstaticmethodiscalledbyC/C++toregistertheBroadcastReceiver.(step1)9 publicstaticvoidregisterBroadcastReceiver(){10 if(MyActivity.s_activity!=null){11 //QtisrunningonadifferentthreadthanAndroid.(step2)12 //InordertoregisterthereceiverweneedtoexecuteitintheUIthread13 MyActivity.s_activity.runOnUiThread(newRegisterReceiverRunnable());14 }15 }16 }
ExtendingJavapart
p.45
NativeFunctions.javapart2
1 classRegisterReceiverRunnableimplementsRunnable2 {3 //Step34 //thismethodiscalledonAndroidUiThread5 @Override6 publicvoidrun(){7 IntentFilterfilter=newIntentFilter();8 filter.addAction(Intent.ACTION_MEDIA_MOUNTED);9 filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);10 filter.addDataScheme("file");11 //thismethodmustbecalledonAndroidUiThread12 MyActivity.s_activity.registerReceiver(newSDCardReceiver(),filter);13 }14 }
ExtendingJavapart
p.46
NativeFunctions.javapart3
1 classSDCardReceiverextendsBroadcastReceiver{2 @Override3 publicvoidonReceive(Contextcontext,Intentintent){4 //Step45 //callthenativemethodwhenitreceivesanewnotification6 if(intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED))7 NativeFunctions.onReceiveNativeMounted();8 elseif(intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED))9 NativeFunctions.onReceiveNativeUnmounted();10 }11 }
ExtendingJavapart
p.47
Architecturediagram,Javapart
ExtendingJavapart
p.48
SDCardNotificationsExample
ExtendingC/C++part
ExtendingC/C++part
p.49
main.cpp
1 #include"mainwindow.h"2 #include<QApplication>3 #include<QAndroidJniObject>45 intmain(intargc,char*argv[])6 {7 QApplicationa(argc,argv);89 //Step1.10 //callregisterBroadcastReceivertoregisterthebroadcastreceiver11 QAndroidJniObject::callStaticMethod<void>("com/kdab/training/NativeFunctions"12 ,"registerBroadcastReceiver"13 ,"()V");1415 MainWindow::instance().show();16 returna.exec();17 }
ExtendingC/C++part
p.50
mainwindow.h
1 classMainWindow:publicQMainWindow2 {3 Q_OBJECT45 public:6 staticMainWindow&instance(QWidget*parent=0);78 publicslots:9 voidonReceiveMounted();10 voidonReceiveUnmounted();1112 private:13 explicitMainWindow(QWidget*parent=0);14 ~MainWindow();1516 private:17 Ui::MainWindow*ui;18 };
ExtendingC/C++part
p.51
mainwindow.cpp
1 MainWindow&MainWindow::instance(QWidget*parent)2 {3 staticMainWindowmainWindow(parent);4 returnmainWindow;5 }67 //Step68 //CallbackinQtthread9 voidMainWindow::onReceiveMounted()10 {11 ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_MOUNTED"));12 }1314 voidMainWindow::onReceiveUnmounted()15 {16 ui->plainTextEdit->appendPlainText(QLatin1String("MEDIA_UNMOUNTED"));17 }
ExtendingC/C++part
p.52
native.cpppart1
1 //Step4.callbackfromjavainAndroidthread2 //defineournativemethods3 staticvoidonReceiveNativeMounted(JNIEnv*/*env*/,jobject/*obj*/)4 {5 //Step5.DelegatethecalltoQtthread.6 QMetaObject::invokeMethod(&MainWindow::instance(),"onReceiveMounted"7 ,Qt::BlockingQueuedConnection);8 }910 staticvoidonReceiveNativeUnmounted(JNIEnv*/*env*/,jobject/*obj*/)11 {12 //Step5.DelegatethecalltoQtthread.13 QMetaObject::invokeMethod(&MainWindow::instance(),"onReceiveUnmounted"14 ,Qt::BlockingQueuedConnection);15 }
ExtendingC/C++part
p.53
native.cpppart2
1 //createavectorwithallourJNINativeMethod(s)2 staticJNINativeMethodmethods[]={3 {"onReceiveNativeMounted","()V",(void*)onReceiveNativeMounted},4 {"onReceiveNativeUnmounted","()V",(void*)onReceiveNativeUnmounted},5 };67 //thismethodiscalledautomaticallybyJavaafterthe.sofileisloaded8 JNIEXPORTjintJNI_OnLoad(JavaVM*vm,void*/*reserved*/)9 {10 JNIEnv*env;//gettheJNIEnvpointer.11 if(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK)12 returnJNI_ERR;1314 //searchforJavaclasswhichdeclaresthenativemethods15 jclassjavaClass=env->FindClass("com/kdab/training/NativeFunctions");16 if(!javaClass)17 returnJNI_ERR;1819 //registerournativemethods20 if(env->RegisterNatives(javaClass,methods,21 sizeof(methods)/sizeof(methods[0]))<0){22 returnJNI_ERR;23 }24 returnJNI_VERSION_1_6;25 }
ExtendingC/C++part
p.54
Architecturediagram,QtJNIpart
ExtendingC/C++part
p.55
Architecturediagram,summary
ExtendingC/C++part
p.56
Questions?
Thankyouforyourtime!
Contactus:
http://www.kdab.comhttp://www.kdab.com
[email protected]@kdab.com
[email protected]@kdab.com
[email protected]@kdab.com
[email protected]@kdab.com