SQLite磁盘I/O异常:磁盘 I/O 错误(代码3850)。

5

我在一些设备上(非常少见,到目前为止只有两次)遇到了上述错误:

android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 3850)
    at android.database.sqlite.SQLiteConnection.nativeExecuteForString(Native Method)
    at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:679)
    at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:361)
    at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:236)
    at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:200)
    at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
    at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
    at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
    at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
    at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
    at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
    at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
    at ***.a(***.java:115)

我的代码中第115行如下所示:
// here the exception occurs
SQLiteDatabase db = SQLiteDatabase.openDatabase(pathApp, null, SQLiteDatabase.OPEN_READONLY); 
// ...
db.close();

故事

我的做法如下:

  • 应用程序拥有Root权限
  • 将另一个应用程序的数据库复制到自己的目录中
  • 尝试打开此数据库并读取某些数据
  • 再次关闭文件

就是这样。该方法在数千台设备上都能正常工作。我确定我的应用程序只在一个地方访问数据库,对此我确信无疑。

问题

有人知道可能导致此问题的原因吗?

也许有趣的事实

  • 这两个设备都是OnePlus2
  • 有人告诉我,在更新到Oxygen 2.1之后出现了这个问题

我突然也收到了用户报告这个错误(自9月25日以来;在此之前从未出现过)。所有的实例都在一个设备上运行,且使用的是Android 5.1.1系统。也许该设备最近接收了系统更新导致了这个问题? - mike47
我知道的两个设备已经得到了系统更新...升级到Oxygen 2.1...但我对此并不了解更多...也还没有找到任何相关信息... - prom85
终于找到了解决方案 :-). 将sqlite二进制文件添加到您的应用程序中,找出在设备上运行的哪一个并将数据库转储到控制台并解析输出... 这甚至在OnePlus2上也可以工作。 - prom85
1
@prom85,你是怎么做到的?能否详细解释一下你的回答?(我们可以在聊天中继续讨论) - Smaran
我写了一个完整的答案... 请查看它。 - prom85
显示剩余2条评论
3个回答

1

我最近遇到一个类似的问题,我的应用在所有安卓设备上都可以正常运行,但是在运行OxygenOS 2.1的OnePlus Two设备上崩溃了。

经过研究,发现这个特定组合非常敏感,会在某些数据库锁定的情况下崩溃。在我的情况下,我的应用程序在第一次运行时将一个新的数据库复制到设备上,并检查数据库版本以确定是否需要更新。在检查期间,它在这些设备上崩溃了。我意识到在我的代码中,我是在检查版本时打开了数据库的另一个实例,而不是使用当前已经打开的数据库。在修改代码以避免打开数据库的多个实例后,应用程序在OnePlus上停止崩溃。

我的建议是:不要在应用程序中打开多个数据库实例,或者在再次打开数据库之前先关闭数据库(也许你的复制方法没有关闭数据库或者正在使用同一数据库的另一个实例)。


我的复制方法将文件作为流进行复制,因此任何内部标志都将保持不变...在复制文件后,我该如何关闭数据库?我正在从另一个应用程序复制文件(我的应用程序具有根权限),并将其复制到我的目录中,以便我可以使用“SQLiteDatabase”读取它... - prom85
在我的应用程序中,我也正在使用流进行复制,但这是在同一应用程序内部以及从另一个函数内部发生的,该函数首先尝试打开数据库以检查其是否存在,然后关闭它,然后将其作为流复制。因此,在复制之前确保它已关闭。 - hbustan
我无法控制数据库文件...也许我可以通过sqlite3直接关闭它...我需要检查一下... - prom85
1
不要直接复制它,尝试将原始数据库转储为 SQL 文件并将其导入到您的应用程序作为新的数据库文件,然后再复制它 - 这样您就可以拥有一个全新的副本,而不必担心标志和其他问题。 - hbustan
对于 Android 6,我无论如何都必须向我的应用程序中添加 sqlite3 二进制文件并使用它...我也尝试过这样做,但目前(仍在等待进一步的反馈),在该特定设备上仍然失败了...我将尝试是否可以成功转储并尝试读取转储后的文件。 - prom85
转储解决方案起作用了!通过控制台和sqlite -csv ...读取失败,复制数据库并操纵所有权限也失败了,但是通过sqlite db .dump 'tablename' 控制台进行转储是可以的。非常感谢。 - prom85

1

在开发中遇到了这个问题。当手机电池耗尽并关闭时,它会触发。可以推测,在发生这种情况时,你正在获取数据库锁定。


0

以下是可行的解决方案(已在生产中测试了大约2个月),并且此解决方案也适用于OnePlus2和Android 6:

然后在您的应用程序中,尝试读取外部数据库,如下所示:

找出哪个二进制文件可用。我尝试了几种方法(例如通过调用/proc/cpuinfo),但无法找到可靠的解决方案,所以我通过以下方式进行试错:
  • 首先,我将二进制文件从我的资产文件夹复制到我的应用程序的files文件夹中
  • 然后,我尝试通过<path_to_SQLite3_bnary> <path_to_database> \.dump '<tablename>'\n转储所需的数据库并读取结果
  • 我检查结果,如果它包含error:,我知道该二进制文件不起作用,如果它以INSERT INTO开头,我知道它可以工作
  • 我重复这个过程,直到找到一个可用的二进制文件
然后我只需读取结果。结果将包含错误或类似csv的内容。我要么处理错误,要么通过row.replace("INSERT INTO \"" + tablename + "\" VALUES(", "").replace(");", "")将每行转换为有效的csv格式,以便我以csv格式获取内容。然后我使用opencsv的CSVReader来解析数据...

就是这样,这个程序在所有设备上都能够正常运行(至少目前为止)

代码 - 从我的源代码中复制,稍微调整一下以适应你的需求

public static List<String> readCSVData(String pathDatabase, String tableName) throws InterruptedException, TimeoutException, RootDeniedException, IOException 
{
    List<String> res = null;
    List<String> csvData = null;

    final String[] architectures = new String[]{
            "armv7",
            "armv7-pie",
            "armv6",
            "armv6-nofpu"
    };

    for (int i = 0; i < architectures.length; i++)
    {
        res = readDatabase(architectures[i], pathDatabase, tableName, rootMethod);

        if (res.toString().contains("[error:"))
        {
            L.d(RootNetworkUtil.class, "Trial and Error - ERROR: " + architectures[i]);
        }
        else
        {
            int maxLength = (res.toString().length() < 100) ? res.toString().length() : 100;
            L.d(RootNetworkUtil.class, "Trial and Error - RESULT: " + res.toString().substring(0, maxLength));
            L.d(RootNetworkUtil.class, "Architecture found via trial and error: " + architectures[i]);
            csvData = res;
            break;
        }
    }

    return csvData;
}

private static List<String> readDatabase(String architecture, String pathDB, String tablename) throws InterruptedException, TimeoutException, RootDeniedException, IOException {
    String sqlite = "sqlite3." + architecture;
    String pathSQLite3 = getSQLitePath(architecture, tablename);
    // OWN class, just copy the file from the assets to the sqlite3 path!!! 
    AssetUtil.copyAsset(sqlite, pathSQLite3);

    String[] cmd = new String[]{
            "su\n",
            //"chown root.root " + pathSQLite3 + "\n",
            "chmod 777 " + pathSQLite3 + "\n",
            pathSQLite3 + " " + pathDB + " \".dump '" + tablename + "'\"\n"
    };
    List<String> res = new ArrayList<>();
    List<String> temp = RootUtils.execute(cmd);
    for (int i = 0; i < temp.size(); i++)
    {
        // Fehlerzeilen behalten!!!
        if (temp.get(i).contains("error:"))
            res.add(temp.get(i));
        else if (temp.get(i).startsWith("INSERT INTO \"" + tablename + "\""))
            res.add(temp.get(i).replace("INSERT INTO \"" + tablename + "\" VALUES(", "").replace(");", ""));
    }
    return res;
}

public static String getSQLitePath(String architecture, String addon)
{
    String sqlite = "sqlite3." + architecture;
    String pathSQLite3 = "/data/data/" + MainApp.get().getPackageName() + "/files/" + sqlite + addon;
    return pathSQLite3;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接