精华内容
下载资源
问答
  • QT Android串口
    2021-06-23 08:49:27

    在电脑上做的串口通信软件需要移植到安卓设备上,查找资料找到一些方法总结出来

    环境:QT5.12.3  for 安卓环境(网上有很多资料,不做介绍)

    安卓设备是购买的安卓开发平板,Android9.0版本

    一、QT自带的QSerialPort类

    可以使用QT自带的串口类进行操作,操作流程和电脑上代码一致

    在编译到安卓设备上时要将串口名改为安卓系统中串口文件名(“tty...”)

    但这并没有成功,在使用超级终端查看该文件权限后发现文件权限(750),在超级终端上使用root权限修改文件权限为(777),再次打开文件串口收发还是不正确,查阅资料发现需要关闭防火墙,关闭后串口收发正常。

    注意:平板重启后防火墙以及串口文件权限会重置,需要在安卓源码中修改文件权限再编译烧写

    二、Linux 串口操作

    Linux一切皆文件,安卓源码就是在我虚拟机上编译,所以想到可以使用Linux方式对串口文件进行读写,网上查找的确可行,还是需要将串口文件及防火墙进行修改

    具体代码:

    int fd = open("/dev/ttymxc2",O_RDWR|O_NOCTTY|O_NDELAY) ; //打开串口设备
    struct termios options;
    options.c_cflag|=(CLOCAL|CREAD ); 
    options.c_cflag &=~CSIZE;
    options.c_cflag |= CS8; //设置8位数据位
    options.c_cflag &= ~PARENB; //无校验位
    /* 设置115200波特率  */
    cfsetispeed(&options, B115200);
    cfsetospeed(&options, B115200);
    options.c_cflag &= ~CSTOPB;/* 设置一位停止位; */
    options.c_cc[VTIME] = 0;/* 读取时的超时时间;*/
    options.c_cc[VMIN]  = 0; /* 读取时的最小字符数*/
    tcflush(fd ,TCIFLUSH);/* tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */
    if((tcsetattr(fd, TCSANOW,&options))!=0){
        return;}

    之后可以使用定时器定时读取串口

    char readbuf[500];
    int length = read(fd,readbuf,500);

    也可以使用IO多路复用select方式监听该文件描述符是否有IO操作来进行读写

    while (1)
    {
        fd_set r_set;
        FD_ZERO(&r_set) ;
        FD_SET(fd, &r_set) ;
        int r = select(fd+1, &r_set, nullptr, nullptr, nullptr) ;
        if(r <= 0){
            return;
        }  
        memset(readbuf, 0, sizeof(readbuf)) ;
        int i = read(fd, readbuf, sizeof(readbuf)) ;
        if(i > 0)
        {
        QByteArray arr(readbuf,i);
        qDebug()<<arr.toHex().toUpper();
        }
    }

     

    更多相关内容
  • Qt Android自动更新

    2021-03-23 21:31:09
    使用Qt的JNI调用Android的java更新代码实现自动更新
  • 含:sdk,jdk,ndk,nat,qt5.9,压缩包合集。完美版!!!!!!!!!!
  • Qt Android openssl支持5.14以上版本,官方下到的,具体链接找不到了所以上传备份一下,给有需要的同学
  • 有关如何在Android上构建Qt / C ++项目的分步教程
  • QT Android ADB手机助手

    2019-07-23 10:49:14
    QTAndroid 手机助手,使用简洁方便,控制Android手机。
  • Qt Android 调用JAVA

    2016-01-18 21:00:55
    Qt for android 调用Java 代码的示例程序
  • Qt android 发短信,查看短信
  • Qt android 浅析

    2020-12-19 02:57:26
    Qt android 浅析Qt5支持编写Android应用。典型main:int main(int argc, char *argv[]){QApplication a(argc, argv);MainWindow w;w.show();return a.exec();}这会在Android设备上显示一个空白窗口。但是:问题1,...

    Qt android 浅析

    Qt5支持编写Android应用。

    典型main:

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

    {

    QApplication a(argc, argv);

    MainWindow w;

    w.show();

    return a.exec();

    }

    这会在Android设备上显示一个空白窗口。

    但是:

    问题1, main函数“冲突”。我们知道Android进程源于zygote的fork,作为进程入口的函数main早就执行过了,那么上述代码这中Qt的入口函数main又何时怎么被执行的呢?

    问题2,Android activity的生命周期是如何转化为qt的生命周期的?

    问题3,qt主线程和Android主线程的关系是什么样的?

    helloworld工程分析

    在qtcreator新建一个helloworld的工程,编译后,在qt的build目录下,可以看到目录结构:

    - android_build/

    - libhelloworld.so

    - main.o

    - mainwindow.o

    .o文件显然对应各个cpp文件,so文件是.o文件的“集合”。android-build的目录经过简单的查看,可以知道是一个gradle组织的android工程。

    所以qt的Android支持,简单看就是将我们写的qt代码生成so文件,并通过自动生成的Android模板工程来最终生成一个apk文件。

    看下android_build中的build.gradle:

    ……

    sourceSets {

    main {

    manifest.srcFile 'AndroidManifest.xml'

    java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']

    aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']

    res.srcDirs = [qt5AndroidDir + '/res', 'res']

    resources.srcDirs = ['src']

    renderscript.srcDirs = ['src']

    assets.srcDirs = ['assets']

    jniLibs.srcDirs = ['libs']

    }

    }

    ……

    build.gradle中通过sourceSets调整了com.android.application插件的默认源码、资源路径。

    主要引入了qt5AndroidDir目录下的src、aidl和res。

    qt5AndroidDir定义在gradle.properties:

    qt5AndroidDir=/Users/xxxx/Qt5.10.1/5.10.1/android_armv7/src/android/java

    一般指向qt安装目录中的android_armv7/src/android/java.

    看下libs目录:

    libs

    ├── QtAndroid-bundled.jar

    └── armeabi-v7a

    ├── gdbserver

    ├── libQt5Core.so

    ├── libQt5Gui.so

    ├── libQt5Widgets.so

    ├── libgdbserver.so

    ├── libgnustl_shared.so

    ├── libhelloworld.so

    ├── libplugins_imageformats_libqgif.so

    ├── libplugins_imageformats_libqicns.so

    ├── libplugins_imageformats_libqico.so

    ├── libplugins_imageformats_libqjpeg.so

    ├── libplugins_imageformats_libqtga.so

    ├── libplugins_imageformats_libqtiff.so

    ├── libplugins_imageformats_libqwbmp.so

    ├── libplugins_imageformats_libqwebp.so

    ├── libplugins_platforms_android_libqtforandroid.so

    └── libplugins_styles_libqandroidstyle.so

    qt运行所需的几个核心so拷贝被拷贝到了libs目录下,这样就会被打包到最终的apk中。还引入了一个QtAndroid-bundled.jar依赖。

    总结

    qt编写的代码会生成为一个动态库,以工程名命名

    qt生成Android应用的策略是借助了一个模板工程

    不难猜测,qt应用的运行模式是,通过模板工程生成的Android应用,以native调用方式执行qt代码

    启动流程

    上节分析可知,Android代码主导了整个进程的运行。那么寻找Qt应用入口就从Android代码入手。

    Android应用入口一般是Application,模板工程android-build中,QtApplication继承了Applicaiton,但是浏览一遍并没有发现加载libhelloworld.so的地方,先略过。

    Application加载后会会执行“主Activity”,也就是指定有category.LAUNCHER的那个Activity,在模板工程中是QtActivity。

    这样,主Activity的onCreate就不亚于第二入口:

    @Override

    public void onCreate(Bundle savedInstanceState)

    {

    super.onCreate(savedInstanceState);

    onCreateHook(savedInstanceState);

    }

    onCreateHook

    protected void onCreateHook(Bundle savedInstanceState) {

    m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;

    m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;

    m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;

    m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;

    m_loader.onCreate(savedInstanceState);

    }

    这里看到的m_loader是类QtActivityLoader

    Loader

    QtActivityLoader.create:

    ……

    m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;

    ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME

    + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";

    if (null == m_activity.getLastNonConfigurationInstance()) {//代码分析看应该总是null,可能是预留的功能吧

    if (m_contextInfo.metaData.containsKey("android.app.background_running")

    && m_contextInfo.metaData.getBoolean("android.app.background_running")) {

    ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";

    } else {

    ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";

    }

    if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor")

    && m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {

    ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";

    }

    startApp(true);//上面大部分只是在设置ENVIRONMENT_VARIABLES,这里是关键

    }

    找到一个亲切的函数——startApp:

    //查AndroidManifest.xml,易得,

    if (m_contextInfo.metaData.containsKey("android.app.use_local_qt_libs")

    && m_contextInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) {

    ……//根据AndroidManifest和ENVIRONMENT_VARIABLES的值来配置loaderParams,此处省略一万字

    //这里调用loaderClassName()设置LOADER_CLASS_NAME,对于QtActivityLoader是org.qtproject.qt5.android.QtActivityDelegate(loaderClassName()函数的名字取得不准确,个人更趋向于叫delegaterClassName())

    loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());

    loadApplication(loaderParams);

    return;

    }

    //如果不使用本地qt库,则绑定ministro的服务,并且在绑定后会启动下载流程

    if (!m_context.bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()),

    m_ministroConnection,

    Context.BIND_AUTO_CREATE)) {

    throw new SecurityException("");

    }

    startApp处理了是否需要ministro介入Qt启动流程的事,ministro可以不在应用中嵌入qt库,而是在运行的时候去下载必要库。QtCreator创建的工程生成的apk是自带qt库的,读者可以忽略ministro.

    最后startApp调用了loadApplication——加载Qt库并运行:

    ……

    //这里上文分析了,加载到的是类是:org.qtproject.qt5.android.QtActivityDelegate

    Class> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class

    Object qtLoader = loaderClass.newInstance(); // create an instance

    //反射调用loadApplication

    Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",

    contextClassName(),

    ClassLoader.class,

    Bundle.class);

    if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))

    throw new Exception("");

    QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);

    //这里会加载libhelloworld.so,以及它依赖的其他库,如libQt5Core.so等

    if (libName != null)

    System.loadLibrary(libName);

    //反射调用startApplication

    Method startAppMethod=qtLoader.getClass().getMethod("startApplication");

    if (!(Boolean)startAppMethod.invoke(qtLoader))

    throw new Exception("");

    这里涉及qt Android封装中一个重要的类——delegate。对于QtActivity而言是QtActivityDelegate.

    到此为止,接触了QtActivity, QtActivityLoader , QtActivityDeleagate,这3个类是其Android封装中很重要的类,注意梳理3个类间关系。

    startApp主要工作是找到loaderClass(成为delegate更合适),然后调用它的loadApplication和startApplication.

    Delegate

    loadApplication主要用来读取loadParams,并设置一些全局值,不是本文重点,不深入分析。

    startApplication是重头戏:

    //qtbase-5.10.1/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java

    public boolean startApplication() {

    ……

    if (null == m_surfaces)

    onCreate(null);

    ……

    }

    public void onCreate(Bundle savedInstanceState) {

    ……

    //创建一个名字是startApplication的“回调”

    startApplication = new Runnable() {

    @Override

    public void run() {

    try {

    String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity);

    //调用QtNative.startApplication

    QtNative.startApplication(m_applicationParameters,

    m_environmentVariables,

    m_mainLib,

    nativeLibraryDir);

    m_started = true;

    } catch (Exception e) {

    e.printStackTrace();

    m_activity.finish();

    }

    }

    };

    //创建一个布局,startApplication回调将在QtLayout.onSizeChanged重载中调用

    m_layout = new QtLayout(m_activity, startApplication);//QtLayout extends ViewGroup

    ……

    //设置为ContentView

    m_activity.setContentView(m_layout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

    ……

    }

    QtActivityDelegate的startApplication调用了自己的onCreate,onCreate中主要是创建一个QtLayout,作为contentView,并在QtLayout onSizeChanged的时候调用局部变量startApplication指向的回调。然后启动任务抛给了QtNative.startApplication.

    QtNative.startApplication:

    public static boolean startApplication(String params,

    String environment,

    String mainLibrary,

    String nativeLibraryDir) throws Exception{

    synchronized (m_mainActivityMutex) {

    res = startQtAndroidPlugin();

    ……

    startQtApplication(f.getAbsolutePath() + params, environment);//native startQtApplication

    m_started = true;

    }

    }

    public static native boolean startQtAndroidPlugin();

    public static native void startQtApplication(String params, String env);

    调到了native方法,实现在qtbase-5.10.1/src/plugins/platforms/android/androidjnimain.cpp:

    static jboolean startQtAndroidPlugin(JNIEnv* /*env*/, jobject /*object*//*, jobject applicationAssetManager*/)

    {

    m_androidPlatformIntegration = nullptr;

    m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();

    return true;

    }

    static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString)

    {

    //通过dlopen+dlsym定位libhelloworld.so中的main函数,也就是qt代码的main函数

    m_mainLibraryHnd = dlopen(m_applicationParams.constFirst().data(), 0);

    if (Q_UNLIKELY(!m_mainLibraryHnd)) {

    qCritical() << "dlopen failed:" << dlerror();

    return false;

    }

    m_main = (Main)dlsym(m_mainLibraryHnd, "main");

    ……

    //创建线程调用startMainMethod

    jboolean res = pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0;

    ……

    }

    static void *startMainMethod(void */*data*/)

    {

    ……

    int ret = m_main(m_applicationParams.length(), const_cast(params.data()));

    ……

    }

    这里native代码中的startQtApplication通过dlsym定位了qt代码中的main函数,然后创建了一个线程来执行这个main函数的代码(所以qt主线程,并不是Android的主线程)。而main函数,我们知道会一直执行QApplication.exec()直到退出。

    至此,qt工程生成的libhelloworld.so开始执行main函数,开始表现得就像传统的桌面qt应用一样。

    让我们梳理下。

    总结

    主要的启动流程是QtActivity.create -> QtActivityLoader.startApp -> QtActivityDelegate.startApplication -> QtNative.startApplicaiton

    qt的主线程并不是Android的主线程,二者相互独立

    qt的main函数并不是android应用中的入口函数,只是用作android代码启动qt代码的调用入口(其实c语言中的main也有异曲同工之处)

    AndroidManifest中留了很多“启动”qt代码的参数

    退出流程

    入口找到了,找下出口。

    传统qt应用,会在主窗口关闭后,事件循环结束,然后QApplication.exec()退出,主函数退出。

    Android中qt创建的“窗口”实际上是Activity中的一块Surface。而且我们知道Android Activity的生命周期onDestroy算是退出。但,也不尽然,因为onDestroy后,进程可以驻留后台等待系统唤起。

    那么,qt的Android封装是如何对接的?

    先看其QtActivity的lauchMode:android:launchMode="singleTop"

    singleTop,简而言之:如果已经在栈顶,复用,否则,重新创建。这意味着,一旦QtActivity退到其他Activity后面,下次回到栈顶就需要重新创建。

    所以onDestroy是一个很合理的“退出点”。这和之前的onCreate作为启动入口正好在Android生命周期上是对应的。

    onDestroy之前onStop会先触发。

    @Override

    protected void onStop()

    {

    super.onStop();

    QtApplication.invokeDelegate();//??

    }

    @Override

    protected void onDestroy()

    {

    super.onDestroy();

    QtApplication.invokeDelegate();//??

    }

    onStop和onDestropy都是一句QtApplication.invokeDelegate搞定,挺优雅的实现,不过对于分析流程而言有点障碍,我们先看下究竟invokeDelegate做了什么?

    分析别人代码,笔者喜欢猜流程。

    比如这里,之前分析启动流程的时候QtActivity是一个壳,调用QtActivityLoader帮忙加载libhelloworld.so,而真正的又处理发生在QtActivityDelegate.这和QtApplication.invokeDelegate应该不会只是名字上的巧合。

    可以合理猜测QtApplication.invokeDelegate的任务是“优雅”地把QtActivity要做的事委托给QtActivityDelegate。

    带着这个猜测分析看看吧。

    QtApplication.invokeDelegate

    private static int stackDeep=-1;

    public static InvokeResult invokeDelegate(Object... args)

    {

    InvokeResult result = new InvokeResult();

    if (m_delegateObject == null)

    return result;

    //通过调用栈查找要被代理的函数

    StackTraceElement[] elements = Thread.currentThread().getStackTrace();

    if (-1 == stackDeep) {

    for (int it=0;it

    //activityClassName在QtLoader.loadApplication中被设置为QtActivity

    if (elements[it].getClassName().equals(activityClassName)) {

    stackDeep = it;

    break;

    }

    }

    if (-1 == stackDeep)

    return result;

    final String methodName=elements[stackDeep].getMethodName();

    if (!m_delegateMethods.containsKey(methodName))

    return result;

    //从m_delegateMethods表中查找要调用的函数,并执行

    for (Method m : m_delegateMethods.get(methodName)) {

    if (m.getParameterTypes().length == args.length) {

    result.methodReturns = invokeDelegateMethod(m, args);

    result.invoked = true;

    return result;

    }

    }

    return result;

    }

    invokeDelegate根据被代理函数的名字,查表调用了代理函数。

    看下m_delegateMethods的赋值:

    //QtLoader.loadApplication中调用到:QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);

    //m_delegateClass = QtActivity.class

    //qtLoader = new QtActivityDelegate

    public static void setQtContextDelegate(Class> clazz, Object listener)

    {

    m_delegateObject = listener;//代理类设为QtActivityDelegate

    activityClassName = clazz.getCanonicalName();//activityClassName设为QtActivity

    //反射获取QtActivityDelegate的方法

    ArrayList delegateMethods = new ArrayList();

    for (Method m : listener.getClass().getMethods()) {

    if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android"))

    delegateMethods.add(m);

    }

    //反射获取QtApplication的字段

    ArrayList applicationFields = new ArrayList();

    for (Field f : QtApplication.class.getFields()) {

    if (f.getDeclaringClass().getName().equals(QtApplication.class.getName()))

    applicationFields.add(f);

    }

    //关联代理方法

    //1. 关联到m_delegateMethods表

    //2. 关联到QtApplication的字段,如Method onKeyUp

    for (Method delegateMethod : delegateMethods) {

    try {

    clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());

    //1. 关联到m_delegateMethods表

    if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) {

    QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);

    } else {

    ArrayList delegateSet = new ArrayList();

    delegateSet.add(delegateMethod);

    QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet);

    }

    //2. 关联到QtApplication的字段,如Method onKeyUp

    for (Field applicationField:applicationFields) {

    if (applicationField.getName().equals(delegateMethod.getName())) {

    try {

    applicationField.set(null,

    展开全文
  • Qt android 打印调试

    千次阅读 2022-02-14 22:35:06
    文章目录安卓调试安卓日志adb 加入pathadb 查看日志将Qt调试信息输出到logcat中工程中使用参考文档 推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,...在上一章中《Qt android 开发


    推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家: Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

    安卓开发,目前主要在windows下开发,虽然可以调试,但是有些库不好调试,只能经过打印来进行。
    在上一章中《Qt android 开发环境搭建》中正常搭建环境,按F5就能对工程进行调试。或者如下图:
    调试按钮
    进行调试

    安卓调试

    安卓日志

    adb 加入path

    找到adb所在的路径,并将其加入PATH中。

    adb未加入PATH在这里插入图片描述
    加入PATH中
    在这里插入图片描述
    重新打开cmd窗口,adb正常
    在这里插入图片描述

    adb 查看日志

    adb logcat
    在这里插入图片描述
    这样的日志,太多查看起来比较麻烦。
    我们可以写入文件中,再通过工具来进行分析,查找。
    adb logcat > log.log
    使用notepad++ 打开log.log
    在这里插入图片描述

    将Qt调试信息输出到logcat中

    CMakeLists.txt

    # 需要添加这一句
    target_link_libraries( android_vlc PRIVATE log )
    

    log.h

    #ifndef __LOG_H__
    #ifndef __LOG_H__
    
    #ifdef ANDROID
    void installLogcatMessageHandler(const char *TAG);
    #else
    #define installLogcatMessageHandler(TAG)
    #endif
    
    #endif
    
    

    log.cpp

    
    #if defined(ANDROID)
    #include "log.h"
    #include <android/log.h>
    #include <QDebug>
    #include <QByteArray>
    
    static const char *g_TAG = 0;
    static void messageOutput2Logcat(QtMsgType type,
        const QMessageLogContext &context,
        const QString &msg)
    {
        int prio = ANDROID_LOG_VERBOSE;
        QByteArray localMsg = msg.toLocal8Bit();
        switch (type) {
        case QtDebugMsg:
            prio = ANDROID_LOG_DEBUG;
            break;
        case QtWarningMsg:
            prio = ANDROID_LOG_WARN;
            break;
        case QtCriticalMsg:
            prio = ANDROID_LOG_INFO;
            break;
        case QtFatalMsg:
            prio = ANDROID_LOG_FATAL;
            abort();
        }
        __android_log_write(prio, g_TAG, localMsg.data());
    }
    
    void installLogcatMessageHandler(const char *TAG)
    {
        g_TAG = (TAG == 0 ? "QDebug" : TAG);
        qInstallMessageHandler(messageOutput2Logcat);
    }
    
    #endif
    
    

    在main中使用
    main.cpp

    #include "log.h"
    #include <QApplication>
    #include <QDebug>
    #include <QMutex>
    
    int main()
    {
    	QApplication a(argc, argv);
        installLogcatMessageHandler("android_log");
        qDebug() << "Debug";
        qWarning() << "qWarning";
        qInfo() << "qInfo";
        qCritical() << "qCritical";
        return a.exec();
    }
    
    

    测试
    在这里插入图片描述

    工程中使用

    开发中,Qt android生成的可执行的工程包,是一个.so文件,最终打包成一个apk,这里如果工程中有多个.so 就不好去单独调试了,这里就更好的去使用日志打印输入。来跟进项目

    参考文档

    Qt on Android:将Qt调试信息输出到logcat中
    QT for android编写的程序如何输出调试信息到android的log日志

    展开全文
  • Qt+android环境配置

    2019-03-14 10:34:25
    罗马尼亚开发者Bogdan Vatra 宣布了Android移动操作系统首个alpha版本的Qt实现。与微软合作的Nokia分支宣布过不会开发一个类似Window Phone的GUI构架,Qt for Android是用于在Android上进行开发的Qt,也意味着唯一...
  • Qt android浅析

    千次阅读 2019-01-25 15:41:10
    转自:... Qt5支持编写Android应用。 典型main: int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } 这会在...

    转自:https://zhuanlan.zhihu.com/p/36798160

    Qt5支持编写Android应用。

    典型main

    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        MainWindow w;
        w.show();
    
        return a.exec();
    }
    

    这会在Android设备上显示一个空白窗口。

    但是:

    • 问题1, main函数“冲突”。我们知道Android进程源于zygote的fork,作为进程入口的函数main早就执行过了,那么上述代码这中Qt的入口函数main又何时怎么被执行的呢?
    • 问题2,Android activity的生命周期是如何转化为qt的生命周期的?
    • 问题3,qt主线程和Android主线程的关系是什么样的?

    以下基于Qt 5.10.1分析(源码参考https://github.com/qt/qtbase/releases/tag/v5.10.1)。

    helloworld工程分析

    在qtcreator新建一个helloworld的工程,编译后,在qt的build目录下,可以看到目录结构:

    - android_build/
    - libhelloworld.so
    - main.o
    - mainwindow.o
    

    .o文件显然对应各个cpp文件,so文件是.o文件的“集合”。android-build的目录经过简单的查看,可以知道是一个gradle组织的android工程。

    所以qt的Android支持,简单看就是将我们写的qt代码生成so文件,并通过自动生成的Android模板工程来最终生成一个apk文件。

    看下android_build中的build.gradle:

    ……
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
            aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
            res.srcDirs = [qt5AndroidDir + '/res', 'res']
            resources.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
       }
    }
    ……
    

    build.gradle中通过sourceSets调整了com.android.application插件的默认源码、资源路径。

    主要引入了qt5AndroidDir目录下的srcaidlres

    qt5AndroidDir定义在gradle.properties

    qt5AndroidDir=/Users/xxxx/Qt5.10.1/5.10.1/android_armv7/src/android/java
    

    一般指向qt安装目录中的android_armv7/src/android/java.

    看下libs目录:

    libs
    ├── QtAndroid-bundled.jar
    └── armeabi-v7a
        ├── gdbserver
        ├── libQt5Core.so
        ├── libQt5Gui.so
        ├── libQt5Widgets.so
        ├── libgdbserver.so
        ├── libgnustl_shared.so
        ├── libhelloworld.so
        ├── libplugins_imageformats_libqgif.so
        ├── libplugins_imageformats_libqicns.so
        ├── libplugins_imageformats_libqico.so
        ├── libplugins_imageformats_libqjpeg.so
        ├── libplugins_imageformats_libqtga.so
        ├── libplugins_imageformats_libqtiff.so
        ├── libplugins_imageformats_libqwbmp.so
        ├── libplugins_imageformats_libqwebp.so
        ├── libplugins_platforms_android_libqtforandroid.so
        └── libplugins_styles_libqandroidstyle.so
    

    qt运行所需的几个核心so拷贝被拷贝到了libs目录下,这样就会被打包到最终的apk中。还引入了一个QtAndroid-bundled.jar依赖。

    总结

    • qt编写的代码会生成为一个动态库,以工程名命名
    • qt生成Android应用的策略是借助了一个模板工程
    • 不难猜测,qt应用的运行模式是,通过模板工程生成的Android应用,以native调用方式执行qt代码

     

    启动流程

    上节分析可知,Android代码主导了整个进程的运行。那么寻找Qt应用入口就从Android代码入手。

    Android应用入口一般是Application,模板工程android-build中,QtApplication继承了Applicaiton,但是浏览一遍并没有发现加载libhelloworld.so的地方,先略过。

    Application加载后会会执行“主Activity”,也就是<intent-filter>指定有category.LAUNCHER的那个Activity,在模板工程中是QtActivity

    这样,主Activity的onCreate就不亚于第二入口:

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        onCreateHook(savedInstanceState);
    }
    

    onCreateHook

    protected void onCreateHook(Bundle savedInstanceState) {
        m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
        m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
        m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
        m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
        m_loader.onCreate(savedInstanceState);
    }
    

    这里看到的m_loader是类QtActivityLoader

    Loader

    QtActivityLoader.create:

    ……
    m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;
    
    ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
            + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
    
    if (null == m_activity.getLastNonConfigurationInstance()) {//代码分析看应该总是null,可能是预留的功能吧
        if (m_contextInfo.metaData.containsKey("android.app.background_running")
                && m_contextInfo.metaData.getBoolean("android.app.background_running")) {
            ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
        } else {
            ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
        }
    
        if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor")
                && m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {
            ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";
        }
    
        startApp(true);//上面大部分只是在设置ENVIRONMENT_VARIABLES,这里是关键
    }
    

    找到一个亲切的函数——startApp:

    //查AndroidManifest.xml,易得, <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
    if (m_contextInfo.metaData.containsKey("android.app.use_local_qt_libs")
                        && m_contextInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) {
        ……//根据AndroidManifest和ENVIRONMENT_VARIABLES的值来配置loaderParams,此处省略一万字
        //这里调用loaderClassName()设置LOADER_CLASS_NAME,对于QtActivityLoader是org.qtproject.qt5.android.QtActivityDelegate(loaderClassName()函数的名字取得不准确,个人更趋向于叫delegaterClassName())
        loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());
        loadApplication(loaderParams);
        return;
    }
    
    //如果不使用本地qt库,则绑定ministro的服务,并且在绑定后会启动下载流程
    if (!m_context.bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()),
            m_ministroConnection,
            Context.BIND_AUTO_CREATE)) {
        throw new SecurityException("");
    }
    

    startApp处理了是否需要ministro介入Qt启动流程的事,ministro可以不在应用中嵌入qt库,而是在运行的时候去下载必要库。QtCreator创建的工程生成的apk是自带qt库的,读者可以忽略ministro.

    最后startApp调用了loadApplication——加载Qt库并运行:

    ……
    //这里上文分析了,加载到的是类是:org.qtproject.qt5.android.QtActivityDelegate
    Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
    Object qtLoader = loaderClass.newInstance(); // create an instance
    
    //反射调用loadApplication
    Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
            contextClassName(),
            ClassLoader.class,
            Bundle.class);
    if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))
        throw new Exception("");
    
    QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
    
    //这里会加载libhelloworld.so,以及它依赖的其他库,如libQt5Core.so等
    if (libName != null)
        System.loadLibrary(libName);
    
    //反射调用startApplication
    Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
    if (!(Boolean)startAppMethod.invoke(qtLoader))
        throw new Exception("");
    

    这里涉及qt Android封装中一个重要的类——delegate。对于QtActivity而言是QtActivityDelegate.

    到此为止,接触了QtActivity, QtActivityLoader , QtActivityDeleagate,这3个类是其Android封装中很重要的类,注意梳理3个类间关系。

    startApp主要工作是找到loaderClass(成为delegate更合适),然后调用它的loadApplicationstartApplication.

    Delegate

    loadApplication主要用来读取loadParams,并设置一些全局值,不是本文重点,不深入分析。

    startApplication是重头戏:

    //qtbase-5.10.1/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
    public boolean startApplication() {
        ……
        if (null == m_surfaces)
            onCreate(null);
        ……
    }
    
    public void onCreate(Bundle savedInstanceState) {
        ……
        //创建一个名字是startApplication的“回调”
        startApplication = new Runnable() {
            @Override
            public void run() {
                try {
                    String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity);
                    //调用QtNative.startApplication
                    QtNative.startApplication(m_applicationParameters,
                        m_environmentVariables,
                        m_mainLib,
                        nativeLibraryDir);
                    m_started = true;
                } catch (Exception e) {
                    e.printStackTrace();
                    m_activity.finish();
                }
            }
        };
    
        //创建一个布局,startApplication回调将在QtLayout.onSizeChanged重载中调用
        m_layout = new QtLayout(m_activity, startApplication);//QtLayout extends ViewGroup
        ……
    
        //设置为ContentView
        m_activity.setContentView(m_layout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                       ViewGroup.LayoutParams.MATCH_PARENT));
    
        ……
    }
    

    QtActivityDelegatestartApplication调用了自己的onCreateonCreate中主要是创建一个QtLayout,作为contentView,并在QtLayout onSizeChanged的时候调用局部变量startApplication指向的回调。然后启动任务抛给了QtNative.startApplication.

    QtNative.startApplication:

    public static boolean startApplication(String params,
                                               String environment,
                                               String mainLibrary,
                                           String nativeLibraryDir) throws Exception{
        synchronized (m_mainActivityMutex) {
            res = startQtAndroidPlugin();
            ……
            startQtApplication(f.getAbsolutePath() + params, environment);//native startQtApplication
            m_started = true;
        }
    }
    
    public static native boolean startQtAndroidPlugin();
    public static native void startQtApplication(String params, String env);
    

    调到了native方法,实现在qtbase-5.10.1/src/plugins/platforms/android/androidjnimain.cpp:

    static jboolean startQtAndroidPlugin(JNIEnv* /*env*/, jobject /*object*//*, jobject applicationAssetManager*/)
    {
        m_androidPlatformIntegration = nullptr;
        m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();
        return true;
    }
    
    static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString)
    {
        //通过dlopen+dlsym定位libhelloworld.so中的main函数,也就是qt代码的main函数
        m_mainLibraryHnd = dlopen(m_applicationParams.constFirst().data(), 0);
        if (Q_UNLIKELY(!m_mainLibraryHnd)) {
            qCritical() << "dlopen failed:" << dlerror();
            return false;
        }
        m_main = (Main)dlsym(m_mainLibraryHnd, "main");
    
        ……
    
        //创建线程调用startMainMethod
        jboolean res = pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0;
        ……
    }
    
    static void *startMainMethod(void */*data*/)
    {
        ……
        int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data()));
        ……
    }
    

    这里native代码中的startQtApplication通过dlsym定位了qt代码中的main函数,然后创建了一个线程来执行这个main函数的代码(所以qt主线程,并不是Android的主线程)。而main函数,我们知道会一直执行QApplication.exec()直到退出。

    至此,qt工程生成的libhelloworld.so开始执行main函数,开始表现得就像传统的桌面qt应用一样。

    让我们梳理下。

    总结

    • 主要的启动流程是QtActivity.create -> QtActivityLoader.startApp -> QtActivityDelegate.startApplication -> QtNative.startApplicaiton
    • qt的主线程并不是Android的主线程,二者相互独立
    • qt的main函数并不是android应用中的入口函数,只是用作android代码启动qt代码的调用入口(其实c语言中的main也有异曲同工之处)
    • AndroidManifest中留了很多“启动”qt代码的参数

     

    退出流程

    入口找到了,找下出口。

    传统qt应用,会在主窗口关闭后,事件循环结束,然后QApplication.exec()退出,主函数退出。

    Android中qt创建的“窗口”实际上是Activity中的一块Surface。而且我们知道Android Activity的生命周期onDestroy算是退出。但,也不尽然,因为onDestroy后,进程可以驻留后台等待系统唤起。

    那么,qt的Android封装是如何对接的?

    先看其QtActivity的lauchMode:android:launchMode="singleTop"

    singleTop,简而言之:如果已经在栈顶,复用,否则,重新创建。这意味着,一旦QtActivity退到其他Activity后面,下次回到栈顶就需要重新创建。

    所以onDestroy是一个很合理的“退出点”。这和之前的onCreate作为启动入口正好在Android生命周期上是对应的。

    onDestroy之前onStop会先触发。

    @Override
    protected void onStop()
    {
        super.onStop();
        QtApplication.invokeDelegate();//??
    }
    
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        QtApplication.invokeDelegate();//??
    }
    

    onStop和onDestropy都是一句QtApplication.invokeDelegate搞定,挺优雅的实现,不过对于分析流程而言有点障碍,我们先看下究竟invokeDelegate做了什么?

    分析别人代码,笔者喜欢猜流程。

    比如这里,之前分析启动流程的时候QtActivity是一个壳,调用QtActivityLoader帮忙加载libhelloworld.so,而真正的又处理发生在QtActivityDelegate.这和QtApplication.invokeDelegate应该不会只是名字上的巧合。

    可以合理猜测QtApplication.invokeDelegate的任务是“优雅”地把QtActivity要做的事委托给QtActivityDelegate。
    带着这个猜测分析看看吧。

    QtApplication.invokeDelegate

    private static int stackDeep=-1;
    public static InvokeResult invokeDelegate(Object... args)
    {
        InvokeResult result = new InvokeResult();
        if (m_delegateObject == null)
            return result;
        //通过调用栈查找要被代理的函数
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        if (-1 == stackDeep) {
            for (int it=0;it<elements.length;it++)
                //activityClassName在QtLoader.loadApplication中被设置为QtActivity
                if (elements[it].getClassName().equals(activityClassName)) {
                    stackDeep = it;
                    break;
                }
        }
        if (-1 == stackDeep)
            return result;
        final String methodName=elements[stackDeep].getMethodName();
        if (!m_delegateMethods.containsKey(methodName))
            return result;
        //从m_delegateMethods表中查找要调用的函数,并执行
        for (Method m : m_delegateMethods.get(methodName)) {
            if (m.getParameterTypes().length == args.length) {
                result.methodReturns = invokeDelegateMethod(m, args);
                result.invoked = true;
                return result;
            }
        }
        return result;
    }
    

    invokeDelegate根据被代理函数的名字,查表调用了代理函数。

    看下m_delegateMethods的赋值:

    //QtLoader.loadApplication中调用到:QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
    //m_delegateClass = QtActivity.class
    //qtLoader = new QtActivityDelegate
    public static void setQtContextDelegate(Class<?> clazz, Object listener)
    {
        m_delegateObject = listener;//代理类设为QtActivityDelegate
        activityClassName = clazz.getCanonicalName();//activityClassName设为QtActivity
    
        //反射获取QtActivityDelegate的方法
        ArrayList<Method> delegateMethods = new ArrayList<Method>();
        for (Method m : listener.getClass().getMethods()) {
            if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android"))
                delegateMethods.add(m);
        }
    
        //反射获取QtApplication的字段
        ArrayList<Field> applicationFields = new ArrayList<Field>();
        for (Field f : QtApplication.class.getFields()) {
            if (f.getDeclaringClass().getName().equals(QtApplication.class.getName()))
                applicationFields.add(f);
        }
    
        //关联代理方法
        //1. 关联到m_delegateMethods表
        //2. 关联到QtApplication的字段,如Method onKeyUp
        for (Method delegateMethod : delegateMethods) {
            try {
                clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
                //1. 关联到m_delegateMethods表
                if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) {
           QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);
                } else {
                    ArrayList<Method> delegateSet = new ArrayList<Method>();
                    delegateSet.add(delegateMethod);
                    QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet);
                }
                //2. 关联到QtApplication的字段,如Method onKeyUp
                for (Field applicationField:applicationFields) {
                    if (applicationField.getName().equals(delegateMethod.getName())) {
                        try {
                            applicationField.set(null, delegateMethod);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
    }
    

    加了注释应该比较容易理解作者的意图了。这里交叉对比了QtActivityQtAcitivtyDelegate的方法,然后构建了一个“代理方法表”,以方便在全局范围内,通过QtApplication.invokeDelegate调用。

    现在,继续之前的退出流程分析。

    onStop

    QtActivity.onStop被QtActivityDelegate.onStop代理:

    //QtActivityDelegate.onStop
    public void onStop()
    {
        QtNative.setApplicationState(ApplicationSuspended);//ApplicationSuspended=0
    }
    
    //QtNative.setApplicationState
    public static void setApplicationState(int state)
    {
        synchronized (m_mainActivityMutex) {
            switch (state) {
                case QtActivityDelegate.ApplicationActive:
                    m_activityPaused = false;
                    Iterator<Runnable> itr = m_lostActions.iterator();
                    while (itr.hasNext())
                        runAction(itr.next());
                    m_lostActions.clear();
                    break;
                default:
                    m_activityPaused = true;
                    break;
            }
        }
        updateApplicationState(state);//我们关注下这个
    }
    
    public static native void updateApplicationState(int state);
    

    跟native代码:

    //androidjnimain.cpp
    /*
    enum ApplicationState {
            ApplicationSuspended    = 0x00000000,
            ApplicationHidden       = 0x00000001,
            ApplicationInactive     = 0x00000002,
            ApplicationActive       = 0x00000004
        };
    */
    static void updateApplicationState(JNIEnv */*env*/, jobject /*thiz*/, jint state)
    {
        ……
    
        if (state == Qt::ApplicationActive)
            QtAndroidPrivate::handleResume();
        else if (state == Qt::ApplicationInactive)
            QtAndroidPrivate::handlePause();
    
        if (state <= Qt::ApplicationInactive) {
            if (QAndroidEventDispatcherStopper::instance()->stopped())
                return;
            QAndroidEventDispatcherStopper::instance()->goingToStop(true);
            QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state));
            if (state == Qt::ApplicationSuspended)//onStop会触发这个分支
                QAndroidEventDispatcherStopper::instance()->stopAll();
        }
        else {
            ……
        }
    }
    

    这里主要调用了QAndroidEventDispatcherStopper::instance()->stopAll();,native代码不是重点,不深入分析QAndroidEventDispatcherStopper了。这里stopAll会导致qt main函数中QtApplicaiton.exec循环退出。

    循环退出后,qt主线程:

    static void *startMainMethod(void */*data*/)
    {
        ……
        int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data()));
    
        ……
    
        sem_post(&m_terminateSemaphore);
        sem_wait(&m_exitSemaphore);//在这里等待信号量
        sem_destroy(&m_exitSemaphore);
    
        // We must call exit() to ensure that all global objects will be destructed
        exit(ret);
        return 0;
    }
    

    “主函数”退出后,会发出m_terminateSemaphore信号,并等待m_exitSemaphore信号。直到m_exitSemaphore信号到来前,线程还不会退出。

    onDestroy

    QtActivity.onDestroy被QtActivityDelegate.onDestroy代理:

    //QtActivityDelegate.onDestroy
    public void onDestroy()
    {
        if (m_quitApp) {
            QtNative.terminateQt();
            QtNative.setActivity(null, null);
            if (m_debuggerProcess != null)
                m_debuggerProcess.destroy();
            System.exit(0);// FIXME remove it or find a better way
        }
    }
    
    //QtNative.terminateQt
    public static native void terminateQt();
    

    跟native代码:

    //androidjnimain.cpp
    static void terminateQt(JNIEnv *env, jclass /*clazz*/)
    {
        // QAndroidEventDispatcherStopper is stopped when the user uses the task manager to kill the application
        if (!QAndroidEventDispatcherStopper::instance()->stopped()) {
            sem_wait(&m_terminateSemaphore);//等待m_terminateSemaphore
            sem_destroy(&m_terminateSemaphore);
        }
    
        ……
    
        if (!QAndroidEventDispatcherStopper::instance()->stopped()) {
            sem_post(&m_exitSemaphore);//发送m_exitSemaphore
            pthread_join(m_qtAppThread, nullptr);
        }
    }
    

    先等待m_terminateSemaphore信号量,然后发送m_exitSemaphore信号量。和startMainMethod的退出相辅相成。

    至此,退出流程也分析完了。

    总结

    • QtActivity使用singleTop模式
    • QtAcitivity的onDestroy是退出点,但qt主线程的退出是由onStop触发的
    • QtActivity的生命周期处理是用代理的方式,转变为Qt应用的生命周期处理

     

    其他

    上面分析的启动、退出流程只是抛砖引玉。

    读者可以借此分析更多,比如:

    • 跟踪qt应用的debug实现
    • 分析其他生命周期的实现,比如pause/resume
    • 学习java反射的用法
    • 在启动和退出流程中hook
    • 基于Android的模板工程,使用Android SDK添加不方便qt实现功能

    Service

    敏锐的读者已经发现,除了基于Activity作为qt应用的"容器"外,还有另一条支线——用Service作为”容器“。(QtService、QtServiceLoader、QtServiceDelegate)

    感兴趣的读者可以分析看看。

    展开全文
  • qt for android 更新APP

    2019-02-15 11:43:43
    qt for android 更新APP, 可以实现在app里更新自己 实现更新。
  • qt Android之环境建立

    千次阅读 2020-11-23 20:44:24
    简要概括和说明qt for Android环境的搭建,之前搭建过一次未记录,文件也没有存档,换了电脑之后再次搭建花了一些时间,很苦恼,所以想想还是决定发出来方式,让更多的人少走一些弯路,按照我的方式直接搭建就妥妥的...
  • 第一步.先介绍下环境搭建: Qt 版本 5.12.11 下载链接 https://download.qt.io/archive/qt/5.12/5.12.11/qt-opensource-linux-x64-5.12.11.run Ubuntu 18.04.6 LTS 下载链接 ...Android Studio版本 ...
  • QT Android 余数计算器

    2019-04-17 22:00:45
    qt写的余数计算器,可以编译成Android程序,同时也可以使用Qt Creator编译在windows下运行
  • QT15.1下载和安装指南,JDK、SDK等Android开发所需软件资源的下载和安装指导;详细说明在配置QT15.1的Android开发环境时遇到的特殊问题及解决办法。
  • openssl1.0.2j for QtAndroid

    2021-04-05 18:46:28
    Qt5.12.3 android提示unauthorized access to "ibcrypto" unauthorized access to "libssl.so" 下载附件,修改pro工程文件,加下以下信息,注意修改你的路径。 contains(ANDROID_TARGET_ARCH,armeabi-v7a) { ...
  • 前提:mac 下 Qt 开发android程序, 从程序能在手机调试上运行,到打包成.apk文件,中间要走的一些知识点总结.第一次用Qt开发过android程序,目前android基础知识也很欠缺.0,我的工程名是testMacQt2,实际的程序的名字是...
  • qt android 使用数据库

    千次阅读 2022-04-21 20:58:20
    记录将pc代码移植到android上,使用的是qt5.12 基本是参考的网上的,但是网上的总是不成功,只是修改了1点,其他基本相同,没有细究原因。 #ifndef DATABASECONNECTION_H #define DATABASECONNECTION_H #include ...
  • 在与ffmpeg同级目录下新建build_android.sh文件,设置ndk地址与编译设置,内容如下: #!/bin/sh cd ffmpeg make clean #这里的这些变量根据自己本地的环境切换 export NDK=/home/xu/Downloads/android-ndk-r21b ...
  • qt android 引用不带界面的aar包

    千次阅读 2022-03-24 09:42:08
    参照百度例子,尝试qt android引用不带界面的aar。带界面的不行,有谁能提供帮助? as生成aar步骤:前边已详细说明步骤,这里大体一说。 1、在应用上右键,new-module-android library,名字mylib 2、在mylib-src-...
  • Qt mysql driver for Android

    热门讨论 2017-09-13 14:32:19
    Qt5 mysql driver for Android, libqsqlmysql.so是放在Qt Android mysql driver对应的地方, libmariadb.so添加到项目的Additional Libraries里面
  • Qt Android环境安装与配置

    千次阅读 2020-05-26 13:58:09
    1、Android模块安装 浏览器进入地址:http://download.qt.io/static/mirrorlist/,找到国内的源,点击HTTP进入镜像地址,找到以下路径:Index of /qtproject/online/qtsdkrepository/windows_x86/root/qt,复制该...
  • 文章目录1 自行创建数据库文件2 使用已有的数据库文件 1 自行创建数据库文件 ...#设计Android数据库 android{ data.files += database/1.db data.path = /assets/database INSTALLS += data } data.files += 源
  • 此工程可以读取Android 手机中的GPS信息,比如经度,纬度,速度,定位精度等信息,并且可以把信息通过UDP发送出去。本工程详细介绍见https://blog.csdn.net/AlexBein/article/details/83660853
  • Android OpenSSL对Qt的支持 适用于Android的OpenSSL脚本和二进制文件(适用于Qt Android应用) 在此存储库中,您可以找到适用于Android的预构建OpenSSL库和可以与Qt项目集成使用的qmake include项目.pri文件。 ...
  • Qt Android QScreen的屏幕旋转功能需要注意以下几点: 确保手机开启了屏幕旋转的功能 QScreen要通过“qApp->primaryScreen()”的方式获取到 QScreen要设置想要监听的屏幕方向,通过“screen->...
  • Qt for android 动态权限申请工具类

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,841
精华内容 11,536
关键字:

QT android