با اینکه کیوت (Qt) کتابخانه‌های متنوع و فراوانی برای اندروید دارد، با این وجود ممکنست برای استفاده از بعضی امکانات اندروید نیاز به فراخوانی کتابخانه‌های جاوا داشته باشیم. در این نوشته می‌خواهم روش فراخوانی کد جاوا (Java) را در C++\Qt آموزش دهم. برای این منظور یک کتابخانه جاوا ایجاد می‌کنیم و آن را در یک برنامه که با C++\Qt می‌نویسیم بکار می‌بریم.

ارتباط جاوا و کیوت

ایجاد کتابخانه جاوا

برای ایجاد یک کتابخانه ساده جاوا برای اندروید می‌توانیم از اندروید استودیو استفاده کنیم. از آنجا که در نوشته «ساخت کتابخانه جاوا در اندروید استودیو» ایجاد کتابخانه را در اندروید استودیو آموزش داده‌ام آن را در اینجا تکرار نمی‌کنم. و فقط از کتابخانه آن، در این آموزش استفاده می‌کنم.

استفاده از کتابخانه جاوا در C++\Qt

ابتدا یک پروژه جدید در Qt Creator ایجاد کنید. پروژه را برای اندروید تنظیم نمایید. برای آشنایی با ایجاد پروژه برای اندروید نوشته‌های «اولین برنامه با کیوت برای اندروید» و «برنامه نویسی اندروید: تنظیم، امضا و انتشار برنامه» را مطالعه کنید. فایل کتابخانه را (در مثال ما testlibrary-debug.aar) به مسیر android/libs در پروژه اضافه کنید. اگر libs در پوشه android نبود آن را ایجاد کنید.

یک کلاس جدید بنام JavaConnect به پروژه اضافه کنید. از آنجا که پروژه مثال ما از Qml استفاده می‌کند و این کلاس باید در Qml در دسترس باشد، از QObject مشتق می شود. برای آشنایی با فراخوانی کد ++C در Qml به نوشته «ارتباط Qml و ++C» مراجعه کنید.

کد کلاس JavaConnect به صورت زیر خواهد بود:

#ifndef JAVACONNECT_H
#define JAVACONNECT_H

#include <QObject>
#include <QJniEnvironment>
#include <QJniObject>

class JavaConnect : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString nativeMsg READ nativeMsg WRITE setNativeMsg NOTIFY nativeMsgChanged FINAL)
public:
    explicit JavaConnect(QObject *parent = nullptr);
    static JavaConnect* instance();
    QString nativeMsg();
    void setNativeMsg(QString msg);
    Q_INVOKABLE QString getJavaMsg();
    static void runFromJava(JNIEnv *env, jobject *obj, jstring msg);

signals:
    void nativeMsgChanged(QString msg);

private:
    QJniEnvironment j_env;
    QJniObject j_object;
    jclass j_class;

    QString native_msg = "BLANK";
    inline static JavaConnect* m_instance = nullptr;
};

#endif // JAVACONNECT_H
#include "javaconnect.h"
#include <QCoreApplication>

JavaConnect::JavaConnect(QObject *parent): QObject{parent} {
    j_class = j_env.findClass("ir/samiyacode/testlibrary/TestClass");
    QJniObject jStr = QJniObject::fromString("FIRST_MESSAGE");
    j_object = QJniObject(j_class,
                          "(Landroid/content/Context;Ljava/lang/String;)V",
                          QNativeInterface::QAndroidApplication::context(),
                          jStr.object<jstring>());
    JNINativeMethod methods[] = {
        {"runFromCpp", "(Ljava/lang/String;)V", reinterpret_cast<void*>(runFromJava)}
    };
    jclass objectClass = j_env->GetObjectClass(j_object.object<jobject>());
    j_env.registerNativeMethods(objectClass, methods, sizeof(methods)/sizeof(methods[0]));
    j_env->DeleteLocalRef(objectClass);
    m_instance = this;
}
JavaConnect *JavaConnect::instance() { return m_instance; }
QString JavaConnect::nativeMsg() { return native_msg; }
void JavaConnect::setNativeMsg(QString msg) {
    native_msg = msg;
    emit nativeMsgChanged(native_msg);
}
QString JavaConnect::getJavaMsg() {
    if (j_object.isValid()) {
        QJniObject resStr = j_object.callObjectMethod("getInfo", "()Ljava/lang/String;");
        return resStr.toString();
    }
    return "JavaError";
}
void JavaConnect::runFromJava(JNIEnv *env, jobject *obj, jstring msg) {
    jboolean isCopy;
    const char *convertedValue = env->GetStringUTFChars(msg, &isCopy);
    QString msgStr = QString::fromStdString(convertedValue);
    env->ReleaseStringUTFChars(msg, convertedValue);
    JavaConnect::instance()->setNativeMsg(msgStr);
}

متد‌های کلاس JavaConnect

در ادامه متدهایی که در کلاس JavaConnect تعریف کرده‌ایم را بطور مختصر توضیح می‌دهم.

JavaConnect(QObject *parent = nullptr)

سازنده کلاس JavaConnect که در آن ارتباط با کتابخانه را ایجاد می‌کنیم. ابتدا کلاس مورد نظر در کتابخانه TestClass را در متغیر class_j ذخیره می‌کنیم. سپس یک آبجکت از class_j ایجاد کرده و در object_j ذخیره می‌کنیم. کلاس TestClass در کتابخانه TestLibrary دو پارامتر از نوع Context و jstring می‌گیرد که در زمان ایجاد آبجکت به آن ارسال می‌کنیم. رشته “V(;Landroid/content/Context;Ljava/lang/String)” امضا متد سازنده کلاس در جاوا است که یک Context و یک رشته String را دریافت کرده و مقداری بر نمی‌گرداند. برای آشنایی با شیوه ایجاد امضای متدها به اسناد JNI از Oracle و QjniObject از Qt مراجعه کنید.

پس از آن متد هایی که قرار است کد Java به آنها دسترسی داشته باشد را در آرایه methods قرار می‌دهیم. در مثال ما اشاره‌گری به متد runFromJava به متد runFromCpp ،native در کلاس testlibrary تعریف شده است. برای آشنایی با جزئیات به اسناد Qt مراجعه کنید. در پایان لیست متد ها را در موتور جاوا رجیستر (Register) می‌کنیم.

()static JavaConnect* instance

این متد اشاره گری به همین (this) آبجکت بر می‌گرداند. از آنجا که متد هایnative باید استاتیک تعریف شوند و این متدهای استاتیک امکان فراخوانی متدهای غیر استاتیک را ندارند ما از این متد استفاده می‌کنیم تا اشاره گری به آبجکت ایجاد شده از کلاس پیدا کنیم. سپس متدهای آن را فراخوانی می‌کنیم. در مثال بالا، ما نیاز داریم تا مقدار nativeMsg را به روز کنیم که آن هم رابط کاربر را بروز می کند.

()Q_INVOKABLE QString getJavaMsg

این متد که از Qml فراخوانی می‌شود متد getInfo را از کلاس TestClass کد جاوا فراخوانی می‌کند و مقدار برگشتی را که یک رشته jstring است به Qml بر می‌گرداند.

static void runFromJava(JNIEnv *env, jobject *obj, jstring msg)

این متد قرار است از جاوا فراخوانی شود. دو پارامتر اول آن را موتور جاوا ارسال می‌کند و پارامترهای بعدی را ما تغریف می‌کنیم. معادل این متد در بخش جاوا متد runFromCpp است که درکلاس TestClass تعریف کردیم. در این متد ابتدا رشته jstring msg را به Qstring تبدیل می‌کنیم. سپس از متد instance استفاده کرده و مقدار nativeMsg را بروز می‌کنیم. (از آنجا که این متد استاتیک است نیاز داریم از instance استفاده کنیم)

متدهای nativeMsg و  setNativeMsg مربوط بهQ_PROPERTY اند که صفت nativeMsg را تعریف می‌کند. این صفت به یک Text در Main.qml متصل bind شده است. با هر تغییر nativeMsg مقدار Text در Qml بروزرسانی می‌شود. (به نوشته «ارتباط Qml با ++C» مراجعه کنید)

کد پروژه مثال بالا قابل دریافت از اینجا ست. می‌توانید آن را کامپایل کرده و تست نمایید.

مبحث JNI و استفاده از آن در ++C و Qt بسیار گسترده‌تر از از مثال بالاست. در اینجا ما یک کتابخانه ساده را استفاده کردیم. ولی می‌توان از کتابخانه‌‌های دیگر هم به راحتی استفاده کرد. مانند کتابخانه‌های پرداخت درون برنامه‌ای مایکت و کافه‌بازار و یا شرکت‌های تبلیغات درون برنامه‌ای.

فراخوانی کتابخانه جاوا در C++\Qt
Tagged on:         

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *