您现在的位置是:网站首页> C/C++

Qt Android开发相关技术及问题收集

  • C/C++
  • 2024-12-15
  • 1080人已阅读
摘要

Qt Android开发相关技术及问题收集

###Qt编译Android时下载必要库或工具出错时,采用全局科学上网###


Qt Android开发资料收集

AndroidStudio网路环境配置及缓存目录

QT Android环境搭建

qt手机开发环境

Qt Android解决启动界面黑屏或白屏

当QT项目用AndroidStudio打开报配置出错时

QT 开发的Android在Android中常见错误处理

Qt for Android 启动短暂的黑屏或白屏问题如何解决

Qt for Android 调用原生Activity 三步走,开启android的潘多拉世界

QT适配Android端界面

QT开发Android 注意NDK版本

qt android 引用不带界面的aar包

qt 如何使用 aar

QT自带的Android类,通过派生修改行为

QT Android 调用JAVA代码

Qt on Android 添加SO库

Qt C++和Java相互调用

QT Android 调用JAVA代码

Qt for Android开发一些总结

Qt for Android程序沉浸式启动页面

Qt for Android 启动短暂的黑屏或白屏问题如何解决?

Android Studio打开QT的Android项目

QT开发Androd编译多个CPU程序

QT编译Android方法

QT 安卓程序如何后台运行保活?



Android SDK换成目录



QT Android环境搭建

点击下载QT Android环境搭建

Qt在线帮助文档

环境配置

帮助文档在F:\QtAll\Docs\Qt-5.12.3下可以直接查看下面目录下的html文件 

QT Android开发经验总结

pro中引入安卓拓展模块 QT += androidextras 。

pro中指定安卓打包目录 ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android 指定引入安卓特定目录比如程序图标、变量、颜色、java代码文件、jar库文件等。

AndroidManifest.xml 每个程序唯一的一个全局配置文件,里面xml格式的数据,标明支持的安卓版本、图标位置、横屏竖屏、权限等。这个文件是最关键的,如果没有这个文件则Qt会默认生成一个。

android/res/drawable-hdpi drawable-xxxhdpi 等目录下存放的是应用程序图标。

android/res/layout 目录下存放的布局文件。

android/res/values/libs.xml 存储的一些变量值。

android/libs 目录下存放的jar库文件。

android/src 目录下存放的java代码文件,可以是根据包名建立的一层层子目录,也可以直接在src目录下。

其他目录自行搜索安卓目录规范。

后面的说明统一用的android目录举例,其实你可以改成任意目录,比如你的代码目录下是xxoo存放的安卓相关的打包文件,你就写成 ANDROID_PACKAGE_SOURCE_DIR = $$PWD/xxoo 。

java类名必须和文件名完全一致,区分大小写。

java类必须在android/src目录下不然不会打包到apk文件,可以是子目录比如 android/src/com/qt 。

Qt代码中的QAndroidJniObject指定传入的java包名,必须严格和java文件package完全一致,不然程序执行到此处会因为找不到而崩溃。

android/scr/MainActivity.java 顶部 没有 package 则代码中必须是 QAndroidJniObject javaClass(“MainActivity”);

android/scr/MainActivity.java 顶部 package com.qandroid; 则代码中必须是 QAndroidJniObject javaClass(“com/qandroid/MainActivity”);

android/scr/com/example/MainActivity.java 顶部 package com.qandroid; 则代码中必须是 QAndroidJniObject javaClass(“com/qandroid/MainActivity”);

android/scr/com/example/MainActivity.java 顶部 package com.example.qandroid; 则代码中必须是 QAndroidJniObject javaClass(“com/qandroid/example/MainActivity”);

总之这个包名是和代码中的package后面一段吻合,而不是目录路径。为了统一管理方便查找文件,建议包名和目录路径一致。

Qt只能干Qt内部类的事情,做一些简单的UI交互还是非常方便,如果涉及到底层操作,还是需要熟悉java会如虎添翼,一般的做法就是写好java文件调试好,提供静态方法给Qt调用,这样通过QAndroidJniObject这个万能胶水可以做到Qt程序调用java中的函数并拿到执行结果,也可以接收java中的函数。

pro中通过 OTHER_FILES += android/AndroidManifest.xml OTHER_FILES += android/src/JniMessenger.java 引入文件其实对整个程序的编译打包没有任何影响,就是为了方便在QtCreator中查看和编辑。

在Qt中与安卓的java文件交互都是用万能的QAndroidJniObject,可以执行java类中的普通函数、静态函数,可以传类对象jclass、类名className、方法methodName、参数,也可以拿到执行结果返回值。 (I)V括号中的是参数类型,括号后面的是返回值类型,void返回值对应V,由于String在java中不是数据类型而是类,所以要用Ljava/lang/String;表示,其他类作为参数也是这样处理。

调用实例方法:callMethod、callObjectMethod。

调用静态方法:callStaticMethod、callStaticObjectMethod。

不带Object的函数名用来执行无返回值或者常规返回值int、float等的方法。

如果返回值是String或者类则需要用带Object的函数名来执行,返回QAndroidJniObject类型再转换处理拿到结果,比如toString拿到字符串。

各种参数和返回值示例。


package org.qt;

import org.qt.QtAndroidData;


public class QtAndroidTest

{

    //需要通过实例来调用 测试发现不论 private public 或者不写都可以调用 我擦

    private void printText()

    {

        System.out.println("printText");

    }


    public static void printMsg()

    {

        System.out.println("printMsg");

    }


    public static void printValue(int value)

    {

        System.out.println("printValue:" + value);

    }


    public static void setValue(float value1, double value2, char value3)

    {

        System.out.println("value1:" + value1 + " value2:" + value2 + " value3:" + value3);

    }


    public static int getValue()

    {

        return 65536;

    }


    public static int getValue(int value)

    {

        return value + 1;

    }


    public static void setMsg(String message)

    {

        System.out.println("setMsg:" + message);

    }


    public static String getMsg()

    {

        return "hello from java";

    }


    public static void setText(int value1, float value2, boolean value3, String message)

    {

        System.out.println("value1:" + value1 + " value2:" + value2 + " value3:" + value3 + " message:" + message);

    }


    public static String getText(int value1, float value2, boolean value3, String message)

    {

        //同时演示触发静态函数发给Qt

        QtAndroidData.receiveData("message", "你好啊 java");


        //下面两种办法都可以拼字符串

        return "value1:" + value1 + " value2:" + value2 + " value3:" + value3 + " message:" + message;

        //return "value1:" + String.valueOf(value1) + " value2:" + String.valueOf(value2) + " value3:" + String.valueOf(value3) + " message:" + message;

    }

}

#include "androidtest.h"


//java类对应的包名+类名

#define className "org/qt/QtAndroidTest"


void AndroidTest::test()

{

    jint a = 12;

    jint b = 4;

    //可以直接调用java内置类中的方法

    jint max = QAndroidJniObject::callStaticMethod<jint>("java/lang/Math", "max", "(II)I", a, b);


    //jclass javaMathClass = "java/lang/Math";

    jdouble value = QAndroidJniObject::callStaticMethod<jdouble>("java/lang/Math", "random");


    qDebug() << "111" << max << value;

}


void AndroidTest::printText()

{

    QAndroidJniEnvironment env;

    jclass clazz = env.findClass(className);

    QAndroidJniObject obj(clazz);

    obj.callMethod<void>("printText");

}


void AndroidTest::printMsg()

{

#if 0

    //查看源码得知不传入jclass类的函数中内部会自动根据类名查找jclass

    QAndroidJniEnvironment env;

    jclass clazz = env.findClass(className);

    QAndroidJniObject::callStaticMethod<void>(clazz, "printMsg");

#else

    //没有参数和返回值可以忽略第三个参数

    QAndroidJniObject::callStaticMethod<void>(className, "printMsg");

    //QAndroidJniObject::callStaticMethod<void>(classNameTest, "printMsg", "()V");

#endif

}


void AndroidTest::printValue(int value)

{

    QAndroidJniObject::callStaticMethod<jint>(className, "printValue", "(I)I", (jint)value);

}


void AndroidTest::setValue(float value1, double value2, char value3)

{

    QAndroidJniObject::callStaticMethod<void>(className, "setValue", "(FDC)V", (jfloat)value1, (jdouble)value2, (jchar)value3);

}


int AndroidTest::getValue(int value)

{

    //java类中有两个 getValue 函数 一个需要传参数

    //jint result = QAndroidJniObject::callStaticMethod<jint>(className, "getValue");

    jint result = QAndroidJniObject::callStaticMethod<jint>(className, "getValue", "(I)I", (jint)value);

    return result;

}


void AndroidTest::setMsg(const QString &msg)

{

    QAndroidJniObject jmsg = QAndroidJniObject::fromString(msg);

    QAndroidJniObject::callStaticMethod<void>(className, "setMsg", "(Ljava/lang/String;)V", jmsg.object<jstring>());

}


QString AndroidTest::getMsg()

{

    QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(className, "getMsg", "()Ljava/lang/String;");

    return result.toString();

}


void AndroidTest::setText(int value1, float value2, bool value3, const QString &msg)

{

    QAndroidJniObject jmsg = QAndroidJniObject::fromString(msg);

    QAndroidJniObject::callStaticMethod<void>(className, "setText", "(IFZLjava/lang/String;)V", (jint)value1, (jfloat)value2, (jboolean)value3, jmsg.object<jstring>());

}


QString AndroidTest::getText(int value1, float value2, bool value3, const QString &msg)

{

    QAndroidJniObject jmsg = QAndroidJniObject::fromString(msg);

    QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(className, "getText", "(IFZLjava/lang/String;)Ljava/lang/String;", (jint)value1, (jfloat)value2, (jboolean)value3, jmsg.object<jstring>());

    return result.toString();

}



在原生Android开发中,不同页面会定义不同的Activity。但使用Qt Quick、Flutter等采用Direct UI方式实现的第三方开发框架则只定义了一个Activity。里面不同页面实际都是使用OpenGL等直接绘制的。

安卓中一个界面窗体对应一个Activity,多个界面就有多个Activity,而在Qt安卓程序中,Qt这边只有一个Activity那就是QtActivity(包名全路径 org.qtproject.qt5.android.bindings.QtActivity),这个QtActivity是固定的写好的,整个Qt程序都是在这个QtActivity界面中。你打开AndroidManifest.xml文件可以看到对应节点有个name=org.qtproject.qt5.android.bindings.QtActivity,所以如果要让Qt程序能够更方便通畅的与对应的java类进行交互(需要上下文传递Activity的,比如震动,消息提示等),建议新建一个java类,继承自QtActivity即可,这样相当于默认Qt启动的就是你java类中定义的Activity,可以很好的控制和交互。


由于AndroidManifest.xml文件每个程序都可能不一样,为了做成通用的组件,这就要求可能不能带上AndroidManifest.xml文件,这样的话每个Qt安卓程序都启动默认内置的Activity,如果依赖Activity上下文的执行函数需要传入Qt的Activity才行,这里切记Qt的Activity包名是 Lorg/qtproject/qt5/android/bindings/QtActivity; 之前顺手想当然的写的 Landroid/app/Activity; 发现死活不行,原来是包名错了。


一个Qt安卓程序中可以有多个Java类,包括继承自Activity的类(这样的Activity可以通过QtAndroid::startActivity函数来调用),但是只能有一个通过AndroidManifest.xml文件指定的Activity,不指定会默认一个。如果java类中不需要拿到Qt的Activity进行处理的,可以不需要继承任何Activity,比如全部是运算的静态函数。


在java类中如果上面没有主动引入包名,则下面需要写全路径,引入了则不需要全路径可以直接用(包括枚举值都可以直接写,比如 VIBRATOR_SERVICE 这种枚举值引入了包名后不需要写android.content.Context.VIBRATOR_SERVICE),建议引入包名,比如上面写了 import org.qtproject.qt5.android.bindings.QtActivity; 则下面继承类可以直接写 public class QtAndroidActivity extends QtActivity,如果没有引入则需要写成 public class QtAndroidActivity extends org.qtproject.qt5.android.bindings.QtActivity 。


建议搭配 android studio 工具开发,因为在 android studio 中写代码都有自动语法提示,包名会提示自动引入,可以查看有那些函数方法等,还可以校验代码是否正确,而如果在QtCreator中手写有时候可能会写错,尤其是某个字母写错,当然这种错误是编译通不过的,会提示错误在哪行。


用Qt做安卓开发最大难点两个,第一个就是传参数这些奇奇怪怪的字符(Ljava/lang/String;)啥意思,如何对应,这也不是Qt故意为难初学者啥的,因为这套定义机制是安卓系统底层要求的,系统层面定义的一套规范,其实这个在帮助文档中写的很清楚,都有数据类型对照表,用熟悉了几次就很简单了。第二个难点就是用java写对应的类,如果是会安卓开发的人来说那不要太简单,尤其是搜索那么方便一大堆,没有搞过安卓开发的人来说就需要学习下,这个没有捷径,只是希望Qt能够尽可能最大化的封装一些可以直接使用的类,比如后期版本就提供了权限申请的类 QtAndroid::requestPermissionsSync 之类的,用起来就非常的爽,不用自己写个java类调来调去的。


理论上来说按照Qt提供的万能大法类QAndroidJniObject,可以不用写java类也能执行各种处理,拿到安卓库中的属性和执行方法,就是写起来太绕太费劲,在java类中一行代码,这里起码三行,所以终极大法就是熟悉安卓开发,直接封装好java类进行调用。


测试发现GetStringUTFChars方法对应的数据字符串中不能带有temp字样,否则解析有问题,不知什么原因。


数据类型参数和返回值类型必须完全一致,否则执行会提示找不到对应的函数,有返回值一定要写上返回值。


jar文件对包名的命名没有要求,只要放在android/libs目录下即可,安卓底层是通过包名去查找,而不是通过文件名,你甚至可以将原来的包名重新改成也可以正常使用,比如classes.jar改成test.jar也能正常使用

关于权限设置,在早期的安卓版本,所有权限都写在全局配置文件AndroidManifest.xml中,这种叫安装时权限,就是安装的时候告诉安卓系统当前app需要哪些权限。大概从安卓6开始,部分权限需要动态申请,这种叫动态权限,这种申请到的权限也可以动态撤销,就是要求程序再次执行代码去向系统申请权限,比如拍照、存储读写等。也不是所有的权限都改成了动态申请,意味着兼容安卓6以上的系统你既要在AndroidManifest.xml中写上要求的权限,也要通过checkPermission申请你需要的权限


android studio 新建并生产jar包步骤。


第一步:文件(File)-》新建(new)-》项目(new project)-》空白窗体(empty activity)。

第二步:刚才新建好的项目鼠标右键新建(new)-》模块(new module)-》安卓库(android library)。

说明:如果选择的不是安卓库(android library)而是java库(Java Library),则直接编译出来的就是jar文件,默认包名 com.example.lib.MyClass。推荐选择java库,编译后不用去一堆文件中找jar文件。

第三步:写好库名字,根据项目需要选择好最低sdk版本-》完成。

第四步:在刚才新建好的库项目mylibrary,依次找到子节点src/main/java/com.example.mylibrary上鼠标右键新建-》class类。切记是这个节点不是java节点或者其他节点。

第五步:写好你的类方法函数等。

package com.example.mylibrary;

public class Test {

    public static int add(int a, int b) {

        return a + b;

    }

}

第六步:选中库项目mylibrary,菜单执行编译(build)-》编译库(make module xxx)。

第七步:此时在mylibrary/build目录下有outputs目录和intermediates目录,其中outputs/aar目录下是生成的Android库项目的二进制归档文件,包含所有资源,class以及res资源文件全部包含。有时候我们仅仅需要jar文件,只包含了class文件与清单文件 ,不包含资源文件,如图片等所有res中的文件。需要到intermediates/aar_main_jar/debug目录下,可以看到classes.jar,将这个拷贝出来使用即可。当然你也可以对刚才的aar文件用解压缩软件解压出来也能看到classes.jar,是同一个文件。

其他:调用jar包非常简单,只需要将jar文件放在你的项目的libs目录下即可,对应的包名和函数一般jar包提供者会提供,没有提供的话,可以在android studio中新建空白项目,切换到project视图,找到libs目录,鼠标右键最下面作为包动态库添加到项目,导入包完成以后会自动在libs目录列出,双击刚刚导入的包然后就自动列出对应的类和函数。

Qt安卓使用jar包步骤。

第一步:将classes.jar放到android/libs目录下,为啥是这个目录?因为这是安卓的规则约定,这个目录就是放库文件,放在这个目录下的文件会自动打包编译到apk文件中。

第二步:调用jar文件之前,前提是你知道jar文件中的函数详细信息,这个一般jar提供者会提供好手册,如果代码没有混肴的话,你可以在android studio中双击打开查阅具体的函数。

第三步:如果jar文件中的函数简单,直接拿到结果不需要绕来绕去,可以直接写Qt类来调用;如果还是很复杂,建议再去新建java类处理完再交给Qt,当然也可以让jar的作者尽可能封装函数的时候就做好,尽量提供最简单的接口返回需要的数据。比如返回图片数据可以做成jar内部存储好图片,然后返回图片路径即可,不然有些数据转换也挺烦。

第四步:编写最终的调用函数。

int AndroidJar::add(int a, int b)

{

#ifdef Q_OS_ANDROID

    const char *className = "com/example/mylibrary/Test";

    jint result = QAndroidJniObject::callStaticMethod<jint>(className, "add", "(II)I", (jint)a, (jint)b);

    return result;

#endif

}

Qt6中对安卓支持部分做了大的改动,目前还不完善,如果是不涉及到与java交互的纯Qt项目,可以正常移植,涉及到的暂时不建议移植到Qt6,等所有类完善了再说。

移除了安卓插件androidextras,将其中部分功能类移到core模块中,不需要额外引入。

类名发生了变化,比如QAndroidJniObject改成了QJniObject、QAndroidJniEnvironment改成了QJniEnvironment,可能是为了统一移动开发平台类,弱化安卓的影响。

对应的安卓jdk要用jdk11而不是jdk1.8,Qt5.15两个都支持,建议就统一用jdk11。

对应封装的java类包名去掉了qt5标识,org.qtproject.qt5.android.bindings.QtActivity改成了org.qtproject.qt.android.bindings.QtActivity、org.qtproject.qt5.android.bindings.QtApplication改成了org.qtproject.qt.android.bindings.QtApplication。

对安卓最低sdk有要求,所以建议在配置AndroidManifest.xml文件的时候不要带上最低版本要求。

对AndroidManifest.xml文件内容有要求,之前Qt5安卓的不能在Qt6安卓下使用,具体内容参见示例下的文件。

对应示例demo在 C:\Qt\Examples\Qt-6.3.0\corelib\platform 目录下,之前是 C:\Qt\Examples\Qt-5.15.2\androidextras ,目前就一个示例,可能因为其他类还没有移植好。

如果想要安卓全屏遮挡住顶部状态栏,可以在main函数中将show改成showFullScreen即可,当然也可以采用java的方式在onCreate函数中加一行 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);


横竖屏切换的识别,在Qt中会同时反映到resizeEvent事件中,你可以在这个是尺寸变化后读取下当前屏幕是横屏还是竖屏,然后界面上做出调整,比如上下排列改成左右排列。


由于不同Qt版本对应的安卓配置文件 AndroidManifest.xml 内容格式不一样,高版本和低版本模板格式互不兼容,所以建议使用自己的Qt版本创建的 AndroidManifest.xml 文件,创建好以后如果使用的是自己重新定义的java文件的启动窗体则需要将 AndroidManifest.xml 文件中的 android:name=“org.qtproject.qt5.android.bindings.QtActivity” 换掉就行。


如果自己用android studio编译的jar文件放到Qt项目的libs目录下,导致编译通不过,提示 com.android.dx.cf.iface.ParseException: bad class file magic 之类的,那是因为jdk版本不一致导致的,你可能需要在android studio项目中找到模块编jdk版本设置的地方降低版本,比如你用的ndk是r14,则需要选择jdk1.6或者jdk1.7。一般来说高版本兼容低版本,因为ndk版本太低无法兼容jdk1.8。后面发现如果直接新建的是java库(Java Library)则不存在这个问题,如果选择的是安卓库(android library)就可能有这个问题。


安卓项目配置文件是固定的名字 AndroidManifest.xml ,改成其他名字就不认识,不要想当然改成其他名字导致无法正常识别。


AndroidManifest.xml文件中的package="org.qtproject.example"是包名,也是整个apk程序的内部唯一标识,如果多个apk这个包名一样,则会覆盖,所以一定要注意不同的程序记得把这个包名改成你自己的。这个包名也决定了java文件中需要使用资源文件时候的引入包名 import org.qtproject.example.R; 如果包名不一样则编译都通不过






Qt Android解决启动界面黑屏或白屏

实例项目结构图

1.png


pro文件

QT       += core gui androidextras


...

DISTFILES += \

    android/AndroidManifest.xml \

    android/build.gradle \

    android/gradle/wrapper/gradle-wrapper.jar \

    android/gradle/wrapper/gradle-wrapper.properties \

    android/gradlew \

    android/gradlew.bat \

    android/res/values/libs.xml \

    android/res/values/style.xml \

    android/res/drawable/logo.png \

    android/src/org/qtproject/example/Hello.java


..



main.cpp


#include "mainwindow.h"

#include <QApplication>

#include<QTimer>

#include <QtAndroidExtras/QtAndroid>

int main(int argc, char *argv[])

{

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QTimer::singleShot(3000,[=](){

        QtAndroid::hideSplashScreen(500);

    });


    QApplication a(argc, argv);

    MainWindow w;

    w.show();


    return a.exec();

}


style.xml

<?xml version='1.0' encoding='utf-8'?>


<resources>


    <style name="Theme.AppStartLoad" parent="android:Theme">


        <item name="android:windowBackground">@drawable/logo</item>


        <!-- @drawable/logo 确保 android\res\drawable-hdpi\logo.png 存在 -->


        <item name="android:windowNoTitle">true</item>


    </style>


    <style name="Theme.AppStartLoadTranslucent" parent="android:Theme">


        <item name="android:windowIsTranslucent">true</item>


        <item name="android:windowNoTitle">true</item>


    </style>


</resources>



AndroidManifest.xml


...

 <application android:theme="@style/Theme.AppStartLoadTranslucent" android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --">

        <activity android:theme="@style/Theme.AppStartLoad" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">

            <intent-filter>

                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>

            </intent-filter>

...



当QT项目用AndroidStudio打开报配置出错时

**QT例子由于目录过深可能编译报错,可以将代码copy出来编译

1.png

还可以设置编译的CPU架构、Android SDK版本、签名等

1.png

**当初始项目报错时候先看Android Studio 中File->setting下的proxy 设置科学上网代理设置是否正确

报告JDK版本和gradle不匹配时

将Andstudio 项目的JDK 改来和QT里一样如图

4.png

5.png

6.png

7.png



QT 开发的Android在Android中常见错误处理

Java版本问题:Your build is currently configured to use Java 17.0.6 and Gradle 5.6.4.

QtCreator注意NDK版本

关于Qt for android构建的Failed to GetFullPathName错误



Java版本问题:Your build is currently configured to use Java 17.0.6 and Gradle 5.6.4.

2.png

修改JAVA的版本

3.png

Java的安装一般路径

C:\Program Files\Android\jdk\jdk-8.0.302.8-hotspot\jdk8u302-b08

C:\Program Files\Java\jdk-17

QtCreator输出多个CPU类型代码设置

2.png



注意QTCreator对应的NDK版本

Qt Creator 4.9.0 (Enterprise) 用android-ndk-r19c

qtcreator4.11     用android-ndk-r20b



关于Qt for android构建的Failed to GetFullPathName错误


关于Qt for android构建的Failed to GetFullPathName错误

最近在做Qt for android的项目的过程中,运用Qt的自带的例子编译一个qt for android的APK,发现了一个编译错误如下:


> Task :compileDebugAidl FAILED


aidl.exe E 04-24 16:06:20 25348 25020 io_delegate.cpp:50] Failed to GetFullPathName(C:\Qt\Qt5.14.1\Examples\Qt-5.14.1\widgets\painting\build-fontsampler-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_1_for_Android_90ede6-Debug\android-build\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\org\kde\necessitas\ministro\IMinistro.java)


FAILURE: Build failed with an exception.


* What went wrong:


Execution failed for task ':compileDebugAidl'.


> 1 exception was raised by workers:


java.lang.RuntimeException: java.lang.RuntimeException: java.io.IOException: com.android.ide.common.process.ProcessException: Error while executing process E:\android_dll\android-sdk-windows\build-tools\28.0.3\aidl.exe with arguments {-pE:\android_dll\android-sdk-windows\platforms\android-29\framework.aidl -oC:\Qt\Qt5.14.1\Examples\Qt-5.14.1\widgets\painting\build-fontsampler-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_1_for_Android_90ede6-Debug\android-build\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out -IC:\Qt\Qt5.14.1\5.14.1\android\src\android\java\src -IC:\Qt\Qt5.14.1\Examples\Qt-5.14.1\widgets\painting\build-fontsampler-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_1_for_Android_90ede6-Debug\android-build\src\debug\aidl -IC:\Qt\Qt5.14.1\Examples\Qt-5.14.1\widgets\painting\build-fontsampler-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_1_for_Android_90ede6-Debug\android-build\src -IC:\Qt\Qt5.14.1\Examples\Qt-5.14.1\widgets\painting\build-fontsampler-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_1_for_Android_90ede6-Debug\android-build\aidl -dC:\Users\Administrator\AppData\Local\Temp\aidl2993109682987047489.d C:\Qt\Qt5.14.1\5.14.1\android\src\android\java\src\org\kde\necessitas\ministro\IMinistro.aidl}


* Try:


Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.


* Get more help at https://help.gradle.org


BUILD FAILED in 9s


1 actionable task: 1 executed


Building the android package failed!


-- For more information, run this command with --verbose.


16:06:20: 进程"C:\Qt\Qt5.14.1\5.14.1\android\bin\androiddeployqt.exe"退出,退出代码 14 。


Error while building/deploying project fontsampler (kit: Android for armeabi-v7a,arm64-v8a,x86,x86_64 (Clang Qt 5.14.1 for Android))


When executing step "Build Android APK"


刚开始也不知道啥问题,然后翻译了一下Failed to GetFullPathName,发现了是不能获取路径的问题,然后就网上的百度了一下,刚好看到了有位大佬的解决方法,究其原因还是这个构建文件夹的路径和文件名称太长了,导致编译的时候编译器无法获取到准确的路径名,所以可以将构建的路径选择到别的文件夹去。


我之前的构建目录:

4.png



然后就报错,后面将方框里面的构建目录改成了G:\QMLobject 目录下  成功的解决了这个问题


Qt for Android 启动短暂的黑屏或白屏问题如何解决

使用图片替换主题

在res/values文件目录下新建一个 style.xml 文件,文件内容如下


<?xml version='1.0' encoding='utf-8'?>

<resources>

<style name="Theme.AppStartLoad" parent="android:Theme">

    <item name="android:windowBackground">@drawable/logo</item>

        <!-- @drawable/logo 确保 android\res\drawable-hdpi\logo.png 存在 -->

    <item name="android:windowNoTitle">true</item>

</style>

<style name="Theme.AppStartLoadTranslucent" parent="android:Theme">

    <item name="android:windowIsTranslucent">true</item>

    <item name="android:windowNoTitle">true</item>

</style>

</resources>


然后在 AndroidManifest.xml 中应用上面定义的两个主题,添加的位置如下


<application   android:theme = "@style/Theme.AppStartLoadTranslucent">

<activity  android:theme="@style/Theme.AppStartLoad">


            <!-- Splash screen -->

            <!-- <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/> -->

            <!-- <meta-data android:name="android.app.splash_screen_sticky" android:value="true"/> -->

            <!-- Splash screen -->

                        //这上面的是添加启动图片


Qt for Android 调用原生Activity 三步走,开启android的潘多拉世界

花了一天时间开启这个hellowrld.所有的东西都复制黏贴了就是调用不到原生的。哎那些写书的大佬能不能多填点坑,这条路子实在是太艰难了。还没开始就结束了。望而却步。现在就让我把这个小坑填掉吧。

1.android studio 的应用文件拷贝到Qt 工程android目录下

  主要有src和res 下的layout

2.配置Qt AndroidMainfest.xml 添加注册你自定义的activity例如:

 <activity android:name="com.demo.testactivity.MainActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
  <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/> <intent-filter>
    <action android:name="com.demo.testactivity.MainActivity"/>
    <category android:name="android.intent.category.DEFAULT"/>

</intent-filter></activity>

//这个是拷贝某大佬的代码修改修改。

#define CHECK_EXCEPTION() \
if(env->ExceptionCheck())\
{\
qDebug() << "exception occured";\
env->ExceptionClear();\
}

void Widget::on_pushButton_clicked()
{undefined
QAndroidJniEnvironment env;
QAndroidJniObject action = QAndroidJniObject::fromString("com.demo.testactivity.MainActivity");
 QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());
QtAndroid::startActivity(intent, 0);
CHECK_EXCEPTION()//这里可有可无,但是人家大佬都这么用了。那就加上吧。便于debug
}

走到这里你发现诶怎么qt原生界面无论怎么跳转都跳转不到java的原生界面是不是开始各种怀疑各种骂爹,什么狗屁不通代码,狗日的我要放弃了我要放弃,android要么原生。什么qt for android 去你麻痹的就是个鸡肋。。。。我真的要放弃了。要不我们在试试。。。认真观察下列划掉的两行代码。。。。至于什么原因呢debug应该会知道原因。

package com.demo.testactivity;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
public class MainActivity extends Activity {undefined
@Override
protected void onCreate(Bundle savedInstanceState) {undefined
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("hello world");
}
}

运行,跳转。。。hello world 

happly enjoy


QT适配Android端界面

#include "mainwindow.h"

#include <QApplication>


int main(int argc, char *argv[])

{

   QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//添加上该句实现自适配

    QApplication a(argc, argv);

    MainWindow w;

    w.show();

    return a.exec();

}

QT简单教程



QT开发Android 注意NDK版本

Qt Creator 4.9.0 (Enterprise) 用android-ndk-r19c

qtcreator4.11     用android-ndk-r20b



qt android 引用不带界面的aar包

as生成aar步骤:前边已详细说明步骤,这里大体一说。


1、在应用上右键,new-module-android library,名字mylib


2、在mylib-src-main-java-com.example.mylib上右键new-java class。名字Test


package com.example.mylib;

 

public class Test {

    public Test(int i)

    {

        System.out.println("test实例化");

    }

    public  static int fun1(){

        System.out.println("静态函数");

        return 1;

    }

    public int fun2(){

        System.out.println("实例函数");

        return 2;

    }

}

3、build-rebuild project,生成aar


qt引用步骤:


1、新建安卓应用test,pro文件增加:QT += androidextras


点项目->build android apk->  create templates。


2、将mylib-debug.aar拷贝到D:\cx\test\testandroid\test\android\libs目录。


3、修改build.gradle,增加repositories { flatDir { dirs 'libs' }},dependencies增加compile(name:'mylib-debug', ext:'aar')


repositories {

    flatDir {

        dirs 'libs'

    }

}

 

dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile(name:'mylib-debug', ext:'aar')

}

4、窗口增加个按钮测试


#include "mainwindow.h"

#include "ui_mainwindow.h"

#include <QtAndroid>

#include <QAndroidJniEnvironment>

#include <QAndroidJniObject>

#include<QMessageBox>

MainWindow::MainWindow(QWidget *parent) :

    QMainWindow(parent),

    ui(new Ui::MainWindow)

{

    ui->setupUi(this);

}

 

MainWindow::~MainWindow()

{

    delete ui;

}

 

void MainWindow::on_pushButton_clicked()

{

    QAndroidJniObject testobj("com/example/mylib/Test","(I)V",10);

    if(testobj.isValid())

    {

        jint funret1=testobj.callMethod<jint>("fun2","()I");

        QString str1=QString("%1").arg(funret1);

        QMessageBox::about(NULL,"okts","ret="+str1);

        jint funret2=QAndroidJniObject::callStaticMethod<jint>("com/example/mylib/Test","fun1","()I");

        QString str2=QString("%1").arg(funret2);

        QMessageBox::about(NULL,"okts","ret="+str2);

    }

    else

    {

        QMessageBox::about(NULL,"ts","no");

    }

}

1.png



qt 如何使用 aar

1、在app的build.gradle中加入以下配置

repositories { flatDir { dirs 'libs' }}// aar目录 

2、将aar文件拷贝到android/libs目录下

3、在dependencies中加入aar引用

compile(name: 'xxxxx', ext: 'aar')

1.png

pro文件内添加“QT += androidextras”

在src同级目录下新建libs文件夹,将jar直接拷入即可


QT自带的Android类,通过派生修改行为

可能是由于内容开始是网络地址(http://...)完整的英文词截取后不能换行造成 为避免代码造成手机端排版的混乱,可适当增加文字描述,将代码往后推移

QtActivity

QtActivityLoader

QtApplication

QtLoader

QtService

QtServericeLoader



QT Android 调用JAVA代码

下面是完整程序


1.写一个普通的JAVA类

//MyJavaClass.java

public class MyJavaClass

{

    public static int mystatic(int n)             //静态函数;

    {

       return n+1;

    }

    public int getNum()                            //没有参数;

    {

        return 12345678;

    }

    public int setNum( int n )                     //有参调用

    {

        return n;

    }

 

    public String getStr(String perfix)                        //返回对象

    {

        String teststr = new String("hello " + perfix);

        return teststr;

    }

 

    boolean boolTest()                              //返回bool类型

    {

        return true;

    }

 

}


2.写一个C++类

//testclass.h

#include <jni.h>

 

class JavaHandler:public QObject

{

    Q_OBJECT

public:

    explicit JavaHandler(QObject *parent=0);

 

public:

    //Q_INVOKABLE宏说明:因为我测试程序是用qml调用的

    //返回值都是String是为了方便我在qml中显示调用结果

   Q_INVOKABLE QString mystatic(int num);   //静态方法

 

   Q_INVOKABLE QString getNum(); // 无参

 

   Q_INVOKABLE QString setNum(int num); //有参

 

   Q_INVOKABLE QString getStr(QString perfix);  //参数和返回值都是对象

 

    Q_INVOKABLE QString boolTest();     //返回值为bool

 

private:

    jobject m_TestObject;       //java对象

};


3.C++源程序

这个示例代码的核心是 

JNIEXPORT jint JNI_OnLoad(JavaVM vm,void ) 

基本上是原生JNI的接口,没有用QT提供的QAndroidJniObject 类 

这样可以在非QT的环境里稍修改为也可以用


//testclass.cpp

#include "testclass.h"

 

static JavaVM* g_javaVM = 0;            //虚拟机指针

 

static jclass g_testClassID   = 0;          //类ID

static jmethodID g_mystaticID = 0;        //静态方法

static jmethodID g_getNumID = 0;          //无参方法

static jmethodID g_setNumID = 0;          //有参方法

static jmethodID g_getStrID = 0;          //参数和返回值为类对象

static jmethodID g_boolTestID = 0;        //返回值为bool型

static jmethodID g_constMethodID = 0;   //构造函数ID

 

 

 

JavaHandler::JavaHandler(QObject *parent)

{

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL))

    {

            qCritical() << "AttachCurrentThread failed";

            return;

    }

    //[] new 一个对象

    m_TestObject = env->NewGlobalRef(env->NewObject(g_testClassID,g_constMethodID));

    if(!m_TestObject)

    {

        qCritical() << "Can not create the object";

        return;

    }

    g_javaVM->DetachCurrentThread();

}

 

QString JavaHandler::mystatic(int num)

{

    if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

    jint ret = env->CallStaticIntMethod(g_testClassID,g_mystaticID,num);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

 

}

 

QString JavaHandler::getNum()

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jint ret = env->CallIntMethod(m_TestObject,g_getNumID);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

}

 

QString JavaHandler::setNum(int num)

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jint ret = env->CallIntMethod(m_TestObject,g_setNumID,num);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

 

}

 

QString JavaHandler::getStr(QString perfix)

{

 

    if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jstring str = env->NewString(reinterpret_cast<const jchar*>(perfix.constData()),perfix.length());

 

    jobject retstr = env->CallObjectMethod(m_TestObject,g_getStrID,str);

 

    jstring jstrtemp = (jstring)retstr;

 

 

   const char *pstr= env->GetStringUTFChars(jstrtemp, 0);

 

    QString rstr(pstr);

 

 

    g_javaVM->DetachCurrentThread();

 

    return rstr;

 

}

 

QString JavaHandler::boolTest()

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jboolean ret = env->CallBooleanMethod(m_TestObject,g_boolTestID);

 

    g_javaVM->DetachCurrentThread();

 

    return (ret?"ture":"false");

}

 

 

 

JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *)

{

    JNIEnv *env;

    //[1] 指定JNI 版本,利用VM获得 env指针

    if(vm->GetEnv(reinterpret_cast<void **>(&env),JNI_VERSION_1_6) != JNI_OK)

    {

        qCritical() << "Can't get the enviroument";

        return -1;

    }

    //保存获得指针以便在任意上下文中使用

    g_javaVM = vm;

 

    //[2] 获取类ID

    jclass clazz = env->FindClass("com/MyJavaClass");

    if(!clazz)

    {

        qCritical() << "Can't find class MyJavaClass";

        return -1;

    }

 

    g_testClassID = (jclass)env->NewGlobalRef(clazz);

 

     // search for its contructor

 

    g_constMethodID = env->GetMethodID(g_testClassID, "<init>", "()V");

    if (!g_constMethodID)

    {

        qCritical()<<"Can't find  class contructor";

        return -1;

    }

 

    g_mystaticID = env->GetStaticMethodID(g_testClassID,"mystatic","(I)I");

    if(!g_mystaticID)

    {

        qCritical() << "Can't find static method "<< endl;

        return -1;

    }

 

 

    g_getNumID = env->GetMethodID(g_testClassID,"getNum","()I");

    if(!g_getNumID)

    {

        qCritical() << "Can't find getNum method "<< endl;

        return -1;

    }

 

    g_boolTestID = env->GetMethodID(g_testClassID,"boolTest","()Z");

    if(!g_boolTestID)

    {

        qCritical() << "Can't find booltest method "<< endl;

        return -1;

    }

 

    g_setNumID = env->GetMethodID(g_testClassID,"setNum","(I)I");

    if(!g_setNumID)

    {

        qCritical() << "Can't find setNUM method "<< endl;

        return -1;

    }

 

    g_getStrID = env->GetMethodID(g_testClassID,"getStr", "(Ljava/lang/String;)Ljava/lang/String;");

    if(!g_getStrID)

    {

        qCritical() << "Can't find GetStr Method "<< endl;

        return -1;

    }

 

    qDebug() << "JNI Load Success !";

    return JNI_VERSION_1_6;

}


main.cpp及qml文件

//main.cpp

#include <QApplication>

#include <QQmlApplicationEngine>

#include <qqmlfile.h>

#include <QQuickTextDocument>

 

#include "testclass.h"

 

int main(int argc, char *argv[])

{

    QApplication app(argc, argv);

 

     qmlRegisterType<JavaHandler>("java.handler",1,0,"JavaHandler");

 

    QQmlApplicationEngine engine;

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

 

    return app.exec();

}

点击按钮分别调用java的方法,弹出消息框显示结果



Qt on Android 添加SO库

在Qt开发android的时候,有时候我们采用开发板厂商提供的jar配合Qt jni接口调用,然而有的时候由于我们Qt是基于C++的,我们可以直接抄底绕开jar调用其底层的so库来进行开发。本文介绍了如何调用so库实现这一功能。


一 右键项目添加库选择外部库

1.png


1.png



二 在android项目中添加so库

1.png

1.png



之后便可以愉快的开发了



Qt C++和Java相互调用

1. C++调用Java

class Test: public QObject

{

public:

void callJavaMethodPrintHello();

}

void Center::callJavaMethodPrintHello()

{

//这里相当于获取MainActibity.java里的类对象

  

 const QAndroidJniObject& activity = QtAndroid::androidActivity();

 

   //调用打印

    

activity.callObjectMethod("printHello", "()V");

   

 //获取值

    

QAndroidJniObject string = QAndroidJniObject::callStaticObjectMethod("net/sun/java/MainActivity", "getHello", "()Ljava/lang/String;");

 

  qDebug()<<"callJavaMethodPrintHello......:"<<string.toString();

    

//传参

   

 jint n = activity.callMethod<jint >("add", "(II)I", 1, 2);

  

  qDebug()<<n;

}

1.const QAndroidJniObject& activity = QtAndroid::androidActivity();相当于获取java里MainActivity的类对象,拿到这个对象开始做其他操作。

2.callObjectMethod、callMethod、callStaticObjectMethod都是调用java代码的方法,具体使用可以参照QT帮助文档QAndroidJniObject类

3.net/sun/java/MainActivity为MainActivity.java的所在路径

4.调用java代码方面新手可能不太会传参和接受返回数据,下边的图标符号对应的类型供参考

1.png

MainActibity.java


public class MainActivity extends org.qtproject.qt5.android.bindings.QtActivity

{

private void printHello {

        Log.i("@@@", "Hello world!");

    }

    private static String getHello() {

        return "Hello!";

    }

    private int add(int a, int b) {

        return a + b;

    }

}

2. java调用C++

Test.cpp


static void JavaNotify_1()

{

    qDebug()<<"C++ JavaNotify_1";

}

static void JavaNotify_2()

{

    qDebug()<<"C++ JavaNotify_2";

}

static const JNINativeMethod gMethods[] = {

    { "CallJavaNotify_1", "()V", (void*)JavaNotify_1},

    { "CallJavaNotify_2", "()V", (void*)JavaNotify_2}

};

void RegJni()

{

    QtAndroid::runOnAndroidThreadSync([=](){

        QAndroidJniEnvironment Environment;

        const char* mClassName ="net/sun/java/MainActivity";

        jclass j_class;

        j_class = Environment->FindClass(mClassName);

        if (j_class == nullptr)

        {

            qDebug()<<"erro clazz";

            return ;

        }

        jint mj = Environment->RegisterNatives(j_class, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));

        if (mj != JNI_OK)

        {

            qDebug()<<"register native method failed!";

            return;

        }else{

            qDebug()<<"RegisterNatives success!";

        }

    });

    qDebug()<<"++++++++++++++++++++++++";

}


Center::Center(QObject *parent) : QObject(parent)

{

}


gMethods是一个二维数组,把需要被调用的C++方法和Java方法绑定到一起,建立对应关系,比如,java中调用CallJavaNotify_1相应C++的JavaNotify_1就会被执行。然后Environment->RegisterNatives注册


MainActibity.java


public class MainActivity extends org.qtproject.qt5.android.bindings.QtActivity

{

public native void CallJavaNotify_1();

    public native void CallJavaNotify_2();


private void callQtFunction(){

CallJavaNotify_1();

}

}

java中在你想要调用的地方调用即可



QT Android 调用JAVA代码

1.写一个普通的JAVA类

public class MyJavaClass

{

    public static int mystatic(int n)             //静态函数;

    {

       return n+1;

    }

    public int getNum()                            //没有参数;

    {

        return 12345678;

    }

    public int setNum( int n )                     //有参调用

    {

        return n;

    }

 

    public String getStr(String perfix)                        //返回对象

    {

        String teststr = new String("hello " + perfix);

        return teststr;

    }

 

    boolean boolTest()                              //返回bool类型

    {

        return true;

    }

 

}

2.写一个C++类

//testclass.h

#include <jni.h>

 

class JavaHandler:public QObject

{

    Q_OBJECT

public:

    explicit JavaHandler(QObject *parent=0);

 

public:

    //Q_INVOKABLE宏说明:因为我测试程序是用qml调用的

    //返回值都是String是为了方便我在qml中显示调用结果

   Q_INVOKABLE QString mystatic(int num);   //静态方法

 

   Q_INVOKABLE QString getNum(); // 无参

 

   Q_INVOKABLE QString setNum(int num); //有参

 

   Q_INVOKABLE QString getStr(QString perfix);  //参数和返回值都是对象

 

    Q_INVOKABLE QString boolTest();     //返回值为bool

 

private:

    jobject m_TestObject;       //java对象

};

3.C++源程序

这个示例代码的核心是 

JNIEXPORT jint JNI_OnLoad(JavaVM vm,void ) 

基本上是原生JNI的接口,没有用QT提供的QAndroidJniObject 类 

这样可以在非QT的环境里稍修改为也可以用

//testclass.cpp

#include "testclass.h"

 

static JavaVM* g_javaVM = 0;            //虚拟机指针

 

static jclass g_testClassID   = 0;          //类ID

static jmethodID g_mystaticID = 0;        //静态方法

static jmethodID g_getNumID = 0;          //无参方法

static jmethodID g_setNumID = 0;          //有参方法

static jmethodID g_getStrID = 0;          //参数和返回值为类对象

static jmethodID g_boolTestID = 0;        //返回值为bool型

static jmethodID g_constMethodID = 0;   //构造函数ID

 

 

 

JavaHandler::JavaHandler(QObject *parent)

{

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL))

    {

            qCritical() << "AttachCurrentThread failed";

            return;

    }

    //[] new 一个对象

    m_TestObject = env->NewGlobalRef(env->NewObject(g_testClassID,g_constMethodID));

    if(!m_TestObject)

    {

        qCritical() << "Can not create the object";

        return;

    }

    g_javaVM->DetachCurrentThread();

}

 

QString JavaHandler::mystatic(int num)

{

    if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

    jint ret = env->CallStaticIntMethod(g_testClassID,g_mystaticID,num);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

 

}

 

QString JavaHandler::getNum()

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jint ret = env->CallIntMethod(m_TestObject,g_getNumID);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

}

 

QString JavaHandler::setNum(int num)

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jint ret = env->CallIntMethod(m_TestObject,g_setNumID,num);

 

    g_javaVM->DetachCurrentThread();

 

    return QString::number(ret,10);

 

}

 

QString JavaHandler::getStr(QString perfix)

{

 

    if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jstring str = env->NewString(reinterpret_cast<const jchar*>(perfix.constData()),perfix.length());

 

    jobject retstr = env->CallObjectMethod(m_TestObject,g_getStrID,str);

 

    jstring jstrtemp = (jstring)retstr;

 

 

   const char *pstr= env->GetStringUTFChars(jstrtemp, 0);

 

    QString rstr(pstr);

 

 

    g_javaVM->DetachCurrentThread();

 

    return rstr;

 

}

 

QString JavaHandler::boolTest()

{

     if(!m_TestObject)

        return QString("Error");

 

    JNIEnv* env;

    if(g_javaVM->AttachCurrentThread(&env,NULL) < 0)

    {

        return QString("Fail to Attach");

    }

 

    jboolean ret = env->CallBooleanMethod(m_TestObject,g_boolTestID);

 

    g_javaVM->DetachCurrentThread();

 

    return (ret?"ture":"false");

}

 

 

 

JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *)

{

    JNIEnv *env;

    //[1] 指定JNI 版本,利用VM获得 env指针

    if(vm->GetEnv(reinterpret_cast<void **>(&env),JNI_VERSION_1_6) != JNI_OK)

    {

        qCritical() << "Can't get the enviroument";

        return -1;

    }

    //保存获得指针以便在任意上下文中使用

    g_javaVM = vm;

 

    //[2] 获取类ID

    jclass clazz = env->FindClass("com/MyJavaClass");

    if(!clazz)

    {

        qCritical() << "Can't find class MyJavaClass";

        return -1;

    }

 

    g_testClassID = (jclass)env->NewGlobalRef(clazz);

 

     // search for its contructor

 

    g_constMethodID = env->GetMethodID(g_testClassID, "<init>", "()V");

    if (!g_constMethodID)

    {

        qCritical()<<"Can't find  class contructor";

        return -1;

    }

 

    g_mystaticID = env->GetStaticMethodID(g_testClassID,"mystatic","(I)I");

    if(!g_mystaticID)

    {

        qCritical() << "Can't find static method "<< endl;

        return -1;

    }

 

 

    g_getNumID = env->GetMethodID(g_testClassID,"getNum","()I");

    if(!g_getNumID)

    {

        qCritical() << "Can't find getNum method "<< endl;

        return -1;

    }

 

    g_boolTestID = env->GetMethodID(g_testClassID,"boolTest","()Z");

    if(!g_boolTestID)

    {

        qCritical() << "Can't find booltest method "<< endl;

        return -1;

    }

 

    g_setNumID = env->GetMethodID(g_testClassID,"setNum","(I)I");

    if(!g_setNumID)

    {

        qCritical() << "Can't find setNUM method "<< endl;

        return -1;

    }

 

    g_getStrID = env->GetMethodID(g_testClassID,"getStr", "(Ljava/lang/String;)Ljava/lang/String;");

    if(!g_getStrID)

    {

        qCritical() << "Can't find GetStr Method "<< endl;

        return -1;

    }

 

    qDebug() << "JNI Load Success !";

    return JNI_VERSION_1_6;

}

4.main.cpp及qml文件

//main.cpp

#include <QApplication>

#include <QQmlApplicationEngine>

#include <qqmlfile.h>

#include <QQuickTextDocument>

 

#include "testclass.h"

 

int main(int argc, char *argv[])

{

    QApplication app(argc, argv);

 

     qmlRegisterType<JavaHandler>("java.handler",1,0,"JavaHandler");

 

    QQmlApplicationEngine engine;

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

 

    return app.exec();

}


Qt for Android开发一些总结

近段时间,本人使用Qt5.3.0开发了Android应用,由于官方资料较少,在此记录开发过程遇到的问题及解决方法


1.Android平台的视频播放,只能使用qml的MediaPlayer


2.qml中控件的路径必须加file://  例如:

  Image{

            source: "file:///mnt/usbhost1/Config/logo.png"

        }

3.C++与qml中js的方法互调

QQuickView view;

view.setSource(QUrl(QStringLiteral("qrc:///qml/MainView.qml")));

QObject *qmlObj =(QObject*) view.rootObject();

MainWnd *w=new MainWnd(object);

//暴露C++类给qml供其调用,别名mainWndClass

view.engine ()->rootContext ()->setContextProperty (QLatin1String("mainWndClass"),w);


//c++调用qml中的js方法

//参数必须转换为QVariant

QMetaObject::invokeMethod (qmlObj,"showRight",Q_ARG(QVariant,1));

//调用子项的js方法

qmlPlayer = qmlObj->findChild<QObject*>("playerArea");

QMetaObject::invokeMethod (qmlPlayer,"setVideoFile",Q_ARG(QVariant,currentVideoFile));


//MainView.qml


Rectangle {

    anchors.fill: parent

    property int leftAreaWidth: this.width/5*4

    property int rightAreaWidth: this.width/5

    property int queueFontSize



    function showRight(isShow){

        ....

    }

    Player{

        id:playerArea

        //设置objectName,在c++中才能找到它

        objectName: "playerArea"

        width: parent.width

        height: parent.height

    }

4.c++调用java Android api


在项目目录下建立目录\android\src\org\rophie\ProjectName\JavaClass.java  


org\rophie\ProjectName即为java类的包名package org.rophie.ProjectName;


如我调用Android API调节系统音量

package org.rophie.ProjectName;


import org.qtproject.qt5.android.bindings.QtActivity;

import android.widget.Toast;

import android.media.AudioManager;

import android.content.Context;


public class JavaClass extends QtActivity{


    private static JavaClass m_instance;

    private static AudioManager mAudioManager;


    public JavaClass()

    {

        //构造函数必须

        m_instance = this;

    }

     public static void setVolume(int vol){

     if(mAudioManager==null){

         mAudioManager = (AudioManager)m_instance.getSystemService(Context.AUDIO_SERVICE);

     }

         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0);

     }

}

C++调用

QAndroidJniObject::callStaticMethod<void>("org/rophie/ProjectName/JavaClass","setVolume","(I)V",3);


//具体参照QAndroidJniObject类


5.BroadcastReceiver实现开机自启,和Android一模一样

public void onReceive(Context context, Intent intent) {

 ......

 //JavaClass为继承QtActivity的java主类

 Intent intent2 = new Intent(context, JavaClass.class);

 ......

6.调用第三方jar包,在src同级目录下新建目录libs,将.jar拷入即可使用


Qt for Android程序沉浸式启动页面

最近研究Qml程序在安卓手机上运行,Qml的运行没啥问题,编译环境配置成功即可运行,安卓原生的一些东西就需要自己去琢磨了,比如程序启动后,默认会有一个短暂的黑屏以及默认标题栏,很影响用户体验,通过查阅相关资料后,记录下踩坑之路,先上最终效果(此方法运行环境为Qt 5.11,高级版本方法看下方):

1.png



首先,解决启动黑屏和无标题栏显示,我们需要创建一个 android 的布局文件 xml,在res/drawable目录下创建一个 xml 文件,并命名为style.xml。


<?xml version="1.0" encoding="utf-8"?>

<resources>

    <style name="custom_Style" parent="android:Theme">

<!--        <item name="android:windowIsTranslucent">true</item>-->

        <item name="android:windowBackground">@drawable/m_splash</item>

        <item name="android:windowNoTitle">true</item>

        <item name="android:windowFullscreen">true</item>

    </style>

</resources>

注意:很多教程上会采用上文中注释掉的<item name="android:windowIsTranslucent">true</item>的方式设置窗口透明的方式实现隐藏黑屏,如果采用该方法,当程序从后台却换到前台时,会没有过渡动画,习惯后台切换的时候就会感觉程序切换的很突兀,因此使用设置启动背景图片,该图片我和程序加载Splash启动页使用同一个,因此可以做到无缝对接。


然后,打开 AndroidMenifest.xml文件,在 Activity 组添加主题的设置:


android:theme="@style/custom_Style"

接下来设置自定义启动页面我们需要创建一个 android 的布局文件 xml,在res/drawable目录下创建一个 xml 文件,并命名为m_splash.xml。

然后,打开该文件,并输入:


<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@color/colorStart"/>

    <item>

        <bitmap

            android:gravity="center"

            android:src="@drawable/start"/>

    </item>

</layer-list>

注意:自定义一个背景颜色,该颜色与背景图片背景颜色一致,同时该背景图片尺寸不要太大,否则会出现超出屏幕的情况,经本人测试,使用一个一个较小尺寸的图片,设置背景颜色与图片背景一致以达到适配不同尺寸屏幕手机的启动页面效果(CSDN的APP就是这么干的)。


color.xml:(自带模板里有这个文件,在后面加上背景颜色即可)


<?xml version="1.0" encoding="utf-8"?>

<resources>

    <color name="purple_200">#FFBB86FC</color>

    <color name="purple_500">#FF6200EE</color>

    <color name="purple_700">#FF3700B3</color>

    <color name="teal_200">#FF03DAC5</color>

    <color name="teal_700">#FF018786</color>

    <color name="black">#FF000000</color>

    <color name="white">#FFFFFFFF</color>

    <color name="colorStart">#f7f8fb</color>

</resources>

在AndroidMenifest.xml中,设置启动页面,原始状态下该属性为注释状态,一个属性表示启动页路径,第二个属性值表示启动页是否保持住,为false时为一闪而过:


<!-- Splash screen -->

<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/m_splash"/>

<meta-data android:name="android.app.splash_screen_sticky" android:value="true"/>

<!-- Splash screen -->

基本功能都添加完毕了,现在的程序APP就像那么回事了,最后设置启动页自动关闭时间,在main.cpp中设置单次计时器:


#ifdef Q_OS_ANDROID

QTimer::singleShot(3000,&app,[=](){QtAndroid::hideSplashScreen(500);});

#endif

基本功能就讲解完了,如果要像我这么要求启动页是沉浸式标题栏(白色),进入主界面时候标题栏变成程序主题色(蓝色),在Qml中添加修改状态栏颜色代码,当计时器时间到后,QtAndroid::hideSplashScreen启动后,调用Qml中修改状态栏颜色的函数即可。


    QObject* obj = engine.rootObjects()[0];

    #ifdef Q_OS_ANDROID

    QTimer::singleShot(3000,&app,[=](){QtAndroid::hideSplashScreen(500);QMetaObject::invokeMethod(obj,"setStatusBarColor");});

    #endif

我设置状态的方法使用 GitHub中一个大神做的一份开源代码,专门处理透明状态栏的,并且使用非常方便,跟着说明添加进工程就可以,链接地址:


https://github.com/jpnurmi/qtstatusbar


最后贴张资源文件图:

1.png



Tips:当在做Qt网上查不到资料时,可以试试直接搜Android 


Qt 5.15添加启动页面

更新Qt版本后,直接提供了启动页面窗口,很方便,如图:

1.png


解决启动黑屏和无标题栏显示,我们需要创建一个 android 的布局文件 xml,在res/drawable目录下创建一个 xml 文件,并命名为style.xml,内容稍有不同,既把启动页面行删掉即可:


<?xml version="1.0" encoding="utf-8"?>

<resources>

    <style name="custom_Style" parent="android:Theme">

<!--        <item name="android:windowIsTranslucent">true</item>-->

<!--        <item name="android:windowBackground">@drawable/m_splash</item>-->

        <item name="android:windowNoTitle">true</item>

        <item name="android:windowFullscreen">true</item>

    </style>

</resources>

注意:用到的文件需要添加到pro中,没用到的文件不要放资源文件中)



Qt for Android 启动短暂的黑屏或白屏问题如何解决?

Qt for Android 启动短暂的黑屏或白屏问题如何解决?可能是由于内容开始是网络地址(http://...)完整的英文词截取后不能换行造成 为避免代码造成手机端排版的混乱,可适当增加文字描述,将代码往后推移


android:theme="@android:style/Theme.Translucent.NoTitleBar"

或android:style/Theme.Light.NoTitleBar

1.png   

其他方法



Android Studio打开QT的Android项目

如里面没有app那么创建

1.png

新添加myapp

1.png



QT开发Androd编译多个CPU程序

将QT编译出的各CPU的Builder放到一个目录,然后再Android studio中编译

比如:F:\2019LastCode\Qt\build-gallery-Android_for_arm64_v8a_Clang_Qt_5_12_3_for_Android_ARM64_v8a-Debug\android-build\libs

1.png



QT编译Android方法

1.png

1.png

直接Android Studio编译

QT里操作

1.png



编译:构建菜单->重新构建

生成目录

2.png


文件


3.png

用Android Studio打开该目录然后编译


QT 安卓程序如何后台运行保活?

修改文件AndroidManifest.xml

要启用后台运行,请取消注释android.app.background_running meta-data并将其设置为true(android:value="true")。我试了一下,感觉可以。。。















Top