首页/文章/ 详情

如何将QtActivity添加至Android Studio项目中

8月前浏览1716
本文翻译自:How to add QtActivity to an Android Studio Project
原文作者:Qt Group Qt for Android Automotive 工程师 Nikunj Arora
校审:Sam Wang
Qt Design Studio,QtCreator和Squish等出色工具可用于设计、开发和测试Android应用。然而,有时我们可能需要将Qt框架的某些功能集成到已有的Android Studio项目中。本篇博文将演示如何将Qt for Android项目集成到Android Studio项目中。

Qt for Android主要用于在单个Activity或Service中使用Qt。因此,其导航功能与常规Android应用的实现并不完全相同。另外,由于Android系统的特性,在使用公共Android SDK时,无法将QtActivity嵌入到另一个Activity中。

构建并运行演示项目

我们将制作简单的应用来演示如何在AndroidStudio项目中使用QtActivity。在这个应用中,我们从Android端向Qt发送消息,根据Android上的按钮来更改QML矩形的颜色。

我们在Qt端有一个矩形,根据Android Activity来改变其颜色。

在Android端,我们只有两个按钮,可以将矩形颜色设置为绿色或青色。

构建面向Android平台的Qt项目。您可以点击此处获取使用 Qt Creator构建的说明。这将在Qt项目的构建目录中创建一个android-build文件夹。这个文件夹是独立的Android项目,您可以在Android Studio中打开并编辑。在我们的演示项目中,要将部分文件复 制到AndroidStudio项目中。

下文中<Android Project>指由Android Studio创建的包含Android项目的文件夹。

<Qt Build>指使用QtCreator构建面向Android的项目时生成的android-build文件夹。常见路径如:/QtProjects/build-ProjectName-Qt_version-DebugOrRelease/android-build。

1. 复 制<QtBuild>/libs中的文件到<AndroidProject>/app/libs

2. 复 制<QtBuild>/assets/文件夹到<Android Project>/app/

从<Qt Build>中需要复 制的文件夹如下图所示

3. 复 制<QtBuild>/res/values/libs.xml 到<AndroidProject>/app/src/main/res/values

4. 在<QtBuild>/gradle.properties中复 制qtAndroidDir属性到<Android Project>/gradle.properties

在<Qt Build>/gradle.properties中需要复 制的属性如下

    ...qtAndroidDir=/home/user/Qt/6.4.2/android_arm64_v8a/src/android/java...
    5. 从<Qt Build>/AndroidManifest.xml 中复 制QtActivity的标签,将其粘贴到<Android Project>的AndroidManifest.xml中,并删除MAIN Intent过滤器。同时复 制相关权限。
    同时将属性android:launchMode从singleTop更改为singleInstance。这有助于在Android Activity和QtActivity之间进行导航。
    下图显示了<Android Project>的AndroidManifest.xml中的activity 标签(节选)
        
      <activity    android:name="org.qtproject.qt.android.bindings.QtActivity"    android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"    android:label="appAndroidTest"    android:launchMode="singleInstance"    android:screenOrientation="unspecified"    android:exported="true">    <meta-data        android:name="android.app.lib_name"        android:value="appAndroidTest" />    <meta-data      android:name="android.app.arguments"        android:value="" />    <meta-data      android:name="android.app.extract_android_style"        android:value="minimal" /></activity>  

      6. 在<Android Project>/app/build.gradle中添加以下代码

          
        ...dependencies {  implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])}...android {...// Extract native libraries from the APKpackagingOptions.jniLibs.useLegacyPackaging true  sourceSets {    main {      java.srcDirs += [qtAndroidDir + '/src']      aidl.srcDirs += [qtAndroidDir + '/src']      resources.srcDirs = ['resources']      assets.srcDirs = ['assets']      jniLibs.srcDirs = ['libs']    }  }}  

        实现原理

        建立Qt和Android之间的通信

        由于我们将本地库复 制到了AndroidStudio项目中,因此我们可以使用JavaNative Interface (JNI)在Java和C++之间进行通信。幸运的是,Qt提供了许多辅助函数来处理这个问题。对于通信,我们将使用JSON字符串,因为它们在QML中是本地可访问的(QML具有JavaScript引擎)。

        注意:每次修改Qt项目时,您需要再次从<Qt Build>复 制资源和库文件夹到<Android Project>(前一节中的步骤1和2)。

        Qt端

        在Qt端,我们创建了名为JniMessenger的新类。以下是头文件。

            
          class JniMessenger : public QObject{    Q_OBJECT    QML_NAMED_ELEMENT(JniMessenger)    QML_SINGLETONprivate:    explicit JniMessenger(QObject *parent = nullptr);public:    Q_INVOKABLE void sendMessageToJava(const QString &message);    static JniMessenger *instance();    static JniMessenger *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);signals:    void messageReceivedFromJava(QString message);};  

          该类作为单例暴露给QML。每当有来自Java的消息时,就会信号被触发。因为我们将该类设置为单例,所以需要将构造函数设为私有,并添加一个”create”函数。在QML中实例化此类时,将调用此函数。请在https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON获取有关向QML暴露单例类的更多信息

              
            JniMessenger *JniMessenger::instance(){    static JniMessenger instance;    return &instance;}JniMessenger *JniMessenger::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine){    Q_UNUSED(qmlEngine)    JniMessenger *singletonInstance = JniMessenger::instance();    // The engine must have the same thread affinity as the singleton.    Q_ASSERT(jsEngine->thread() == singletonInstance->thread());    return singletonInstance;}  

            将此函数添加到类中,用于向Java发送消息。此函数调用Java端的静态方法,我们稍后将创建该方法。使用新的JNI语法“Q_DECLARE_JNI_CLASS(javaMessageHandlerClass,com/example/androidapp/MainActivity)”定义了javaMessageHandlerClass。您可以阅读Volker的https://www.qt.io/blog/unstringifying-android-development-with-qt-6.4这篇博文来了解关于新JNI语法的更多内容。该语法用预定义的类和类型签名代替JNI方法调用中使用的字符串签名。

                
              void JniMessenger::sendMessageToJava(const QString &message){    QJniObject::callStaticMethod<void, jstring>(                QtJniTypes::className<QtJniTypes::javaMessageHandlerClass>(),                "receiveMessageFromQt",                QJniObject::fromString(message).object<jstring>());}  

              接下来在类的外部添加以下代码。sendMessageToQt函数是一个在Java中声明并在此处定义的本地方法。它将消息从Java发送到Qt。JNI_OnLoad函数由JNI调用,您应该在这里注册所有本地方法。通过使用新的JNI语法,如代码所示,本地方法的注册变得更容易。

                  
                void sendMessageToQt(JNIEnv *env, jclass cls, jstring message){    Q_UNUSED(cls)    QString string =  env->GetStringUTFChars(message, nullptr);    emit JniMessenger::instance()->messageReceivedFromJava(string);}Q_DECLARE_JNI_NATIVE_METHOD(sendMessageToQt)JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved){    Q_UNUSED(vm)    Q_UNUSED(reserved)    static bool initialized = false;    if (initialized)        return JNI_VERSION_1_6;    initialized = true;    // get the JNIEnv pointer.    QJniEnvironment env;    if (!env.isValid())        return JNI_ERR;    const jclass javaClass = env.findClass(                QtJniTypes::className<QtJniTypes::javaMessageHandlerClass>());        if (!javaClass)            return JNI_ERR;    static JNINativeMethod methods[] = { Q_JNI_NATIVE_METHOD(sendMessageToQt) };    // register our native methods    if (!env.registerNativeMethods(javaClass, methods, std::size(methods)))        return JNI_ERR;    return JNI_VERSION_1_6;}  

                在main函数中添加以下代码:

                    
                  QTimer::singleShot(0, [argc, argv]() {  if (argc > 1) {      emit JniMessenger::instance()->messageReceivedFromJava(argv[1]);    }});  

                  将QtActivity作为第二个Activity加载的一个问题是,本地库还没有加载完毕,所以我们还不能立即调用本地方法。因此,我们使用AndroidIntents在第一次传递数据时进行操作,然后再继续执行常规的本地函数调用。Android端给出了正确地将Intent数据发送到Qt的方法。0毫秒延迟的QTimer::singleShot使得发出的信号进入主事件循环,确保 QML能够及时捕获信号。

                  最后,在QML侧,为按钮添加功能并建立连接以便监听信号。当按钮被点击时,它会向Java发送一个包含单一属性的JSON字符串:navigate:back。这个信号通知Java再次加载第一个Activity的开始部分。当我们从Java接收到消息时,我们会寻找color属性,并将矩形的颜色设定为该属性值。

                    Window {    ...    Rectangle {        focus: true        Keys.onReleased: function(event) {            if (event.key === Qt.Key_Back) {                console.log("Back key pressed");                event.accepted = true;                JniMessenger.sendMessageToJava(JSON.stringify(                   {                        navigate: "back"                   }                ));            }        }    }    Connections {        target: JniMessenger        function onMessageReceivedFromJava(message) {            const data = JSON.parse(message);            for (let key in data) {                if (data.hasOwnProperty(key)) {                    console.log("Setting " + key + " to " + data[key]);                    if (data.color) {                        rectangle.color = data.color;                    }                }            }        }    }    ...}

                    Android端

                    在Android端,我们添加了新的静态方法以便从Qt端调用。MessageFromQtListener是用于添加来自Qt消息侦听器的接口。我们还有一个已经在Qt端定义的本地方法 sendMessageToQt。以下是MainActivity 类:
                        
                      public class MainActivity extends AppCompatActivity {    private interface MessageFromQtListener {        public void onMessage(String message);    };    private static final String TAG = "Android/MainActivity";    private static boolean firstTime = true;    private JsonHandler jsonHandler;    private static MessageFromQtListener m_messageListener;    public static native void sendMessageToQt(String message);    public static void setOnMessageFromQtListener(MessageFromQtListener listener) {        m_messageListener = listener;    }    public static void receiveMessageFromQt(String message) {        Log.d(TAG, "Message received from Qt: " + message);        if (m_messageListener != null) {            m_messageListener.onMessage(message);        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        jsonHandler = new JsonHandler();        MainActivity.setOnMessageFromQtListener(new MessageFromQtListener() {            @Override            public void onMessage(String message) {                try {                    JSONObject jsonObject = new JSONObject(message);                    Map<String, Object> json = JsonHandler.toMap(jsonObject);                    if (json.containsKey("navigate") &&                            Objects.equals((String) json.get("navigate"), "back")) {                        Intent intent = new Intent(getApplicationContext(), MainActivity.class);                        startActivity(intent);                    }                } catch (JSONException e) {                    Log.e(TAG, "Not valid JSON: " + message);                }            }        });    }    public void onBtnClicked(View view) {        String color = ((Button) view).getText().toString().toLowerCase();        Log.d(TAG, color);        Intent intent = new Intent(MainActivity.this, QtActivity.class);        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);        if (!firstTime) {            MainActivity.sendMessageToQt(jsonHandler.buildJson("color", color).toString());        } else {            intent.putExtra("applicationArguments",                    jsonHandler.buildJson("color", color).toString());            firstTime = false;        }        startActivity(intent);    }}  
                      onBtnClicked由XML调用,并向QML发送更新颜色的JSON字符串。如“Qt端”一节所解释的那样,在QtActivity加载之前,我们不能调用本地方法,因此我们使用m_qtLibsLoaded变量来判断。如果它的值为true,我们就使用常规的本地方法。如果它的值为false,我们就加载QtActivity并向其传递Intent数据。QtActivity在加载库时查找名为applicationArguments的意图字段,并将其作为环境变量传递给C++代码。以上这些可以在main函数中使用argv参
                      以上就是从Android Studio项目中运行QtActivity所需的所有操作。


                      深圳市优飞迪科技有限公司成立于2010年,是一家专注于产品开发平台解决方案与物联网技术开发的国家级高新技术企业。

                      十多年来,优飞迪科技在数字孪生、工业软件尤其仿真技术、物联网技术开发等领域积累了丰富的经验,并在这些领域拥有数十项独立自主的知识产权。同时,优飞迪科技也与国际和国内的主要头部工业软件厂商建立了战略合作关系,能够为客户提供完整的产品开发平台解决方案。

                      优飞迪科技技术团队实力雄厚,主要成员均来自于国内外顶尖学府、并在相关领域有丰富的工作经验,能为客户提供“全心U+端到端服务”。

                      来源:IFD优飞迪
                      ACT通信数字孪生Creator
                      著作权归作者所有,欢迎分享,未经许可,不得转载
                      首次发布时间:2023-09-02
                      最近编辑:8月前
                      优飞迪科技
                      赋能新仿真,创优新设计
                      获赞 301粉丝 248文章 345课程 4
                      点赞
                      收藏
                      未登录
                      还没有评论

                      课程
                      培训
                      服务
                      行家

                      VIP会员 学习 福利任务 兑换礼品
                      下载APP
                      联系我们
                      帮助与反馈