首页 > 解决方案 > 资产文件夹上大于 1mb 的 android 数据库

问题描述

所以我有这个大约 7MB 的数据库,当我在 2014 年第一次开始开发应用程序时,我读到我应该将数据库分成 1mb 的部分并将其放在资产文件夹中,如下所示:01.db 02.db 03.db ... 07.db

在我的数据库助手类上,我有

private void copyDataBase(File DBFile) throws IOException
    {
        AssetManager am = myContext.getAssets();
        OutputStream os = new FileOutputStream(DBFile);
        DBFile.createNewFile();
        byte []b = new byte[1024];
        int i, r;
        String []Files = am.list("");
        Arrays.sort(Files);
        for(i=1;i<15;i++) //I have definitely less than 10 files; you might have more
        {
            String fn = String.format("0%d.db", i);
            System.out.println(fn);
            if(Arrays.binarySearch(Files, fn) < 0) //No such file in assets - time to quit the loop
                break;
            InputStream is = am.open(fn);
            while((r = is.read(b)) != -1)
                os.write(b, 0, r);
            is.close();
        }
        os.close();
    }

这段代码是否仍然相关?这是我完整的数据库助手类,你们中的任何人都有它的更新版本吗? DatabaseHelper.class

public class DatabaseHelper extends SQLiteOpenHelper {

private SQLiteDatabase myDataBase;
private final Context myContext;

//public static String PACKAGE_NAME
//PACKAGE_NAME = getApplicationContext().getPackageName();
//The Androids default system path of your application database.
//private static String DB_PATH = "/data/data/"+PACKAGE_NAME+"/databases/";
private static String DB_PATH = "";
private static String DB_NAME = "fulldb6";//name of your Database
//File DBFile                 = new File(DB_PATH, DB_NAME);
File DBFile;



/**
  * Constructor
  * Takes and keeps a reference of the passed context in order to access to the application assets and resources.
  * @param context
  */

public DatabaseHelper(Context context) {

    super(context, DB_NAME, null, 3);
    this.myContext = context;
    DB_PATH = myContext.getDatabasePath(this.DB_NAME).getPath();;
    DBFile  = myContext.getDatabasePath(this.DB_NAME);

    //System.out.println(DBFile.getPath());
}

/**
  * Creates a empty database on the system and rewrites it with your own database.
  * */
public void createDataBase() throws IOException{

    boolean dbExist = checkIfDataBaseExists();

    if(dbExist){
        //do nothing - database already exist
    } else{

        //By calling this method and empty database will be created into the default system path
        //of your application so we are gonna be able to overwrite that database with our database.
        this.getReadableDatabase();

        try {
            copyDataBase(DBFile);
        } catch (IOException e) {
            throw new Error("Error copying database");
        }
    }


}

/**
  * Check if the database already exist to avoid re-copying the file each time you open the application.
  * @return true if it exists, false if it doesn't
  */
private boolean checkIfDataBaseExists(){

    SQLiteDatabase checkDB = null;

    try{
        String myPath = DB_PATH;
        checkDB       = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);

    } catch(SQLiteException e){


    }

    if(checkDB != null){

        checkDB.close();

    }

    return checkDB != null ? true : false;
}



private void copyDataBase(File DBFile) throws IOException
{
    AssetManager am = myContext.getAssets();
    OutputStream os = new FileOutputStream(DBFile);
    DBFile.createNewFile();
    byte []b = new byte[1024];
    int i, r;
    String []Files = am.list("");
    Arrays.sort(Files);
    for(i=1;i<15;i++) //I have definitely less than 10 files; you might have more
    {
        String fn = String.format("0%d.db", i);
        System.out.println(fn);
        if(Arrays.binarySearch(Files, fn) < 0) //No such file in assets - time to quit the loop
            break;
        InputStream is = am.open(fn);
        while((r = is.read(b)) != -1)
            os.write(b, 0, r);
        is.close();
    }
    os.close();
}


public void openDataBase() throws SQLException{

        //Open the database
        String myPath = DB_PATH ;
        myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);

}

@Override
public synchronized void close() {

    if(myDataBase != null)
        myDataBase.close();

    super.close();

}

@Override
public void onCreate(SQLiteDatabase db) {

}

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

}

还有谁知道硬编码 db_path 到 /data/data/com.packagename/databases 是否会导致任何崩溃?在没有内存的设备中,也许 db 存储在 sd 卡上?许多人报告崩溃或无法运行的应用程序,我无法在我的设备和安卓模拟器上复制。

我是否应该检查是否没有内存可以在 sdcard 中安装 db?

标签: androidsqliteandroid-sqlite

解决方案


这段代码是否仍然相关?

这将导致 Android 9+ 失败,因为 getReadableDatabase 将导致创建 -wal 和 -shm 文件(在 Android 9+ 中默认打开 WAL)这些文件被标记为由新数据库拥有。新数据库被覆盖,但 -wal 和 -shm 文件仍然存在。复制有效,但是当通过 SQliteOpenHelper 打开数据库时,由于 -shm 和 -wal 文件存在导致的错误导致可用的数据库为空,即数据已被擦除,通常应用程序将因找不到表而崩溃。

我怀疑,历史上使用 getReadableDatabase 或(getWritableDatabase 没有区别,除非有这样的灾难)的唯一原因是有人发现它在未使用时绕过了 OpenError ENOENT。get???Database 在打开数据库的漫长而曲折的过程中所做的是创建数据库目录(如果它不存在)。

为什么不使用mkdirs的明显用途我不知道。

从资产中检查和准备数据库文件副本的有效方法是检查数据库文件是否存在(如果存在则无需从资产中复制),如果不存在则检查文件的 parentFile (数据库文件夹)查看它是否存在。如果它不存在,则对父文件执行mkdirs,然后复制该文件。

  • 无需打开数据库文件,
  • 没有生成 -wal 和 -swm 文件,
  • 没有基础处理来创建数据库文件、-shm 文件和 -wal 文件,只是为了将它们丢弃。-没有消失的桌子技巧。

/data/data/com.packagename/databases

由于问题(主要是拼写错误)引发了许多问题。但是,不需要硬编码。使用ContextgetDatabasePath(dbname)方法返回路径。唯一的硬代码(应该是 1 个常量)是数据库名称(在您的情况下为 DB_NAME)。

所以我有这个大约 7MB 的数据库,当我在 2014 年第一次开始开发应用程序时,我读到我应该将数据库分成 1mb 的部分并将其放在资产文件夹中,如下所示:01.db 02.db 03.db ... 07.db

我不认为这个限制仍然存在。我相信资产可以成为 APK 的 100mb 的一部分,但可以使用扩展 APK 处理更大的数据。


推荐阅读