尝试使用 Room 在资产中打开数据库时获取 FileNotFoundException

Get FileNotFoundException while trying to open database in assets using Room

我决定重组我的数据库。我还决定使用 Room 来组织我的数据库和我的应用程序之间的协作,而不是使用 SQL 查询和游标。我的数据库位于 Android Studio 的 Assets 文件夹中以及模拟器上的 assets/files 文件夹中。我的 class,将数据库从资产复制到下面的 assets/files 文件夹。

public class DatabaseHelper extends SQLiteOpenHelper implements TypeOfTest {
    private static String DB_PATH;
    private static String DB_NAME;
    private static final int SCHEMA = 2;

    public static final String DB_OLD = "ForEastory.db";
    public static final String DB_NEW = "ForEastory2.db";

    private Context myContext;

    public DatabaseHelper(Context context, String dbName) {
        super(context, DB_NAME, null, SCHEMA);
        this.myContext = context;
        DB_NAME = dbName;
        DB_PATH = context.getFilesDir().getPath() + "/"+ DB_NAME;
        Log.d("files dir + name", DB_PATH);
    }

       @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion,  int newVersion) {
    }

    public void create_db(){
        InputStream myInput;
        OutputStream myOutput;
        try {
            File file = new File(DB_PATH);
            if (!file.exists()) {
                this.getReadableDatabase();
                myInput = myContext.getAssets().open(DB_NAME);
                String outFileName = DB_PATH;

                myOutput = new FileOutputStream(outFileName);

                byte[] buffer = new byte[1024];
                int length;
                while ((length = myInput.read(buffer)) > 0) {
                    myOutput.write(buffer, 0, length);
                }

                myOutput.flush();
                myOutput.close();
                myInput.close();
            }
        }
        catch(IOException ex){
            ex.printStackTrace();
        }
    }

    public SQLiteDatabase open() throws SQLException {
        return SQLiteDatabase.openDatabase(DB_PATH, null, SQLiteDatabase.OPEN_READWRITE);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }
}

如您所见,这里我要数据库标题。这个 class 与我的旧数据库配合得很好。此外,使用设备文件资源管理器,我可以看到,该数据库实际上位于模拟器上的 assets/files 文件夹中。也许,问题出在房间数据库 class 中,我正在尝试使用我准备好的数据库。

@androidx.room.Database(entities = {Question.class, Topic.class, Test.class}, version = 1, exportSchema = false)
public abstract class Database extends RoomDatabase {
    private static Database instance;

    public abstract TestDao testDao();

    public static String ASSET_DIR;

    public static synchronized Database getInstance(Context context)  {
        if (instance == null) {
            try {
                ASSET_DIR = context.getFilesDir().getPath() + "/"+ DatabaseHelper.DB_NEW;
                instance = Room.databaseBuilder(context.getApplicationContext(),
                        Database.class,
                        "AppDB")
                        .createFromAsset(ASSET_DIR)
                        .build();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return instance;
    }
}

这是我的 TestDao class.

@Dao
public interface TestDao {

    @Query("SELECT * FROM tests WHERE topic_id = :id")
    TopicWithQuestions getTopicWithQuestionsById(int id);
}

以及我如何完全使用它。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);

        testNum = findViewById(R.id.testNum);
        getQuestions = findViewById(R.id.get_questions);

        DatabaseHelper databaseHelper = new DatabaseHelper(getApplicationContext(), DatabaseHelper.DB_NEW);
        databaseHelper.create_db();
        try {
            myDb = databaseHelper.open();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        db = Database.getInstance(getApplicationContext());
        testDao = db.testDao();

        getQuestions.setOnClickListener(v -> {
            try {
                int id = Integer.parseInt(testNum.getText().toString());

                //In this place i've catched the exception (RoomTest.java:61)
                TopicWithQuestions topicWithQuestions = testDao.getTopicWithQuestionsById(id);
                List<Question> questionList = topicWithQuestions.getQuestion();

                for (Question question : questionList) {
                    System.out.println(question.getQuestion());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

最后,logcat:

W/System.err: java.lang.RuntimeException: Unable to copy database file.
        at androidx.room.SQLiteCopyOpenHelper.verifyDatabaseFile(SQLiteCopyOpenHelper.java:131)
        at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:87)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at com.prodadimhaski.eastory2.Room.Dao.TestDao_Impl.getTopicWithQuestionsById(TestDao_Impl.java:33)
        at com.prodadimhaski.eastory2.Room.RoomTest.lambda$onCreate[=14=]$RoomTest(RoomTest.java:61)
        at com.prodadimhaski.eastory2.Room.-$$Lambda$RoomTestiMa7yj1shnTdXRtmzsROKAwX-w.onClick(lambda)
        at android.view.View.performClick(View.java:4438)
        at android.view.View$PerformClick.run(View.java:18422)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5001)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
        at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.io.FileNotFoundException: /data/data/com.prodadimhaski.eastory2/files/ForEastory2.db
        at android.content.res.AssetManager.openAsset(Native Method)
        at android.content.res.AssetManager.open(AssetManager.java:316)
        at android.content.res.AssetManager.open(AssetManager.java:290)
        at androidx.room.SQLiteCopyOpenHelper.copyDatabaseFile(SQLiteCopyOpenHelper.java:178)
        at androidx.room.SQLiteCopyOpenHelper.verifyDatabaseFile(SQLiteCopyOpenHelper.java:128)
        ... 17 more

这个 __db.assertNotSuspendingTransaction(); 是位于 TestDao_Impl.java:33

的代码片段

根据 official documentation 中预填充的房间数据库。您有 2 个选项:

1) createFromAssets: 在此选项中,您可以在 assets 文件夹下创建一个名为 "databases" 的目录 所以你可以如下:

.createFromAssets("/databases/YOUR DATABASE FILENAME")

2) createFromFile:此选项可能适用于您为其指定路径的文件。

.createFromFile(File("YOUR FILE PATH"))

如果您对这两种解决方案感到困惑,可以尝试手动解决方案,我们可以称之为手动解决方案耶!。通过访问资产文件夹中的数据库文件。

    private fun copyDBFromStorage(databaseName: String) {
    if (checkIfDBExists(this, databaseName)) return
    val databaseFile = File(this.getDatabasePath(databaseName).toString())
    val sourceLocation = assets.open("Your database file path")
    try {
        val inputStream = sourceLocation
        val os = FileOutputStream(databaseFile)
        val buffer = ByteArray(1024 * 32)
        var length = inputStream.read(buffer)
        while (length > 0) {
            os.write(buffer, 0, length)
            length = inputStream.read(buffer)
        }
        os.flush()
        os.close()
        inputStream.close()

    } catch (ex: IOException) {
        ex.printStackTrace();
        throw  RuntimeException("Error copying storage database");
    }
}

private fun checkIfDBExists(
    context: Context,
    databaseName: String
): Boolean {
    val dbfile = File(context.getDatabasePath(databaseName).toString())
    if (dbfile.exists()) return true
    if (!dbfile.parentFile.exists()) {
        dbfile.parentFile.mkdirs()
    }
    return false
}

注意:您可以使用 android studio 代码转换功能将此 Kotlin 代码转换为 java。

如果您发现任何问题,请回复。

编码愉快