带有数据库的应用程序

如果您的应用程序需要数据库并且它带有内置数据,那么交付该应用程序的最佳方式是什么?我应该:

  1. 重新创建SQLite数据库并将其包含在.apk

  2. 在应用程序中包含SQL命令,并让它创建数据库并在首次使用时插入数据?

我看到的缺点是:

  1. 可能的SQLite版本不匹配可能会导致问题,我目前不知道数据库应该去哪里以及如何访问它。

  2. 在设备上创建和填充数据库可能需要很长时间。

有什么建议吗?关于任何问题,请向留档人提出建议。

248568 次浏览

根据我所看到的,您应该正在交付一个已经具有表设置和数据的数据库。但是,如果您愿意(并且取决于您拥有的应用程序类型),您可以允许“升级数据库选项”。然后您要做的是下载最新的sqlite版本,获取在线托管的文本文件的最新插入/创建语句,执行语句并从旧数据库进行数据搬迁到新数据库。

目前还没有办法预先创建一个SQLite数据库来与您的apk一起发布。您能做的最好的事情就是将适当的SQL保存为资源并从您的应用程序中运行它们。是的,这会导致数据重复(作为资源和数据库存在相同的信息),但现在没有其他方法。唯一的缓解因素是apk文件被压缩。我的经验是908KB压缩到小于268KB。

下面的线程有我找到的最好的讨论/解决方案和良好的示例代码。

http://groups.google.com/group/android-developers/msg/9f455ae93a1cf152

我将CREATE语句存储为要使用Context.getString()读取的字符串资源,并使用SQLiteDatabse.execSQL()运行它。

我将插入的数据存储在res/raw/inserts.sql中(我创建了sql文件,7000多行)。使用上面链接中的技术,我进入一个循环,逐行读取文件并将数据压缩到“INSERT INTO tbl VALUE”上,然后执行另一个SQLiteDatabase.execSQL()。

在模拟器上大约需要20秒,我不知道这在真正的手机上需要多长时间,但它只发生一次,当用户第一次启动应用程序时。

如果所需的数据不是太大(限制我不知道,取决于很多事情),你也可以从网站/网络应用程序下载数据(XML、JSON等)。接收后,使用接收到的数据执行SQL语句创建表并插入数据。

如果您的移动应用程序包含大量数据,稍后可能更容易使用更准确的数据或更改更新已安装应用程序中的数据。

创建和更新数据库有两种选择。

一种是在外部创建一个数据库,然后将其放在项目的资产文件夹中,然后从那里复制整个数据库。如果数据库有很多表和其他组件,这要快得多。通过更改res/值/strings.xml文件中的数据库版本号来触发升级。升级将通过在外部创建一个新数据库来完成,用新数据库替换资产文件夹中的旧数据库,将旧数据库保存在另一个名称的内部存储中,将新数据库从资产文件夹复制到内部存储中,将旧数据库(之前重命名的)中的所有数据传输到新数据库中,最后删除旧数据库。您最初可以通过使用SQLite Manager FireFox插件来执行创建sql语句来创建数据库。

另一种选择是从sql文件在内部创建数据库。这不是那么快,但如果数据库只有几个表,延迟可能不会被用户注意到。通过更改res/值/strings.xml文件中的数据库版本号来触发升级。升级将通过处理升级sql文件来完成。数据库中的数据将保持不变,除非其容器被删除,例如删除一个表。

下面的示例演示了如何使用任一方法。

这是一个示例create_database.sql文件。它将被放置在项目的资产文件夹中,用于内部方法,或者复制到SQLite Manager的“执行SQL”中,为外部方法创建数据库。(注意:请注意Android所需的有关表的注释。)

--Android requires a table named 'android_metadata' with a 'locale' columnCREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US');INSERT INTO "android_metadata" VALUES ('en_US');
CREATE TABLE "kitchen_table";CREATE TABLE "coffee_table";CREATE TABLE "pool_table";CREATE TABLE "dining_room_table";CREATE TABLE "card_table";

这是一个示例update_database.sql文件。它将被放置在项目的资产文件夹中,用于内部方法,或者复制到SQLite Manager的“执行SQL”中,为外部方法创建数据库。(注意:请注意,本示例中包含的sql解析器将忽略所有三种类型的SQL注释。)

--CREATE TABLE "kitchen_table";  This is one type of comment in sql.  It is ignored by parseSql./** CREATE TABLE "coffee_table"; This is a second type of comment in sql.  It is ignored by parseSql.*/{CREATE TABLE "pool_table";  This is a third type of comment in sql.  It is ignored by parseSql.}/* CREATE TABLE "dining_room_table"; This is a second type of comment in sql.  It is ignored by parseSql. */{ CREATE TABLE "card_table"; This is a third type of comment in sql.  It is ignored by parseSql. }
--DROP TABLE "picnic_table"; Uncomment this if picnic table was previously created and now is being replaced.CREATE TABLE "picnic_table" ("plates" TEXT);INSERT INTO "picnic_table" VALUES ('paper');

以下是要添加到数据库版本号的 /res/values/strings.xml文件中的条目。

<item type="string" name="databaseVersion" format="integer">1</item>

这是一个访问数据库然后使用它的活动。(注意:如果数据库代码使用大量资源,您可能希望在单独的线程中运行它。

package android.example;
import android.app.Activity;import android.database.sqlite.SQLiteDatabase;import android.os.Bundle;
/*** @author Danny Remington - MacroSolve**         Activity for demonstrating how to use a sqlite database.*/public class Database extends Activity {/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);DatabaseHelper myDbHelper;SQLiteDatabase myDb = null;
myDbHelper = new DatabaseHelper(this);/** Database must be initialized before it can be used. This will ensure* that the database exists and is the current version.*/myDbHelper.initializeDataBase();
try {// A reference to the database can be obtained after initialization.myDb = myDbHelper.getWritableDatabase();/** Place code to use database here.*/} catch (Exception ex) {ex.printStackTrace();} finally {try {myDbHelper.close();} catch (Exception ex) {ex.printStackTrace();} finally {myDb.close();}}
}}

这是数据库助手类,如果需要,可以在其中创建或更新数据库。(注意:Android要求您创建一个扩展SQLiteOpenHelper的类,以便使用Sqlite数据库。)

package android.example;
import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;
import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;
/*** @author Danny Remington - MacroSolve**         Helper class for sqlite database.*/public class DatabaseHelper extends SQLiteOpenHelper {
/** The Android's default system path of the application database in internal* storage. The package of the application is part of the path of the* directory.*/private static String DB_DIR = "/data/data/android.example/databases/";private static String DB_NAME = "database.sqlite";private static String DB_PATH = DB_DIR + DB_NAME;private static String OLD_DB_PATH = DB_DIR + "old_" + DB_NAME;
private final Context myContext;
private boolean createDatabase = false;private boolean upgradeDatabase = false;
/*** 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, context.getResources().getInteger(R.string.databaseVersion));myContext = context;// Get the path of the database that is based on the context.DB_PATH = myContext.getDatabasePath(DB_NAME).getAbsolutePath();}
/*** Upgrade the database in internal storage if it exists but is not current.* Create a new empty database in internal storage if it does not exist.*/public void initializeDataBase() {/** Creates or updates the database in internal storage if it is needed* before opening the database. In all cases opening the database copies* the database in internal storage to the cache.*/getWritableDatabase();
if (createDatabase) {/** If the database is created by the copy method, then the creation* code needs to go here. This method consists of copying the new* database from assets into internal storage and then caching it.*/try {/** Write over the empty data that was created in internal* storage with the one in assets and then cache it.*/copyDataBase();} catch (IOException e) {throw new Error("Error copying database");}} else if (upgradeDatabase) {/** If the database is upgraded by the copy and reload method, then* the upgrade code needs to go here. This method consists of* renaming the old database in internal storage, create an empty* new database in internal storage, copying the database from* assets to the new database in internal storage, caching the new* database from internal storage, loading the data from the old* database into the new database in the cache and then deleting the* old database from internal storage.*/try {FileHelper.copyFile(DB_PATH, OLD_DB_PATH);copyDataBase();SQLiteDatabase old_db = SQLiteDatabase.openDatabase(OLD_DB_PATH, null, SQLiteDatabase.OPEN_READWRITE);SQLiteDatabase new_db = SQLiteDatabase.openDatabase(DB_PATH,null, SQLiteDatabase.OPEN_READWRITE);/** Add code to load data into the new database from the old* database and then delete the old database from internal* storage after all data has been transferred.*/} catch (IOException e) {throw new Error("Error copying database");}}
}
/*** Copies your database from your local assets-folder to the just created* empty database in the system folder, from where it can be accessed and* handled. This is done by transfering bytestream.* */private void copyDataBase() throws IOException {/** Close SQLiteOpenHelper so it will commit the created empty database* to internal storage.*/close();
/** Open the database in the assets folder as the input stream.*/InputStream myInput = myContext.getAssets().open(DB_NAME);
/** Open the empty db in interal storage as the output stream.*/OutputStream myOutput = new FileOutputStream(DB_PATH);
/** Copy over the empty db in internal storage with the database in the* assets folder.*/FileHelper.copyFile(myInput, myOutput);
/** Access the copied database so SQLiteHelper will cache it and mark it* as created.*/getWritableDatabase().close();}
/** This is where the creation of tables and the initial population of the* tables should happen, if a database is being created from scratch instead* of being copied from the application package assets. Copying a database* from the application package assets to internal storage inside this* method will result in a corrupted database.* <P>* NOTE: This method is normally only called when a database has not already* been created. When the database has been copied, then this method is* called the first time a reference to the database is retrieved after the* database is copied since the database last cached by SQLiteOpenHelper is* different than the database in internal storage.*/@Overridepublic void onCreate(SQLiteDatabase db) {/** Signal that a new database needs to be copied. The copy process must* be performed after the database in the cache has been closed causing* it to be committed to internal storage. Otherwise the database in* internal storage will not have the same creation timestamp as the one* in the cache causing the database in internal storage to be marked as* corrupted.*/createDatabase = true;
/** This will create by reading a sql file and executing the commands in* it.*/// try {// InputStream is = myContext.getResources().getAssets().open(// "create_database.sql");//// String[] statements = FileHelper.parseSqlFile(is);//// for (String statement : statements) {// db.execSQL(statement);// }// } catch (Exception ex) {// ex.printStackTrace();// }}
/*** Called only if version number was changed and the database has already* been created. Copying a database from the application package assets to* the internal data system inside this method will result in a corrupted* database in the internal data system.*/@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {/** Signal that the database needs to be upgraded for the copy method of* creation. The copy process must be performed after the database has* been opened or the database will be corrupted.*/upgradeDatabase = true;
/** Code to update the database via execution of sql statements goes* here.*/
/** This will upgrade by reading a sql file and executing the commands in* it.*/// try {// InputStream is = myContext.getResources().getAssets().open(// "upgrade_database.sql");//// String[] statements = FileHelper.parseSqlFile(is);//// for (String statement : statements) {// db.execSQL(statement);// }// } catch (Exception ex) {// ex.printStackTrace();// }}
/*** Called everytime the database is opened by getReadableDatabase or* getWritableDatabase. This is called after onCreate or onUpgrade is* called.*/@Overridepublic void onOpen(SQLiteDatabase db) {super.onOpen(db);}
/** Add your public helper methods to access and get content from the* database. You could return cursors by doing* "return myDataBase.query(....)" so it'd be easy to you to create adapters* for your views.*/
}

这是FileHelper类,它包含用于字节流复制文件和解析sql文件的方法。

package android.example;
import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.Reader;import java.nio.channels.FileChannel;
/*** @author Danny Remington - MacroSolve**         Helper class for common tasks using files.**/public class FileHelper {/*** Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy* of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then* it will be replaced with a copy of <i><b>fromFile</b></i>. The name and* path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both* <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this* operation.** @param fromFile*            - InputStream for the file to copy from.* @param toFile*            - InputStream for the file to copy to.*/public static void copyFile(InputStream fromFile, OutputStream toFile) throws IOException {// transfer bytes from the inputfile to the outputfilebyte[] buffer = new byte[1024];int length;
try {while ((length = fromFile.read(buffer)) > 0) {toFile.write(buffer, 0, length);}}// Close the streamsfinally {try {if (toFile != null) {try {toFile.flush();} finally {toFile.close();}}} finally {if (fromFile != null) {fromFile.close();}}}}
/*** Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy* of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then* it will be replaced with a copy of <i><b>fromFile</b></i>. The name and* path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both* <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this* operation.** @param fromFile*            - String specifying the path of the file to copy from.* @param toFile*            - String specifying the path of the file to copy to.*/public static void copyFile(String fromFile, String toFile) throws IOException {copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));}
/*** Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy* of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then* it will be replaced with a copy of <i><b>fromFile</b></i>. The name and* path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both* <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this* operation.** @param fromFile*            - File for the file to copy from.* @param toFile*            - File for the file to copy to.*/public static void copyFile(File fromFile, File toFile) throws IOException {copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));}
/*** Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy* of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then* it will be replaced with a copy of <i><b>fromFile</b></i>. The name and* path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both* <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this* operation.** @param fromFile*            - FileInputStream for the file to copy from.* @param toFile*            - FileInputStream for the file to copy to.*/public static void copyFile(FileInputStream fromFile, FileOutputStream toFile) throws IOException {FileChannel fromChannel = fromFile.getChannel();FileChannel toChannel = toFile.getChannel();
try {fromChannel.transferTo(0, fromChannel.size(), toChannel);} finally {try {if (fromChannel != null) {fromChannel.close();}} finally {if (toChannel != null) {toChannel.close();}}}}
/*** Parses a file containing sql statements into a String array that contains* only the sql statements. Comments and white spaces in the file are not* parsed into the String array. Note the file must not contained malformed* comments and all sql statements must end with a semi-colon ";" in order* for the file to be parsed correctly. The sql statements in the String* array will not end with a semi-colon ";".** @param sqlFile*            - String containing the path for the file that contains sql*            statements.** @return String array containing the sql statements.*/public static String[] parseSqlFile(String sqlFile) throws IOException {return parseSqlFile(new BufferedReader(new FileReader(sqlFile)));}
/*** Parses a file containing sql statements into a String array that contains* only the sql statements. Comments and white spaces in the file are not* parsed into the String array. Note the file must not contained malformed* comments and all sql statements must end with a semi-colon ";" in order* for the file to be parsed correctly. The sql statements in the String* array will not end with a semi-colon ";".** @param sqlFile*            - InputStream for the file that contains sql statements.** @return String array containing the sql statements.*/public static String[] parseSqlFile(InputStream sqlFile) throws IOException {return parseSqlFile(new BufferedReader(new InputStreamReader(sqlFile)));}
/*** Parses a file containing sql statements into a String array that contains* only the sql statements. Comments and white spaces in the file are not* parsed into the String array. Note the file must not contained malformed* comments and all sql statements must end with a semi-colon ";" in order* for the file to be parsed correctly. The sql statements in the String* array will not end with a semi-colon ";".** @param sqlFile*            - Reader for the file that contains sql statements.** @return String array containing the sql statements.*/public static String[] parseSqlFile(Reader sqlFile) throws IOException {return parseSqlFile(new BufferedReader(sqlFile));}
/*** Parses a file containing sql statements into a String array that contains* only the sql statements. Comments and white spaces in the file are not* parsed into the String array. Note the file must not contained malformed* comments and all sql statements must end with a semi-colon ";" in order* for the file to be parsed correctly. The sql statements in the String* array will not end with a semi-colon ";".** @param sqlFile*            - BufferedReader for the file that contains sql statements.** @return String array containing the sql statements.*/public static String[] parseSqlFile(BufferedReader sqlFile) throws IOException {String line;StringBuilder sql = new StringBuilder();String multiLineComment = null;
while ((line = sqlFile.readLine()) != null) {line = line.trim();
// Check for start of multi-line commentif (multiLineComment == null) {// Check for first multi-line comment typeif (line.startsWith("/*")) {if (!line.endsWith("}")) {multiLineComment = "/*";}// Check for second multi-line comment type} else if (line.startsWith("{")) {if (!line.endsWith("}")) {multiLineComment = "{";}// Append line if line is not empty or a single line comment} else if (!line.startsWith("--") && !line.equals("")) {sql.append(line);} // Check for matching end comment} else if (multiLineComment.equals("/*")) {if (line.endsWith("*/")) {multiLineComment = null;}// Check for matching end comment} else if (multiLineComment.equals("{")) {if (line.endsWith("}")) {multiLineComment = null;}}
}
sqlFile.close();
return sql.toString().split(";");}
}

SQLiteAssetHelper库使这个任务非常简单。

作为gradle依赖项很容易添加(但是Jar也可用于ant/Eclipse),并且可以在以下位置找到它的留档:
https://github.com/jgilfelt/android-sqlite-asset-helper

备注:这个项目不再像上面Github链接所说的那样维护。

如留档所述:

  1. 将依赖项添加到模块的gradle构建文件中:

    dependencies {compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'}
  2. Copy the database into the assets directory, in a subdirectory called assets/databases. For instance:
    assets/databases/my_database.db

    (Optionally, you may compress the database in a zip file such as assets/databases/my_database.zip. This isn't needed, since the APK is compressed as a whole already.)

  3. Create a class, for example:

    public class MyDatabase extends SQLiteAssetHelper {
    private static final String DATABASE_NAME = "my_database.db";private static final int DATABASE_VERSION = 1;
    public MyDatabase(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}}

将数据库运送到apk中,然后将其复制到/data/data/...将使数据库的大小增加一倍(apk中的1,data/data/...中的1),并会增加apk的大小(当然)。所以你的数据库不应该太大。

最后我做到了!!我使用了这个链接帮助在Android应用程序中使用您自己的SQLite数据库,但不得不稍微改变一下。

  1. 如果你有很多包,你应该把主包名称放在这里:

    private static String DB_PATH = "data/data/masterPakageName/databases";

  2. 我更改了将数据库从本地文件夹复制到模拟器文件夹的方法!当该文件夹不存在时,它会出现一些问题。所以首先,它应该检查路径,如果它不存在,它应该创建文件夹。

  3. 在前面的代码中,当数据库不存在并且checkDataBase方法导致异常时,从未调用copyDatabase方法。所以我稍微更改了代码。

  4. 如果您的数据库没有文件扩展名,请不要将文件名与文件扩展名一起使用。

它对我很有用,我希望它对你也有用

    package farhangsarasIntroduction;

import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.HashMap;
import android.content.Context;import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteException;import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DataBaseHelper extends SQLiteOpenHelper{
//The Android's default system path of your application database.private static String DB_PATH = "data/data/com.example.sample/databases";
private static String DB_NAME = "farhangsaraDb";
private SQLiteDatabase myDataBase;
private final Context myContext;
/*** 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, 1);this.myContext = context;
}
/*** Creates a empty database on the system and rewrites it with your own database.* */public void createDataBase() {
boolean dbExist;try {
dbExist = checkDataBase();

} catch (SQLiteException e) {
e.printStackTrace();throw new Error("database dose not exist");
}
if(dbExist){//do nothing - database already exist}else{
try {
copyDataBase();

} catch (IOException e) {
e.printStackTrace();throw new Error("Error copying database");
}//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();

}
}
/*** 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 checkDataBase(){
SQLiteDatabase checkDB = null;
try{String myPath = DB_PATH +"/"+ DB_NAME;
checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);}catch(SQLiteException e){
//database does't exist yet.throw new Error("database does't exist yet.");
}
if(checkDB != null){
checkDB.close();
}
return checkDB != null ? true : false;}
/*** Copies your database from your local assets-folder to the just created empty database in the* system folder, from where it can be accessed and handled.* This is done by transfering bytestream.* */private void copyDataBase() throws IOException{


//copyDataBase();//Open your local db as the input streamInputStream myInput = myContext.getAssets().open(DB_NAME);
// Path to the just created empty dbString outFileName = DB_PATH +"/"+ DB_NAME;File databaseFile = new File( DB_PATH);// check if databases folder exists, if not create one and its subfoldersif (!databaseFile.exists()){databaseFile.mkdir();}
//Open the empty db as the output streamOutputStream myOutput = new FileOutputStream(outFileName);
//transfer bytes from the inputfile to the outputfilebyte[] buffer = new byte[1024];int length;while ((length = myInput.read(buffer))>0){myOutput.write(buffer, 0, length);}
//Close the streamsmyOutput.flush();myOutput.close();myInput.close();


}


@Overridepublic synchronized void close() {
if(myDataBase != null)myDataBase.close();
super.close();
}
@Overridepublic void onCreate(SQLiteDatabase db) {
}


@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
you to create adapters for your views.
}

Android已经提供了一种版本感知的数据库管理方法。这种方法已在Android应用程序的BARACUS框架中得到利用。

它使您能够在应用程序的整个版本生命周期中管理数据库,从而能够将sqlite数据库从任何先前版本更新到当前版本。

此外,它允许您运行SQLite的热备份和热恢复。

我不是100%确定,但是特定设备的热恢复可能使您能够在应用程序中提供准备好的数据库。但我不确定数据库二进制格式,这可能特定于某些设备、供应商或设备世代。

由于内容是Apache License 2,请随意重用代码的任何部分,可以在github上找到

编辑:

如果你只想发送数据,你可以考虑在应用程序第一次启动时实例化和持久化POJO。BARACUS对此提供了内置支持(用于配置信息的内置键值存储,例如“APP_FIRST_RUN”加上一个后上下文引导钩子,以便在上下文上运行启动后操作)。这使你能够与应用程序一起提供紧密耦合的数据;在大多数情况下,这适合我的用例。

我的解决方案既不使用任何第三方库,也不强制你在SQLiteOpenHelper子类上调用自定义方法来在创建时初始化数据库。它还负责数据库升级。需要做的就是子类SQLiteOpenHelper

先决条件:

  1. 您希望随应用程序一起提供的数据库。它应该包含一个名为android_metadata的1x1表,其属性locale具有值en_US以及您的应用程序独有的表。

子类化SQLiteOpenHelper

  1. 子类SQLiteOpenHelper
  2. SQLiteOpenHelper子类中创建private方法。此方法包含将数据库内容从“资产”文件夹中的数据库文件复制到在应用程序包上下文中创建的数据库的逻辑。
  3. 覆盖SQLiteOpenHelperonCreateonUpgradeonOpen方法。

说的够多了。下面是SQLiteOpenHelper子类:

public class PlanDetailsSQLiteOpenHelper extends SQLiteOpenHelper {private static final String TAG = "SQLiteOpenHelper";
private final Context context;private static final int DATABASE_VERSION = 1;private static final String DATABASE_NAME = "my_custom_db";
private boolean createDb = false, upgradeDb = false;
public PlanDetailsSQLiteOpenHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);this.context = context;}
/*** Copy packaged database from assets folder to the database created in the* application package context.** @param db*            The target database in the application package context.*/private void copyDatabaseFromAssets(SQLiteDatabase db) {Log.i(TAG, "copyDatabase");InputStream myInput = null;OutputStream myOutput = null;try {// Open db packaged as asset as the input streammyInput = context.getAssets().open("path/to/shipped/db/file");
// Open the db in the application package context:myOutput = new FileOutputStream(db.getPath());
// Transfer db file contents:byte[] buffer = new byte[1024];int length;while ((length = myInput.read(buffer)) > 0) {myOutput.write(buffer, 0, length);}myOutput.flush();
// Set the version of the copied database to the current// version:SQLiteDatabase copiedDb = context.openOrCreateDatabase(DATABASE_NAME, 0, null);copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION);copiedDb.close();
} catch (IOException e) {e.printStackTrace();throw new Error(TAG + " Error copying database");} finally {// Close the streamstry {if (myOutput != null) {myOutput.close();}if (myInput != null) {myInput.close();}} catch (IOException e) {e.printStackTrace();throw new Error(TAG + " Error closing streams");}}}
@Overridepublic void onCreate(SQLiteDatabase db) {Log.i(TAG, "onCreate db");createDb = true;}
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {Log.i(TAG, "onUpgrade db");upgradeDb = true;}
@Overridepublic void onOpen(SQLiteDatabase db) {Log.i(TAG, "onOpen db");if (createDb) {// The db in the application package// context is being created.// So copy the contents from the db// file packaged in the assets// folder:createDb = false;copyDatabaseFromAssets(db);
}if (upgradeDb) {// The db in the application package// context is being upgraded from a lower to a higher version.upgradeDb = false;// Your db upgrade logic here:}}}

最后,要获得数据库连接,只需在SQLiteOpenHelper子类上调用getReadableDatabase()getWritableDatabase(),它将负责创建一个数据库,如果数据库不存在,则从“资产”文件夹中的指定文件复制数据库内容。

简而言之,您可以使用SQLiteOpenHelper子类访问资产文件夹中提供的数据库,就像您将使用onCreate()方法中的SQL查询初始化的数据库一样。

我写了一个图书馆来简化这个过程。

dataBase = new DataBase.Builder(context, "myDb").//        setAssetsPath(). // default "databases"//        setDatabaseErrorHandler().//        setCursorFactory().//        setUpgradeCallback()//        setVersion(). // default 1build();

它将从assets/databases/myDb.db文件创建一个数据库。此外,您将获得所有这些功能:

  • 从文件加载数据库
  • 同步访问数据库
  • 通过请求使用sqlite-android,Android特定分发最新版本的SQLite。

github克隆它。

我修改了类和问题的答案,并编写了一个允许通过DB_VERSION更新数据库的类。

public class DatabaseHelper extends SQLiteOpenHelper {private static String DB_NAME = "info.db";private static String DB_PATH = "";private static final int DB_VERSION = 1;
private SQLiteDatabase mDataBase;private final Context mContext;private boolean mNeedUpdate = false;
public DatabaseHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);if (android.os.Build.VERSION.SDK_INT >= 17)DB_PATH = context.getApplicationInfo().dataDir + "/databases/";elseDB_PATH = "/data/data/" + context.getPackageName() + "/databases/";this.mContext = context;
copyDataBase();
this.getReadableDatabase();}
public void updateDataBase() throws IOException {if (mNeedUpdate) {File dbFile = new File(DB_PATH + DB_NAME);if (dbFile.exists())dbFile.delete();
copyDataBase();
mNeedUpdate = false;}}
private boolean checkDataBase() {File dbFile = new File(DB_PATH + DB_NAME);return dbFile.exists();}
private void copyDataBase() {if (!checkDataBase()) {this.getReadableDatabase();this.close();try {copyDBFile();} catch (IOException mIOException) {throw new Error("ErrorCopyingDataBase");}}}
private void copyDBFile() throws IOException {InputStream mInput = mContext.getAssets().open(DB_NAME);//InputStream mInput = mContext.getResources().openRawResource(R.raw.info);OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);byte[] mBuffer = new byte[1024];int mLength;while ((mLength = mInput.read(mBuffer)) > 0)mOutput.write(mBuffer, 0, mLength);mOutput.flush();mOutput.close();mInput.close();}
public boolean openDataBase() throws SQLException {mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);return mDataBase != null;}
@Overridepublic synchronized void close() {if (mDataBase != null)mDataBase.close();super.close();}
@Overridepublic void onCreate(SQLiteDatabase db) {
}
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {if (newVersion > oldVersion)mNeedUpdate = true;}}

使用类。

在活动类中,声明变量。

private DatabaseHelper mDBHelper;private SQLiteDatabase mDb;

在onCreate方法中,编写以下代码。

mDBHelper = new DatabaseHelper(this);
try {mDBHelper.updateDataBase();} catch (IOException mIOException) {throw new Error("UnableToUpdateDatabase");}
try {mDb = mDBHelper.getWritableDatabase();} catch (SQLException mSQLException) {throw mSQLException;}

如果将数据库文件添加到文件夹res/raw,则使用该类的以下修改。

public class DatabaseHelper extends SQLiteOpenHelper {private static String DB_NAME = "info.db";private static String DB_PATH = "";private static final int DB_VERSION = 1;
private SQLiteDatabase mDataBase;private final Context mContext;private boolean mNeedUpdate = false;
public DatabaseHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);if (android.os.Build.VERSION.SDK_INT >= 17)DB_PATH = context.getApplicationInfo().dataDir + "/databases/";elseDB_PATH = "/data/data/" + context.getPackageName() + "/databases/";this.mContext = context;
copyDataBase();
this.getReadableDatabase();}
public void updateDataBase() throws IOException {if (mNeedUpdate) {File dbFile = new File(DB_PATH + DB_NAME);if (dbFile.exists())dbFile.delete();
copyDataBase();
mNeedUpdate = false;}}
private boolean checkDataBase() {File dbFile = new File(DB_PATH + DB_NAME);return dbFile.exists();}
private void copyDataBase() {if (!checkDataBase()) {this.getReadableDatabase();this.close();try {copyDBFile();} catch (IOException mIOException) {throw new Error("ErrorCopyingDataBase");}}}
private void copyDBFile() throws IOException {//InputStream mInput = mContext.getAssets().open(DB_NAME);InputStream mInput = mContext.getResources().openRawResource(R.raw.info);OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);byte[] mBuffer = new byte[1024];int mLength;while ((mLength = mInput.read(mBuffer)) > 0)mOutput.write(mBuffer, 0, mLength);mOutput.flush();mOutput.close();mInput.close();}
public boolean openDataBase() throws SQLException {mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);return mDataBase != null;}
@Overridepublic synchronized void close() {if (mDataBase != null)mDataBase.close();super.close();}
@Overridepublic void onCreate(SQLiteDatabase db) {
}
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {if (newVersion > oldVersion)mNeedUpdate = true;}}

http://blog.harrix.org/article/6784

我使用ORMLite和下面的代码为我工作

public class DatabaseProvider extends OrmLiteSqliteOpenHelper {private static final String DatabaseName = "DatabaseName";private static final int DatabaseVersion = 1;private final Context ProvidedContext;
public DatabaseProvider(Context context) {super(context, DatabaseName, null, DatabaseVersion);this.ProvidedContext= context;SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);boolean databaseCopied = preferences.getBoolean("DatabaseCopied", false);if (databaseCopied) {//Do Nothing} else {CopyDatabase();SharedPreferences.Editor editor = preferences.edit();editor.putBoolean("DatabaseCopied", true);editor.commit();}}
private String DatabasePath() {return "/data/data/" + ProvidedContext.getPackageName() + "/databases/";}
private void CopyDatabase() {try {CopyDatabaseInternal();} catch (IOException e) {e.printStackTrace();}}
private File ExtractAssetsZip(String zipFileName) {InputStream inputStream;ZipInputStream zipInputStream;File tempFolder;do {tempFolder = null;tempFolder = new File(ProvidedContext.getCacheDir() + "/extracted-" + System.currentTimeMillis() + "/");} while (tempFolder.exists());
tempFolder.mkdirs();
try {String filename;inputStream = ProvidedContext.getAssets().open(zipFileName);zipInputStream = new ZipInputStream(new BufferedInputStream(inputStream));ZipEntry zipEntry;byte[] buffer = new byte[1024];int count;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {filename = zipEntry.getName();if (zipEntry.isDirectory()) {File fmd = new File(tempFolder.getAbsolutePath() + "/" + filename);fmd.mkdirs();continue;}
FileOutputStream fileOutputStream = new FileOutputStream(tempFolder.getAbsolutePath() + "/" + filename);while ((count = zipInputStream.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, count);}
fileOutputStream.close();zipInputStream.closeEntry();}
zipInputStream.close();} catch (IOException e) {e.printStackTrace();return null;}
return tempFolder;}
private void CopyDatabaseInternal() throws IOException {
File extractedPath = ExtractAssetsZip(DatabaseName + ".zip");String databaseFile = "";for (File innerFile : extractedPath.listFiles()) {databaseFile = innerFile.getAbsolutePath();break;}if (databaseFile == null || databaseFile.length() ==0 )throw new RuntimeException("databaseFile is empty");
InputStream inputStream = new FileInputStream(databaseFile);
String outFileName = DatabasePath() + DatabaseName;
File destinationPath = new File(DatabasePath());if (!destinationPath.exists())destinationPath.mkdirs();
File destinationFile = new File(outFileName);if (!destinationFile.exists())destinationFile.createNewFile();
OutputStream myOutput = new FileOutputStream(outFileName);
byte[] buffer = new byte[1024];int length;while ((length = inputStream.read(buffer)) > 0) {myOutput.write(buffer, 0, length);}
myOutput.flush();myOutput.close();inputStream.close();}
@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {}
@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int fromVersion, int toVersion) {
}}

请注意,代码从资产中的zip文件中提取数据库文件

在Android Studio 3.0中使用数据库文件运送应用程序

将应用程序与数据库文件一起发送对我来说是个好主意。优点是,如果您的数据集很大,您不需要进行复杂的初始化,这有时会花费大量时间。

步骤1:准备数据库文件

准备好您的数据库文件。它可以是. db文件或. sqlite文件。如果您使用. sqlite文件,您需要做的就是更改文件扩展名。步骤相同。

在这个例子中,我准备了一个名为testDB.db.它有一个表和一些示例数据,如下所示输入图片描述

第2步:将文件导入您的项目

如果您还没有资产文件夹,请创建资产文件夹。然后将数据库文件复制并粘贴到此文件夹中

在此处输入图片描述

第3步:将文件复制到应用程序的数据文件夹

您需要将数据库文件复制到应用程序的数据文件夹,以便与之进行进一步的交互。这是复制数据库文件的一次性操作(初始化)。如果多次调用此代码,数据文件夹中的数据库文件将被资产文件夹中的数据库文件覆盖。当您想在应用程序更新期间将来更新数据库时,此覆盖过程很有用。

请注意,在应用程序更新期间,此数据库文件不会在应用程序的数据文件夹中更改。只有卸载才会删除它。

数据库文件需要复制到/databases文件夹。打开设备文件资源管理器。输入data/data/<YourAppName>/位置。这是上面提到的应用程序的默认数据文件夹。默认情况下,数据库文件将放置在此目录下名为数据库的另一个文件夹中

在此处输入图片描述

现在,复制文件过程与Java正在做的非常相似。使用以下代码进行复制粘贴。这是启动代码。它也可以用于将来更新(通过覆盖)数据库文件。

//get context by calling "this" in activity or getActivity() in fragment//call this if API level is lower than 17  String appDataPath = "/data/data/" + context.getPackageName() + "/databases/"String appDataPath = context.getApplicationInfo().dataDir;
File dbFolder = new File(appDataPath + "/databases");//Make sure the /databases folder existsdbFolder.mkdir();//This can be called multiple times.
File dbFilePath = new File(appDataPath + "/databases/testDB.db");
try {InputStream inputStream = context.getAssets().open("testDB.db");OutputStream outputStream = new FileOutputStream(dbFilePath);byte[] buffer = new byte[1024];int length;while ((length = inputStream.read(buffer))>0){outputStream.write(buffer, 0, length);}outputStream.flush();outputStream.close();inputStream.close();} catch (IOException e){//handle}

然后刷新文件夹以验证复制过程

在此处输入图片描述

步骤4:创建数据库打开助手

SQLiteOpenHelper创建一个子类,包含连接、关闭、路径等。我将其命名为DatabaseOpenHelper

import android.content.Context;import android.database.SQLException;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseOpenHelper extends SQLiteOpenHelper {public static final String DB_NAME = "testDB.db";public static final String DB_SUB_PATH = "/databases/" + DB_NAME;private static String APP_DATA_PATH = "";private SQLiteDatabase dataBase;private final Context context;
public DatabaseOpenHelper(Context context){super(context, DB_NAME, null, 1);APP_DATA_PATH = context.getApplicationInfo().dataDir;this.context = context;}
public boolean openDataBase() throws SQLException{String mPath = APP_DATA_PATH + DB_SUB_PATH;//Note that this method assumes that the db file is already copied in placedataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.OPEN_READWRITE);return dataBase != null;}
@Overridepublic synchronized void close(){if(dataBase != null) {dataBase.close();}super.close();}
@Overridepublic void onCreate(SQLiteDatabase db) {}
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}

步骤5:创建与数据库交互的顶级类

这将是读取和写入数据库文件的类。还有一个示例查询来打印数据库中的值。

import android.content.Context;import android.database.Cursor;import android.database.SQLException;import android.database.sqlite.SQLiteDatabase;import android.util.Log;
public class Database {private final Context context;private SQLiteDatabase database;private DatabaseOpenHelper dbHelper;
public Database(Context context){this.context = context;dbHelper = new DatabaseOpenHelper(context);}
public Database open() throws SQLException{dbHelper.openDataBase();dbHelper.close();database = dbHelper.getReadableDatabase();return this;}
public void close(){dbHelper.close();}
public void test(){try{String query ="SELECT value FROM test1";Cursor cursor = database.rawQuery(query, null);if (cursor.moveToFirst()){do{String value = cursor.getString(0);Log.d("db", value);}while (cursor.moveToNext());}cursor.close();} catch (SQLException e) {//handle}}}

步骤6:测试运行

通过运行以下代码行来测试代码。

Database db = new Database(context);db.open();db.test();db.close();

按下运行按钮,欢呼!

在此处输入图片描述

2017年11月,Google发布了房间持久化库

从留档:

Room持久性库在SQLite上提供了一个抽象层允许流畅的数据库访问,同时充分利用SQLite

该库可帮助您在设备上创建应用数据的缓存正在运行您的应用程序。这个缓存,作为您的应用程序的单个真实来源,允许用户查看密钥的一致副本您的应用程序中的信息,无论用户是否拥有互联网连接。

首次创建或打开数据库时,Room数据库会有一个回调。您可以使用创建回调来填充您的数据库。

Room.databaseBuilder(context.applicationContext,DataDatabase::class.java, "Sample.db")// prepopulate the database after onCreate was called.addCallback(object : Callback() {override fun onCreate(db: SupportSQLiteDatabase) {super.onCreate(db)// moving to a new threadioThread {getInstance(context).dataDao().insert(PREPOPULATE_DATA)}}}).build()

此代码博客文章

如果您使用的是房间,那么在官方留档中已经有一个非常直接的前进路径https://developer.android.com/training/data-storage/room/prepopulate。以下是从资产文件重新填充数据库的操作方式:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db").createFromAsset("database/myapp.db").build()

或从文件:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db").createFromFile(File("mypath")).build()

如果你不使用房间,我强烈建议你😁