如何使用 Qt 在 Android 中读取现有的 SQLite 数据库?

Posted

技术标签:

【中文标题】如何使用 Qt 在 Android 中读取现有的 SQLite 数据库?【英文标题】:How to read an existing SQLite database in Android using Qt? 【发布时间】:2020-04-20 03:11:48 【问题描述】:

我一直在 Ubuntu 上使用 Qt 开发一个应用程序,它可以与 SQLite 数据库和几 GB 的文件一起使用。在 Ubuntu 和 Windows 中一切都运行良好,但是......当我尝试移植到 android 时,我遇到了一些问题。我已将 database.db 文件和其他文件放在 SD 卡上,将其安装在我的目标设备中,最后能够使用以下代码“找到”它:

QString dbName;
#ifdef Q_OS_ANDROID
  QAndroidJniObject androidContext = QtAndroid::androidContext();
  QAndroidJniObject            dir = QAndroidJniObject::fromString(QString(""));
  QAndroidJniObject path = androidContext.callObjectMethod("getExternalFilesDir",
                                                           "(Ljava/lang/String;)Ljava/io/File;",
                                                           dir.object());
  // qInfo() << "Path: " + path.toString();
  dbName = path.toString()+"/database.db";
#else
  QSettings *sp = new QSettings();
  dbName = sp->value( "dbName", "/database.db" ).toString();
  sp->deleteLater();
#endif

  qDebug( "Database::Database(%s)", qPrintable( dbName ) );
  const QString DRIVER("QSQLITE");
  if ( !QSqlDatabase::isDriverAvailable(DRIVER) )
     qDebug( "QSqlDatabase::isDriverAvailable(%s) is false", qPrintable(DRIVER) ); 
   else
     db = QSqlDatabase::addDatabase(DRIVER);
      db.setDatabaseName( dbName );
      QSqlDriver *driver = QSqlDatabase::database().driver();
      if ( !db.open() )
         qDebug( "Error opening database %s: %s", qPrintable( dbName ), qPrintable( db.lastError().text() ) );
        
       else
         qDebug( "Database opened." );
        
    

所以,最后通过使用 JNI 来追踪 SD 卡文件夹,我让数据库报告它已成功打开。但是,现在当我尝试读取我的第一个表时,我在 Android 中遇到 query.exec() 失败(这在 Linux/Windows 中不会发生):

  QSqlQuery query(db);
  query.prepare( "SELECT time FROM history ORDER BY time DESC;" );
  if ( !query.exec()  )  qDebug( "ERROR: %s, %s", qPrintable( query.lastError().text() ), qPrintable( query.executedQuery() ) ); return; 

返回:

D MyApp: ERROR: No query Unable to fetch row, SELECT time FROM history ORDER BY time DESC;

适用于 Android,但适用于其他操作系统。所有后续从数据库读取的尝试在 Android 中也会失败。 MyApp 是一个控制台应用程序,我最终希望将它作为服务运行,但在实现这一飞跃之前,我希望看到它以“简单模式”运行。应用程序输出确实在程序启动之前抛出了几个警告(标记为 W),但它们似乎没有连接?

I zygote  : Late-enabling -Xcheck:jni
W System  : ClassLoader referenced unknown path:
I Qt      : qt started
I zygote  : Do partial code cache collection, code=26KB, data=23KB
I zygote  : After code cache collection, code=26KB, data=23KB
I zygote  : Increasing code cache capacity to 128KB
I zygote  : Do partial code cache collection, code=62KB, data=55KB
I zygote  : After code cache collection, code=62KB, data=55KB
I zygote  : Increasing code cache capacity to 256KB
D OpenGLRenderer: HWUI GL Pipeline
I Adreno  : QUALCOMM build                   : 94aeef9, Ie8d0be8cd4
I Adreno  : Build Date                       : 11/09/17
I Adreno  : OpenGL ES Shader Compiler Version: EV031.22.00.01
I Adreno  : Local Branch                     :
I Adreno  : Remote Branch                    :
I Adreno  : Remote Branch                    :
I Adreno  : Reconstruct Branch               :
W RenderThread: type=1400 audit(0.0:2413): avc: denied  search  for name="proc" dev="debugfs" ino=12448 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:qti_debugfs:s0 tclass=dir permissive=0
I Adreno  : PFP: 0x005ff087, ME: 0x005ff063
I OpenGLRenderer: Initialized EGL, version 1.4
D OpenGLRenderer: Swap behavior 2
I zygote  : Do full code cache collection, code=112KB, data=112KB
I zygote  : After code cache collection, code=110KB, data=100KB
D MyApp: Database::Database(/storage/emulated/0/Android/data/com.mangocats.myapp/files/database.db)
D MyApp: Database opened.
D MyApp: ERROR: No query Unable to fetch row, SELECT time FROM history ORDER BY time DESC;

这是昨天下载和配置的最新稳定 Android Studio,JDK 1.8,昨天下载和安装的最新 Qt Creator,在 Ubuntu 18.04,Qt 5.14.2 上运行,针对 Android 8.0 手机。我在手机上成功运行的示例 Qt 应用程序和简单的 GUI 应用程序,并且控制台应用程序的 http 服务器端按预期工作。我已授予应用 READ 和 WRITE EXTERNAL STORAGE 权限(尽管许多注释表明这些版本不需要它们......)

我还需要为 Android 做什么才能让 SQLite 数据库正常工作?

【问题讨论】:

检查数据库文件路径是否正确。 open() 即使文件不存在也不会返回 false;它将创建一个新的空数据库。 好消息,我敢打赌就是这样。 【参考方案1】:

确保文件存在

正如在 cmets 中已经提到的,在调用之前

db.setDatabaseName(dbName);

确保dbName 引用文件系统中的现有文件。因为否则QSqlDatabase::open() 将通过该名称默默地创建一个空的 SQLite3 数据库。然后,您会在尝试第一次查询时看到类似您的错误,或者“参数计数不匹配”。

或者阻止open()创建文件

如果您希望QSqlDatabase::open() 在指定数据库不存在且只需要只读访问的情况下失败,您可以利用连接选项QSQLITE_OPEN_READONLY (source) 的副作用:

db.setConnectOptions("QSQLITE_OPEN_READONLY");
db.setDatabaseName(dbName);
if (!db.open()) 
    // ...

SQLite 本身也有一个option SQLITE_OPEN_READWRITE,它不会将此解决方案限制为只读访问,但 Qt 界面尚不支持此选项。

并且只使用普通的文件系统文件名

作为参考,在调用QSqlDatabase::setDatabaseName() 时不要尝试使用qrc:/ Qt 资源方案或assets:/ Qt 样式的Android 资产访问方案来指定文件。该方法只接受简单的相对或绝对文件系统文件名(或它们与 SQLite 特定的 file: URI 等效的文件名)。 ((这在文档中没有提到,但在 Qt 源代码here 中可见:Qt 将给定的文件名转发到sqlite3_open_v2() 和that 只接受文件系统文件名。))

访问与应用程序捆绑到 Android APK 包中的数据库的唯一方法是首先将其复制到应用程序的数据目录。因为那个副本有一个普通的文件系统路径。形式为/data/data/com.example.appname/files/database.sqlite

【讨论】:

以上是关于如何使用 Qt 在 Android 中读取现有的 SQLite 数据库?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Qt 集成到现有的 C++ 项目中

如何在 android 应用程序中使用现有的 Sip Stack 库?

如何在 Android 平板电脑上实现主从视图 Qt/QML?

如何在 WP7 的 WebBrowser 控件中读取现有的 html 文件?

如何将 qml ScatterSeries 添加到现有的 qml 定义的 ChartView?

将 Qt GUI 嵌入到现有的 OpenGL 程序中