Search code examples
androidc++qtjava-native-interface

Why the same code works from QPushButton::clicked() and crashs from QAction::triggered()


I have a pretty simple Qt program I'm deploying on Android 4.4.2 (Nexus 5 phone) via QtCreator 3.0.1 (Qt 5.2.1).

This program creates a dummy file and then proposes to open it using user's default file editor via a Java SDK call. The file can be opened either by clicking a button or selecting a menu item.

  • When using the button, file is opened
  • When using the menu item, file is opened, but application crashs.

Why one crashs but not the other when it actually executes the same code?

From button:

enter image description here

Then I press F10 and it continues.

enter image description hereenter image description here

From menu:

enter image description here

Then I press F10 and it crashs (my program ends, but Android's file viewer remains opened):

enter image description here

Qt application log reports:

F/libc    ( 6949): Fatal signal 11 (SIGSEGV) at 0x00000001 (code=1), thread 6971 (ample.MenuCrash)
"org.qtproject.example.MenuCrash" est mort.

enter image description hereenter image description here

Here is the code:

pro file:

QT       += core gui androidextras widgets
TARGET = MenuCrash
TEMPLATE = app
SOURCES += main.cpp\
        mainwindow.cpp
HEADERS  += mainwindow.h
CONFIG += mobility
MOBILITY = 

main.cpp:

#include "mainwindow.h"
#include <QApplication>

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

    return a.exec();
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void openFile();

private:
    QString m_fileName;
};

#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"
#include <fstream>
#include <QMenuBar>
#include <QMenu>
#include <QVBoxLayout>
#include <QAndroidJniObject>
#include <QPushButton>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    m_fileName = "/sdcard/Download/test_file.txt";

    std::fstream file;
    // create the file
    file.open( m_fileName.toStdString().c_str(), std::ios_base::out );
    if ( file.is_open() )
    {
        // Write something in the file
        file << "Hello!!!" << std::endl;
        file.close();

        QWidget* parent = new QWidget( this );
        QVBoxLayout* layout = new QVBoxLayout( parent );
        setCentralWidget( parent );

        QMenuBar* pBar = menuBar();
        QMenu* pMenu = pBar->addMenu( "" );

        QPushButton* button = new QPushButton( "Open file", this );
        layout->addWidget( button );
        QObject::connect( button, SIGNAL(clicked()), this, SLOT(openFile()) );

        pMenu->addAction( "Open file now", this, SLOT(openFile()) );

        QAction* action = pMenu->addAction( "Open file later" );
        QObject::connect( action, SIGNAL(triggered()), this, SLOT(openFile()), Qt::QueuedConnection );
    }
    else
    {
        QMessageBox::critical(this,"Error","Unable to create file");
    }
}

MainWindow::~MainWindow()
{

}

void MainWindow::openFile()
{
    /* Translate this java code:
        Intent intent = new Intent();
        intent.setAction(android.content.Intent.ACTION_VIEW);
        File file = new File( m_fileName );
        intent.setDataAndType(Uri.fromFile(file), "text/plain");
        myActivity.startActivity(intent);
    */
    QAndroidJniObject activity =
            QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
                                                      "activity",
                                                      "()Landroid/app/Activity;");
    if ( activity.isValid() ) //activity is valid
    {
        QAndroidJniObject intent("android/content/Intent","()V");
        if ( intent.isValid() )
        {
            QAndroidJniObject name = QAndroidJniObject::fromString(m_fileName);
            QAndroidJniObject type = QAndroidJniObject::fromString("text/plain");
            QAndroidJniObject action = QAndroidJniObject::fromString("android.intent.action.VIEW");

            if ( type.isValid() && name.isValid() && action.isValid() )
            {
                QAndroidJniObject file( "java/io/File","(Ljava/lang/String;)V",name.object<jobject>());
                if ( file.isValid() )
                {
                    QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "fromFile", "(Ljava/io/File;)Landroid/net/Uri;", file.object<jobject>());
                    if ( uri.isValid() )
                    {
                        intent.callObjectMethod("setDataAndType","(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;",uri.object<jobject>(),type.object<jobject>());
                        intent.callObjectMethod("setAction","(Ljava/lang/String;)Landroid/content/Intent;",action.object<jobject>());

                        if ( intent.isValid() )
                        {
                            activity.callObjectMethod("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());
                        }
                    }
                }
            }
        }
    }
}

Note:

  • I tried to delay menu item execution using Qt::QueuedConnection, same crash when using the menu
  • I tried to connect QAction::triggered() to QPushButton::click() (pMenu->addAction( "Open file now", button, SLOT(click()) );), same crash when using the menu
  • I tried to connect QPushButton::clicked() to QAction::trigger() (QObject::connect( button, SIGNAL(clicked()), action, SLOT(trigger()) );), it does not crash when clicking the button. So it seems the QAction can be triggered.

...so now I try stackoverflow....;-)

Just created a Qt bug report: https://bugreports.qt-project.org/browse/QTBUG-41395


Solution

  • Christian from Qt team fixed this...

    See https://bugreports.qt-project.org/browse/QTBUG-41395

    The fix is to replace

    activity.callObjectMethod("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());
    

    by

    activity.callMethod<void>("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());
    

    As startActivity is a void method...