Pull to refresh

Hello android from qt

Reading time6 min
Views43K
Эта статья о том, как подружить языки Java и C++ в одном приложение для операционной системы Android.

На С++ написано огромное количество кода. Мне хотелось бы как-то использовать этот код в своих приложения, но почему-то у меня процесс использования вызывал некий дискомфорт. Скорей всего, это, банально, было связано с тем, что основой моей рабочей средой является Android Studio, в которой работа с нативным кодом, скажем так, не самая лучшая. Но вот мысль о том, чтобы комфортно разрабатывать приложение с нативной частью не оставляла меня никогда. Поэтому я решил попробовать скрестить всю мощь библиотеки Qt и приложение, написаное на родном для Android языке — Java.

На данный момент экосистема Qt разрешает писать приложение полностью средствами этого фреймворка, даже больше — компилировать его для целого набора операционных систем, среди которых находится и Android. Но двигаясь этим путем очень сложно разработать интерфейс, который будет выглядеть и вести себя соответственно гайдам платформ. Да и становится невозможным использовать библиотеки, написаные на Java для оформление внешнего вида и поведения приложения. Именно поэтому хотелось бы испробовать модель разработки, при которой вся бизнес логика писалась на С++, а пользовательский интерфейс — средствами целевой платформы. В дальнейшем это обеспечит простой перенос приложения на другие платформы с внешним видом, характерным для целевой системы и функционалом, который уже проверен и отложен.

Итак, дальше вас ожидает инструкция о том, как написать приложение, которое будет иметь часть, написаную на Qt, и часть на Java.

Для работы нам понадобится библиотека Qt и ide Qt Creator, настроенные для работы с Android-проектами (это сделать довольно просто, подробно об этом можно прочитать по ссылке), Android Studio, а так же Android ndk.

1. Для начала нужно создать андроид проект (назовем его QTtests) и в качестве имени пакета зададим «penguin.in.flight.qttests». На следующих этапах мастера создания проектов можно выбирать любые, удобные для работы, настройки. Главным условием для того, чтобы можно было следовать дальнейшим инструкциям является соблюдение имени пакета.

Создание нового проекта в Android Studio
image

2. Когда Android проект уже есть, можно на время забыть о нем и перейти к созданию Qt части. Первым шагом будет создание (желательно в корне основного проекта) стандартного проекта типа библиотека. Дадим ему имя cpp_lib. При создании проекта в качестве целевой платформы нужно выбрать armeabi-v7.

Создание библиотеки




3. Шаблон проекта у нас есть, теперь самое время написать наш Hello Word на 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());
}

Как видно по коду, приложение у нас будет очень простое. Оно содержит две функции — say и Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello. Первая — простая и понятная. Они принимает на вход объект, который в фреймворка является текстовой строкой, и отдает новую строку. Намного интереснее следующая функция. В ней JNIEXPORT и JNICALL обозначаю то, что она может быть вызвана с кода, написаного на джава. В качестве возвращаемого типа она имеет jstring (который доступен нам благодаря этому инклуду #include <jni.h>). Этот тип является типом String в джава.

В завершение разберем аргументы метода. *env — это ссылка на структуру, которая содержит методы, необходимые для работы с сущностями джавы, а obj является обьектом, который вызвал метод.

3. После того, как код написан, нужно сделать так, чтобы наша библиотека корректно собиралась и мы могли использовать ее в своих проектах. Для этого нам нужно убрать вот эту строку TEMPLATE = lib в файле настроек проекта ( в нашем случае это cpp_lib.pro). Если этого не сделать то при сборке мы получим ошибку, вроде этой:

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

4. Дальше нужно сделать так, чтобы Qt Creator собирал библиотеку по таком адресу: {путь_к_проекту}/QTtests/app/src/main/qt_output:

Задание папки сборки



5. Там же, где мы указывали место сборки, нужно проверить настройки сборки приложения. А именно — целевую платформу сборки. Нужно выбрать последнюю (у меня это android 22). Далее поставить галочку напротив Build Qt libraries in APK. Это нужно для того, чтобы система сборки сгенерировала все необходимые библиотеки. Так же нужно поставить галочку напротив Use Gradle — для того, чтобы система сборки сгенерировала проект нужного нам типа.

Скриншот настроек



6. Теперь мы можем собрать библиотеку и получить все необходимые файлы.

Собрать проект



7. На этом работа с нативной библиотекой оканчивается. Мы возвращаемся назад в Android studio. Первое, что нужно сделать — отредактировать файл сборки, чтобы подключить Qt библиотеки. Для этого нужно добавить следующие строки в файле /QT_tests/app/build.gradle:

   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 нужно для того, чтобы иметь доступ к ресурсам, в которые система сборки прописала имена библиотек, от которых зависит основной Qt проект. Путь src/main/qt_output/android-build/libs указывает на место, где лежат сами файлы библиотеки.

Так же модифицируем блок зависимостей следующим образом:

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. Теперь у нас есть собрание библиотеки и они даже подключены к проекту. Далее нужно подключить их во время старта самой программы. Для этого будем использовать следующий класс:

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);
        }
    }

}

Как видно из кода, класс находится в пакете penguin.in.flight.qttests.utils и называется JavaNatives. Это важно для того, чтобы обеспечить связь между тем кодом, который мы написали в нативной части. Чтоб понять как, давайте вспомним метод с именем Java_penguin_in_flight_qttests_utils_JavaNatives_sayHello. Его имя состоит из префикса Java_, потом идет имя пакета, где имена саб пакетов разделены символом "_" (penguin_in_flight_qttests_utils). Дальше идет имя класса и, наконец, имя метода. Детальнее о именование методов в нативном кодом можно почитать здесь.

9. Чтобы использовать написаный нами класс в onCreate в стартовой активити, нужно вставить эту строку:

JavaNatives.init(this);

10. Теперь осталось только использовать то, что мы написали. Для этого добавим кнопку и в обработке нажатия на нее вставим следующий код:

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

На этом инструкции об использовании нативной части, написаной на Qt в Андроид приложениях, окончены.

Всем спасибо за внимание, весь код примера доступен на github.com.
Tags:
Hubs:
Total votes 18: ↑17 and ↓1+16
Comments17

Articles