使用预先填充的数据库运送android应用

时间:2019-05-06 16:14:04

标签: android sqlite

这是我的代码,

public DBHelper(Context context) {
    super(context, DB_NAME, null, 2);
    this.context = context;
    DB_PATH = context.getDatabasePath(DB_NAME).getAbsolutePath();
}

@Override
public void onCreate(SQLiteDatabase db) {
    createDataBase();
}

private void createDataBase() {
    boolean dbExist = checkDataBase();
    if (!dbExist) {
        copyDataBase();
    }
}

private boolean checkDataBase() {
    System.out.println("DB_PATH : " + DB_PATH);
    File dbFile = new File(DB_PATH);
    return dbFile.exists();
}

private void copyDataBase() {
    Log.i("Database",
            "New database is being copied to device!");
    byte[] buffer = new byte[1024];
    OutputStream myOutput;
    int length;
    InputStream myInput;
    try {
        myInput = context.getAssets().open(DB_NAME);
        myOutput = new FileOutputStream(DB_PATH);
        while ((length = myInput.read(buffer)) > 0) {
            myOutput.write(buffer, 0, length);
        }
        myOutput.close();
        myOutput.flush();
        myInput.close();
        Log.i("Database",
                "New database has been copied to device!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

一切正常,我什至收到日志New database has been copied to device!,但是当我尝试从db读取数据时,却遇到no such table异常。

注意:我正在尝试更新我的一个旧应用程序,相同的代码可在5.0和更低版本的旧设备中使用,但是当我尝试使用最新设备更新该应用程序时,则不起作用。

1 个答案:

答案 0 :(得分:2)

假设您已复制到资产文件夹的数据库确实包含该表,那么我认为您的问题是您正在实例化DBHelper的实例,然后隐式或显式地隐式打开了数据库。调用getWritableDatabase或getReadableDatabase,然后使用 onCreate 方法初始化副本。

如果是这样,则get?ableDatabase将创建一个空数据库,副本将覆盖此数据库,但是在更高版本的Android 9+上,-shm和-wal文件将保留原样,并且在数据库建立时保留打开,然后由于-shm和-wal文件与原始的空数据库不匹配,因此检测到损坏,因此当SDK代码尝试提供可用的数据库时,创建的新数据库为空。

  • 默认情况下,从Android 9+起默认使用WAL (Write Ahead Logging),这是创建和使用-shm和-wal文件的原因。

有3个修复程序。

  • 通过覆盖SQLiteOpenHelper类的onConfigure方法来使用disableWriteAheadLogging方法。然后,它将使用较旧的日记模式。

  • 确保未调用getWritableDatabase / getReadableDatabase。这可以通过确保在实例化DBHelper实例时完成复制来完成。

  • 确保-wal和-shm文件(如果在复制时存在)被删除。

使用第一种方法可能只会延迟不可避免的情况,因此不建议这样做,因为它没有利用WAL模式的好处。

以下版本的DBHelper包含第二个修订,并且作为预防措施还包含了第三个修订:-

how_many <- function(p){
  gb <- ggplot_build(p)
  length(gb$layout$panel_params[[1]][['x.major']])
}

raxe <- function(p, n){

  gb <- ggplot_build(p)
  x_params <- gb$layout$panel_params[[1]]
  ni <- length(x_params[['x.major']])
  labels <- x_params[['x.labels']]
  if(ni < n){
    dummy <- c(labels, paste0("__",letters[seq_len(n-ni)]))
    print(dummy)
    phantom <- c(labels, rep('', n-ni))
    return(p + scale_x_discrete(lim=dummy, labels=phantom))
  }
p
}

n_breaks <- sapply(p_list, how_many)
p_list <- lapply(p_list, raxe, max(n_breaks))

egg::ggarrange(plots = p_list, ncol=2)

这已在Android 5和Android 10上使用活动中的以下代码进行了测试(以及用于转储架构的其他代码(请注意,不是您的数据库而是可用的数据库)):-

public class DBHelper extends SQLiteOpenHelper {

    public static final  String DB_NAME = "myDBName";
    public static String DB_PATH;

    Context context;

    public DBHelper(Context context) {
        super(context, DB_NAME, null, 2);
        this.context = context;
        //<<<<<<<<<< ADDED (moved from createDatabase) 1st Fix >>>>>>>>>>
        DB_PATH = context.getDatabasePath(DB_NAME).getAbsolutePath();
        if (!checkDataBase()) {
            copyDataBase();
        }
        //<<<<<<<<<< END OF ADDED CODE >>>>>>>>>>
        this.getWritableDatabase(); //<<<<<<<<<< Added to force an open after the copy - not essential
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //createDataBase(); <<<<<<<<<< relying on this was the cause of the issue >>>>>>>>>>
    }

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

    }

    //<<<<<<<<<< NOT NEEDED AND SHOULD NOT BE CALLED >>>>>>>>>
    private void createDataBase() {
        boolean dbExist = checkDataBase();
        if (!dbExist) {
            copyDataBase();
        }
    }

    private boolean checkDataBase() {
        System.out.println("DB_PATH : " + DB_PATH);
        File dbFile = new File(DB_PATH);
        if (dbFile.exists()) return true;
        //<<<<<<<<<< ADDED to create the databases directory if it doesn't exist >>>>>>>>>>
        //it may be that getWritableDatabase was used to circumvent the issue that the copy would fail in the databases directory does not exist, hence this fix is included
        if (!new File(dbFile.getParent()).exists()) {
            new File(dbFile.getParent()).mkdirs();
        }
        return false;
    }

    private void copyDataBase() {
        Log.i("Database",
                "New database is being copied to device!");
        byte[] buffer = new byte[1024];
        //<<<<<<<<<< ADDED to delete wal and shm files if they exist (3rd fix) >>>>>>>>>>
        File dbDirectory = new File(new File(DB_PATH).getParent());
        File dbwal = new File(dbDirectory.getPath() + File.separator + "-wal");
        if (dbwal.exists()) {
            dbwal.delete();
        }
        File dbshm = new File(dbDirectory.getPath() + File.separator + "=shm");
        if (dbshm.exists()) {
            dbshm.delete();
        }
        //<<<<<<<<<< END OF ADDED CODE >>>>>>>>>>

        OutputStream myOutput;
        int length;
        InputStream myInput;
        try {
            myInput = context.getAssets().open(DB_NAME);
            myOutput = new FileOutputStream(DB_PATH);
            while ((length = myInput.read(buffer)) > 0) {
                myOutput.write(buffer, 0, length);
            }
            myOutput.close();
            myOutput.flush();
            myInput.close();
            Log.i("Database",
                    "New database has been copied to device!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

根据日志的结果:-

DBHelper mDBHlpr;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mDBHlpr = new DBHelper(this);