0

我正在使用本文中的代码将我拥有的预填充数据库复制到新创建的数据库中,从而覆盖它。

覆盖成功。我能够在正确的路径 data/data/my.package/databases/myDatabase 中看到正确复制的 db。但这仅在我离开 getWritableDatabse() 之前查看数据库的情况下。

如果我离开 getWritableDatabse() 数据库无效:它仍然保持正确的大小但它只有 android_metadata 而不是我的表然后全部消失。

我没有设法访问 Eclipse 的 Android 源代码(按照所有说明操作,但它仍然不起作用),但使用GrepCode我设法将问题缩小到方法中的第 173-176 行getWritableDatabase()

173 db.setVersion(mNewVersion);
174 db.setTransactionSuccessful();
175 } finally {
176   db.endTransaction();

任何人都知道为什么这段代码可能会重写我的数据库并将其清空?

这是相关的堆栈:

Thread [<1> main] (Suspended)   
StournamentDbAdapter$DatabaseHelper(SQLiteOpenHelper).getWritableDatabase() line: 180   
StournamentDbAdapter.open() line: 192   
ActivityTournamentsList.onCreate(Bundle) line: 57   
ActivityTournamentsList(Activity).performCreate(Bundle) line: 4465  
Instrumentation.callActivityOnCreate(Activity, Bundle) line: 1049   
ActivityThread.performLaunchActivity(ActivityThread$ActivityClientRecord, Intent) line: 1920    
ActivityThread.handleLaunchActivity(ActivityThread$ActivityClientRecord, Intent) line: 1981 
ActivityThread.access$600(ActivityThread, ActivityThread$ActivityClientRecord, Intent) line: 123    
ActivityThread$H.handleMessage(Message) line: 1147  
ActivityThread$H(Handler).dispatchMessage(Message) line: 99 
Looper.loop() line: 137 
ActivityThread.main(String[]) line: 4424    
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]  
Method.invoke(Object, Object...) line: 511  
ZygoteInit$MethodAndArgsCaller.run() line: 784  
ZygoteInit.main(String[]) line: 551 
NativeStart.main(String[]) line: not available [native method]  

我首先使用mDb = mDbHelper.getWritableDatabase();which 调用DatabaseHelper.onCreate()实际发生数据库创建的位置。在DatabaseHelper.onCreate()我只打电话给copyDatabase();它看起来像这样:

private void copyDatabase() throws IOException
    {
        //Open your local db as the input stream
        InputStream myInput =  sContext.getAssets().open(StournamentConstants.Database.DATABASE_NAME);

        // Path to the just created empty db
        String outFileName = StournamentConstants.Database.DB_PATH + StournamentConstants.Database.DATABASE_NAME;

        //Open the empty db as the output stream
        OutputStream myOutput = new FileOutputStream(outFileName);

        //transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer))>0)
        {
            myOutput.write(buffer, 0, length);
        }

        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
    } 

所有这一切都很好,并且数据库被正确复制。只有当我爬出堆栈并返回到getWritableDatabase() line: 180那个问题时:数据库的大小保持不变,但它会丢失除 àndroid_metadata 之外的所有表。


中间结论

我对代码进行了更多研究(上面提到的 GrepCode 站点非常有帮助),因为我想深入了解它。进入后getWritableDatabase(),在第 165 行它会调用onCreate(),我在其中使用copyDatabase();. 问题是,在之前, ongetWritableDatabase() line 162 db.beginTransaction();被调用。为什么这是个问题?我将通过显示事件的顺序来解释:

  1. 称呼getWritableDatabase()
  2. getWritableDatabase()调用db = SQLiteDatabase.create(null);因此创建一个数据库
  3. getWritableDatabase()来电db.beginTransaction();
  4. getWritableDatabase()来电onCreate()
  5. 在里面onCreate()我通过调用copyDatabase();将数据库从资产复制到数据库位置来替换数据库。不知道为什么已经创建的数据库在那一刻没有崩溃。也许是因为它没有锁?
  6. 在第 176 行getWritableDatabase()调用db.endTransaction();,这是关键部分:很高兴我将我的数据库复制到最初创建的数据库中,但是当db.endTransaction();被调用时,系统会将所有更改提交到我新复制的数据库。但请记住:我没有做任何更改,我没有创建表 - 我只是将数据库复制到正确的位置。所以提交不提交任何内容,从而用 NOTHING 重写数据库。为此,我得到一个空的 db

我有一个邪恶的想法:

  1. 将数据库复制到onCreate()
  2. 打开新复制的数据库并用它替换数据库getWritableDatabase()使用(onCreate()获取数据库作为参数getWritableDatabase())。

像这样的东西:

@Override
public void onCreate(SQLiteDatabase db) 
{
    try 
    {               
        copyDatabase();

        String dbPath = StournamentConstants.Database.DB_PATH + StournamentConstants.Database.DATABASE_NAME;
        SQLiteDatabase activeDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);              

         db = activeDb;
    } 
       catch (IOException e) 
    {
       Log.i(getClass().getName() + ".onCreate ", e.getMessage());
   }   
}

问题是我得到了一个android.database.sqlite.SQLiteDatabaseLockedException: database is locked所以我不能得到新复制的数据库。除此之外 - 我不确定在这种不正当的行为背后隐藏着什么样的险恶后果。

所以我会按照@Barak 的建议去做,并将我的复制移到构造函数中——在系统尝试创建数据库之前。因此,当getWritableDatabase()最终到达时,数据库已经存在并且系统不会尝试触摸它。我今天学到了很多——我很高兴。

结论:如果你能读懂(代码),你有一定的优势:^)

将让您了解这对我来说是如何工作的。


定论

我解决了这个问题。

几个区别:

  1. 我不在DatabaseHelper's 的构造函数中这样做。那将需要太多的代码更改。相反open(),我在private static class DatabaseHelper extends SQLiteOpenHelper课堂上做
  2. 我不使用getReadableDatabase()但是getWritableDatabase()。原因很简单:getReadableDatabase()总是会打电话getWritableDatabase(),所以我想我会保存那个...

我也试图成为一个聪明的人,而不是getWritableDatabase()简单地自己创建数据库文件(这有一些代码开销)。我失败了。我认为这是由于许可问题或其他原因。这是我在里面尝试的copyDatabase()

File f = new File(DB_PATH);
if (!f.exists()) 
{
    f.mkdir();
}

但它总是失败。后来我发现OutputStream myOutput = new FileOutputStream(outFileName);实际上应该处理文件创建。

希望这对任何人都有帮助!

4

1 回答 1

1

好吧,我看到的唯一区别是我没有在 中进行数据库复制/创建onCreate,那是空的。我在构造函数中完成所有操作,如下所示:

private static class DatabaseHelper extends SQLiteOpenHelper {
    DatabaseHelper(Context context, String dbname, int dbversion) {
        super(context, dbname, null, dbversion);
            if (checkDataBase(dbname)) {
                openDataBase(dbname);
            } else {
                try {
                    this.getReadableDatabase();
                    copyDataBase(dbname);
                    this.close();
                    openDataBase(dbname);
                } catch (IOException e) {
                     throw new Error("Error copying database");
                }
                Toast.makeText(context,"Initial " + dbname + " database has been created",
                    Toast.LENGTH_LONG).show();
            }
      }
    @Override
    public void onCreate(SQLiteDatabase db) {
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
于 2012-06-07T06:01:35.033 回答