【问题标题】:ContentProvider throws SQLiteCantOpenDatabaseException: unable to open database fileContentProvider 抛出 SQLiteCantOpenDatabaseException:无法打开数据库文件
【发布时间】:2013-11-25 09:53:25
【问题描述】:

在我的内容提供程序中,我创建并维护了 3 个 SQLiteDatabase 对象。它们是这样创建的:

    private ContentProviderHelper helper;

    @Override
    public boolean onCreate() { // that's the ContentProvider onCreate()
       SQLiteDatabase dbLog = new DbLog(getContext()).getWritableDatabase();
       SQLiteDatabase dbSession = new DbSession(getContext()).getWritableDatabase();
       SQLiteDatabase dbLocation = new DbLocation(getContext()).getWritableDatabase();

       helper = new ContentProviderHelper(UriManager.getAuthority(getContext()));
       helper.addDb(dbLog, DbLog.TABLE_NAME, UriManager.LOG, SQLiteDatabase.CONFLICT_REPLACE);
       helper.addDb(dbSession, DbSession.TABLE_NAME, UriManager.SESSION, SQLiteDatabase.CONFLICT_REPLACE);
       helper.addDb(dbLocation, DbLocation.TABLE_NAME, UriManager.LOCATION, SQLiteDatabase.CONFLICT_REPLACE);

ContentProviderHelper 将这些 SQLiteDatabase 存储在一个用 UriMatcher 索引的 ArrayList 中。

<provider> 已在清单中正确注册,并且我的应用具有 SD 卡权限。

对于我们 500,000 名用户群中的大多数人来说,它运行良好,但每隔一段时间我就会从 Google Play 收到 SQLiteCantOpenDatabaseException: unable to open database file

相关的堆栈轨迹是:

Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file
at android.database.sqlite.SQLiteDatabase.dbopen(Native Method)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1013)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:986)
at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1051)
at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:787)
at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:157)

这些错误报告通常来自普通的无品牌劣质设备。

任何有关如何正确避免这些错误的帮助将不胜感激。

编辑:

我在相同代码上遇到的不同 SQL 异常。

Caused by: android.database.sqlite.SQLiteDiskIOException: disk I/O error: COMMIT;
at android.database.sqlite.SQLiteDatabase.native_execSQL(Native Method)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1763)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:583)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:137)
at ***.***.***.data.ContentManager.onCreate(ContentManager.java:26)

除了try{} catch(){},还有其他方法可以解决这个问题吗?

编辑:

关于游标使用的额外信息:

一般来说,游标保持打开的时间尽可能短。所有 3 个 SQLiteDatabase 都有一个 Wrapper,它执行实际的 Cursor 调用,从中读取数据并关闭它们。只有 1 个实例将光标传递回调用对象,但它是一个 IntentService,同样,单线程、单实例、单进程(就像 ContentProvider)循环通过光标并关闭它。

【问题讨论】:

  • 如在其他线程中所见,可能与数据库的路径(在android文件系统内部)有关。 stackoverflow.com/questions/4651797/… 也许情况是有人将应用程序移至 SD 并在卸载 SD 时访问/使用您的应用程序(在 Windows 中使用)。只是一个理论!也许试试看:)

标签: android sqlite android-sqlite android-contentprovider


【解决方案1】:

这看起来不像是您(Java)方面的编程错误,也不是 Android SQLite 包装类。

提供的堆栈跟踪和代码并没有提供太多信息来证明我的猜测,但我认为这应该是由于这些 SDCard 的不可靠性质造成的(您已经提到观察到的跟踪通常来自低质量的手机)。

异常本身是在本地 sqlite 代码中生成的,虽然我没有查找 JNI 接口的 C/C++ 部分,但这应该直接来自底层的 sqlite3_open 调用。由于非显而易见的原因,实际的错误代码未包含在引发的异常中,因此您基本上无法在这里找到根本原因。

因为这直接来自本机层,所以它是某种文件系统/硬件问题。卡可能坏了,卡槽可能坏了/脏了/任何东西,或者两者之间的任何东西都可能被弄乱了(很可能是物理上的)。

为您解决无法解决的问题提供可能的解决方案:不要使用(有时不可靠的)SDCard。如果您的数据库足够小(小于几 MB),您应该可以将它们存储在内部闪存上。这必须是可靠的存储,因为如果 thouse 写入或查询失败,用户无论如何都会遇到更严重的问题。

如果这不是一个选项,您可以在内部写入“最新数据”(我不知道您存储的是哪种数据)并将这些数据定期迁移到 SDCard 上。这样,您至少可以尝试存储它而不用 try/catch 淹没您的代码(如果您在一个单独的线程中尝试这个,该线程在顶层捕获任何异常),但它不能解决问题的读取部分。

我可以想象的另一件事,虽然有点丑陋:让异常直接飞过并在 Application 类中捕获它们(您可以重载它),将其推送到磁盘(显然是内部的)并重新抛出它以使其崩溃你的应用程序。在下一次启动时,您可以查找是否因 SQLite 异常而崩溃并向用户显示一条消息,其中指出“抱歉,您的 SDCard 似乎已损坏,因为我们无法使用它。考虑购买新的订购更好的设备之一”。 ACRA 可能会帮助你,它完成了我上次使用它时(1 年前)试图描述的很好。

【讨论】:

  • 显然最后我会try{}catch{} 的东西。但这是一个经过充分讨论且基础充分的答案。这不是很多数据,它是我们即将开源的分析(使用/会话日志)库的一部分,我希望它尽可能稳定,但应用程序可以移动到 SDcard(我假设它也会传输DB 文件),我没有为 DB 指定任何疯狂的位置,只是 OpenHelper 使用的标准。 ACRA 似乎是一个很好用的东西(即使不是针对这个特定问题),我将来会探索使用它。
【解决方案2】:

我认为您在并发方面遇到了一些问题,并且您多次尝试获取 DB 对象。 此外 - 拥有 3 个不同的数据库(可能)会使应用程序一次运行 3 个数据库引擎(内存问题)。 当问题位于引发异常的其他地方时,没有单一且简单的解决方案,但您可以执行一些步骤: 将 3 个数据库放入一个文件中(如果可能)。 制作一个简单的单例以将 DB 对象保存在内存中并避免对存储文件的并发尝试。请记住,SQLite 被称为“线程安全”而不是“多线程”,因此 IO 错误可能只是保护数据库损坏的自然行为。 确保游标尽快关闭(即在查询对象后读取游标并将数据映射到某些 POJO 对象。

解决方法 - 在打开数据库时使用 try/catch,如果出现异常,请在一段时间(1 秒)后尝试执行此操作。

【讨论】:

  • 嗨,感谢您的尝试,但我们正在使用 ContentProvider(如问题中所述)。 ContentProvider 本质上是在单个进程和单个线程中运行的单例对象,通过某些 IPC 连接到主应用程序。将它们放入 ContentProvider 的全部原因是为了消除并发性。有 3 个 DB,因为它们确实拥有 3 个完全独立的数据,而且我不相信 Android 会像运行 3 个单独的 DB 引擎实例那样低效率。我有 DbWrapper 类可以做到这一点,运行查询,放入 POJO,关闭游标 n 返回(我将添加该信息)。
  • 您是否 100% 确定数据库上没有并发操作?特别是。如果您尝试在表中插入一些数据并同时从中选择它们?
  • 嗯,错误发生在内容提供者的onCreate 期间,根据文档:This method is called for all registered content providers on the application main thread at application launch time。我做了更多的谷歌,发现它甚至在我的 Application.onCreate() 之前就被调用了。所以是的,我 100% 确定我的数据库上没有并发操作。
  • 如果你的onCreate命令被频繁调用,你的db文件仍有可能被sqlite lib锁定。
猜你喜欢
  • 1970-01-01
  • 2022-07-25
  • 1970-01-01
  • 1970-01-01
  • 2015-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多