📜 ⬆️ ⬇️

Hello android from qt

This article is about how to make friends languages ​​Java and C ++ in one application for the Android operating system.

A huge amount of code is written in C ++. I would like to somehow use this code in my applications, but for some reason the process of using me caused some discomfort. Most likely, this was trivially related to the fact that the basis of my working environment is Android Studio, in which working with native code is, let's say, not the best. But the thought of developing the application comfortably with the native part never left me. Therefore, I decided to try to cross all the power of the Qt library and an application written in the native Android language - Java.

At the moment, the Qt ecosystem allows you to write an application completely with the tools of this framework, even more - to compile it for a whole set of operating systems, among which is Android. But moving this way it is very difficult to develop an interface that will look and behave accordingly to the platform guides. And it becomes impossible to use libraries written in Java for the appearance and behavior of the application. That is why I would like to try a development model, in which all business logic was written in C ++, and the user interface - using the target platform. In the future, this will provide a simple transfer of the application to other platforms with an appearance typical of the target system and functionality that has already been tested and deferred.

So, further you will find instructions on how to write an application that will have a part written in Qt and a part in Java.
')
To work, we need the Qt library and ide Qt Creator, configured to work with Android projects (this is quite simple, you can read about it in detail here ), Android Studio , as well as Android ndk .

1. First you need to create an android project (let's call it QTtests) and set “penguin.in.flight.qttests” as the package name. In the next steps of the project creation wizard, you can choose any settings that are convenient for work. The main condition for following the instructions is to follow the package name.

Creating a new project in Android Studio
image

2. When the Android project is already there, you can forget about it for a while and go on to create the Qt part. The first step is to create (preferably at the root of the main project) a standard project of the type library. Give it the name cpp_lib. When creating a project, you need to select armeabi-v7 as the target platform.

Creating a library




3. We have a project template, now is the time to write our Hello Word on Qt.
cpp_lib.h

#ifndef CPP_LIB_H #define CPP_LIB_H #include "cpp_lib_global.h" #include <jni.h> #include <QString> class CPP_LIBSHARED_EXPORT Cpp_lib { public: Cpp_lib(); QString say(QString subject); }; #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello(JNIEnv *env, jobject obj); #ifdef __cplusplus } #endif #endif // CPP_LIB_H 

cpp_lib.cpp

 #include "cpp_lib.h" Cpp_lib::Cpp_lib() { } QString Cpp_lib::say(QString subject) { QString result = QString("Qt say \" %1 \"").arg(subject); return result; } JNIEXPORT jstring JNICALL Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello(JNIEnv *env, jobject obj){ Cpp_lib *lib = new Cpp_lib(); return env->NewStringUTF(lib->say("Hello android!!!").toLatin1().data()); } 

As you can see by the code, the application will be very simple. It contains two functions - say and Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello. The first is simple and straightforward. They take an object as input, which is a text string in the framework, and returns a new string. Much more interesting is the following function. In it, JNIEXPORT and JNICALL mean that it can be called from code written in Java. As a return type, it has a jstring (which is available to us thanks to this #include <jni.h>). This type is a String type in Java.

In conclusion, analyze the method arguments. * env is a reference to the structure that contains the methods necessary for working with java entities, and obj is the object that called the method.

3. After the code is written, you need to make sure that our library is correctly assembled and we can use it in our projects. To do this, we need to remove this line TEMPLATE = lib in the project settings file (in our case, it is cpp_lib.pro). If this is not done then during assembly we will get an error like this:

Internal Error: Could not find .pro file.
Error while building / deploying project cpp_lib (kit: Android for armeabi-v7a (GCC 4.9, Qt 5.4.1))
When executing step "Build Android APK"

4. Next, you need to make Qt Creator compile the library at the following address: {path_to_project} / QTtests / app / src / main / qt_output:

Setting the build folder



5. In the same place where we indicated the place of assembly, you need to check the application’s build settings. Namely - the target assembly platform. You need to choose the latter (I have this android 22). Next, tick the Build Qt libraries in APK. This is necessary for the build system to generate all the necessary libraries. You also need to tick the Use Gradle box in order for the build system to generate the project of the type you need.

Screenshot of settings



6. Now we can build the library and get all the necessary files.

Build a project



7. On this work with the native library ends. We are coming back to Android studio. The first thing to do is edit the assembly file to include the Qt library. To do this, add the following lines in the /QT_tests/app/build.gradle file:

  sourceSets { main { java.srcDirs += ['/src/main/java'] res.srcDirs += ['src/main/res','src/main/qt_output/android-build/res'] jniLibs.srcDirs += 'src/main/qt_output/android-build/libs' } } 

src / main / qt_output / android-build / res is needed in order to have access to the resources in which the build system has registered the names of the libraries on which the main Qt project depends. The src / main / qt_output / android-build / libs path points to the location of the library files themselves.

We also modify the dependency block as follows:

 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile files('src/main/qt_output/android-build/libs/QtAndroid-bundled.jar') compile files('src/main/qt_output/android-build/libs/QtAndroidAccessibility-bundled.jar') //  } 


8. Now we have a library meeting and they are even connected to the project. Next you need to connect them during the start of the program itself. For this we will use the following class:

 package penguin.in.flight.qttests.utils; import android.content.Context; import android.util.Log; import penguin.in.flight.qttests.R; /** * Created on 11.04.15. */ public class JavaNatives { public native static String sayHello(); public static void init(Context context) { load(context, R.array.bundled_in_assets); load(context, R.array.qt_libs); System.loadLibrary("cpp_lib"); } static void load(Context context, int arrayResourceId) { String[] libsToLoad = context.getResources().getStringArray(arrayResourceId); for (String lib : libsToLoad) { if (lib.indexOf('/') > -1) { lib = lib.substring(lib.indexOf('/')); } if (lib.indexOf("lib") == 0) { lib = lib.substring(3); } if (lib.endsWith(".so")) { lib = lib.substring(0, lib.length() - 3); } Log.i(JavaNatives.class.getSimpleName(), "loading " + lib); try { System.loadLibrary(lib); } catch (Throwable e) { Log.i(JavaNatives.class.getSimpleName(), "failed to load " + lib + " " + e); e.printStackTrace(); } Log.i(JavaNatives.class.getSimpleName(), "Successfully loaded " + lib); } } } 

As you can see from the code, the class is in the package penguin.in.flight.qttests.utils and is called JavaNatives. This is important in order to provide a connection between the code that we wrote in the native part. To understand how, let's recall a method called Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello. Its name consists of the prefix Java_, then comes the name of the package, where the names of sub packets are separated by the symbol "_" (penguin_in_flight_qttests_utils). Next comes the class name and, finally, the name of the method. More information about the naming of methods in native code can be found here .

9. To use the class we wrote in onCreate in the start-up, you need to insert this line:

 JavaNatives.init(this); 

10. Now it remains only to use what we have written. To do this, add a button and in the processing of clicking on it we insert the following code:

 AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setMessage(JavaNatives.sayHello()); dialog.setPositiveButton("Ok", null); dialog.show(); 

On this, instructions for using the native part, written in Qt in Android applications, are over.

Thank you all for your attention, the entire sample code is available on github.com .

Source: https://habr.com/ru/post/255597/


All Articles