0

测试我的数据库时,出现以下错误:

  • SQLiteDiskIOException:磁盘 I/O 错误(HTC Desire 620)
  • SQLiteReadOnlyDatabaseException:尝试写入只读数据库(Moto g2)

显然取决于我测试它的设备。运行应用程序时不会发生错误。不过,如果我无法测试应用程序,我的代码可能有问题。

该应用程序使用了两个库,它们应该可以很好地结合使用 SQLDelight 和 SQLBrite,这可能会使这个问题有些具体。

为了更好地理解正在发生的事情,我将对我的数据包中的文件进行简短描述。

-+-data-+
 |      |-manager-+
 |      |         |-LocationManager
 |      |         |-RunManager
 |      |-model-+
 |      |       |-Location
 |      |       |-Run
 |-DatabaseContract
 |-DataRepository
 |-MyDBHelper

文件LocationRun是 SQLDelight 生成的行模型。LocationManagerRunManager启用生成 sqlStatements 以从相应的表中插入或删除数据。下面看起来相似RunManagerLocationMangager

public class RunManager {

    public final Run.InsertRun insertRace;
    public final Run.DeleteRun deleteRun;
    public final Run.DeleteRunWhereTimeSmallerThan deleteRunWhereTimeSmallerThan;

    public RunManager(SQLiteDatabase db) {
        insertRace = new Run.InsertRun(db);
        deleteRun = new Run.DeleteRun(db);
        deleteRunWhereTimeSmallerThan = new Run.DeleteRunWhereTimeSmallerThan(db);
    }

}

MyDBHelper 以标准方式扩展了 SQLiteOpenHelper。

public class MyDbHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "runner.db";

    private static MyDbHelper INSTANCE = null;

    private MyDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    public static MyDbHelper getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new MyDbHelper(context);
        }
        return INSTANCE;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // create table (omitted)
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        // upgrade table (omitted)
    }
}

数据存储库聚合了各种插入和查询操作。

public class DataRepository implements DatabaseContract {

    private static DataRepository INSTANCE = null;

    BriteDatabase briteDatabase;
    LocationManger locationManger;
    RunManager runManager;

    private DataRepository(Context context) {
        SqlBrite sqlBrite = new SqlBrite.Builder().build();
        MyDbHelper helper = MyDbHelper.getInstance(context);
        briteDatabase = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());
        locationManger = new LocationManger(briteDatabase.getWritableDatabase());
        runManager = new RunManager(briteDatabase.getWritableDatabase());
    }

    public static DataRepository getInstance(Context context) {
        if (null == INSTANCE) {
            INSTANCE = new DataRepository(context);
        }
        return INSTANCE;
    }

    @Override
    public Observable<List<Run>> getAllRun() {
        return briteDatabase.createQuery(
                Run.TABLE_NAME,
                Run.SELECT_ALL
        ).mapToList(Run.MAPPER::map);
    }

    @Override
    public Observable<List<Location>> getLocationsForRun(long id) {
        return briteDatabase.createQuery(
                Location.TABLE_NAME, Location.FACTORY.selectAllByRace(id).statement
        ).mapToList(Location.MAPPER::map);
    }

    @Override
    public long insertRun(double distance, long duration, double avgSpeed, long timestamp) {
        runManager.insertRace.bind(distance, duration, avgSpeed, timestamp);
        return briteDatabase.executeInsert(Run.TABLE_NAME, runManager.insertRace.program);
    }

    @Override
    public void deleteRun(long id) {
        runManager.deleteRun.bind(id);
        briteDatabase.executeUpdateDelete(Run.TABLE_NAME, runManager.deleteRun.program);
    }

    @Override
    public void deleteRunWhereTimestampSmallerThan(long timestamp) {
        runManager.deleteRunWhereTimeSmallerThan.bind(timestamp);
        briteDatabase.executeUpdateDelete(Run.TABLE_NAME, runManager.deleteRunWhereTimeSmallerThan.program);
    }

    @Override
    public long insertLocation(long raceId, double lat, double lng, double alt, long timestamp) {
        locationManger.insertLocation.bind(raceId, lat, lng, alt, timestamp);
        return briteDatabase.executeInsert(Location.TABLE_NAME, locationManger.insertLocation.program);
    }

    public Observable<List<SingleRun>> getAllSingleRunModels() {
        return briteDatabase.createQuery(
                Run.TABLE_NAME,
                Run.SELECT_ALL
        ).mapToList(Run.MAPPER::map)
        // omitted

}

现在到问题的主要部分。现在,我编写了以下测试用例并遇到了顶部列出的错误。有趣的是,当我单独运行测试时,我没有收到任何错误,所有测试都通过了。

@RunWith(AndroidJUnit4.class)
@LargeTest
public class DataRepositoryTest {

    private Context context;
    private DataRepository mDataRepository;

    @Before
    public void setUp() {
        context = InstrumentationRegistry.getTargetContext();
        context.deleteDatabase(MyDbHelper.DATABASE_NAME);
        mDataRepository = DataRepository.getInstance(InstrumentationRegistry.getTargetContext());
    }

    @Test
    public void testPreConditions() {
        Assert.assertNotNull(context);
        Assert.assertNotNull(mDataRepository);
    }

    @Test
    public void testInsertRace() { // this test failes when all tests are run.
        long raceID1 = mDataRepository.insertRun(5.0, 35, 3.5, 1000);
        Assert.assertEquals(1, raceID1);
        long raceID2 = mDataRepository.insertRun(10.0, 70, 3.5, 2000);
        Assert.assertEquals(2, raceID2);
        long locationID1 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1000);
        Assert.assertEquals(1, locationID1);
        long locationID2 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1001);
        Assert.assertEquals(2, locationID2);
        long locationID3 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1002);
        Assert.assertEquals(3, locationID3);
        long locationID4 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1003);
        Assert.assertEquals(4, locationID4);
        long locationID5 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2000);
        Assert.assertEquals(5, locationID5);
        long locationID6 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2001);
        Assert.assertEquals(6, locationID6);
        long locationID7 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2002);
        Assert.assertEquals(7, locationID7);
        long locationID8 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2003);
        Assert.assertEquals(8, locationID8);
    }

    @Test
    public void testRaceObservable() {
        long raceID1 = mDataRepository.insertRun(5.0, 35, 3.5, 1000);
        Run run1 = Run.FACTORY.creator.create(raceID1, 5.0, 35l, 3.5, 1000l);
        Assert.assertEquals(1, raceID1);
        long raceID2 = mDataRepository.insertRun(10.0, 70, 3.5, 2000);
        Run run2 = Run.FACTORY.creator.create(raceID2, 10.0, 70l, 3.5, 2000l);
        Assert.assertEquals(2, raceID2);
        List<Run> expectedResult = Arrays.asList(run1, run2);
        Assert.assertEquals(expectedResult, mDataRepository.getAllRun().blockingFirst());
    }


}

我认为这与从不同线程访问数据库有关,但我不知道如何解决这个问题。

4

1 回答 1

2

您的问题是,当setUp第二次运行时,DataRepository.getInstance返回数据存储库,这意味着它不会创建新的SQLiteOpenHelper. 当您删除数据库时,您还需要清理 DataRepository 和 MyDbHelper 的单例。

或者根本不使用单例:

@Before
public void setUp() {
    context = InstrumentationRegistry.getTargetContext();
    context.deleteDatabase(MyDbHelper.DATABASE_NAME);
    mDataRepository = new DataRepository(InstrumentationRegistry.getTargetContext());
}

// In DataRepository.java
DataRepository(Context context) {
    SqlBrite sqlBrite = new SqlBrite.Builder().build();
    MyDbHelper helper = new MyDbHelper(context);
    briteDatabase = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());
    locationManger = new LocationManger(briteDatabase.getWritableDatabase());
    runManager = new RunManager(briteDatabase.getWritableDatabase());
}

// In MyDbHelper.java
MyDbHelper(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
于 2017-09-20T14:06:53.553 回答