尝试使用 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。
如果您发现任何问题,请回复。
编码愉快
我决定重组我的数据库。我还决定使用 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。
如果您发现任何问题,请回复。
编码愉快