如果您的应用程序需要一个数据库并且它带有内置数据,那么发布该应用程序的最佳方式是什么?我是不是该:
预先创建 SQLite 数据库并将其包含在
.apk
?在应用程序中包含 SQL 命令并让它创建数据库并在首次使用时插入数据?
我看到的缺点是:
可能的 SQLite 版本不匹配可能会导致问题,我目前不知道数据库应该去哪里以及如何访问它。
在设备上创建和填充数据库可能需要很长时间。
有什么建议么?指向有关任何问题的文档的指针将不胜感激。
如果您的应用程序需要一个数据库并且它带有内置数据,那么发布该应用程序的最佳方式是什么?我是不是该:
预先创建 SQLite 数据库并将其包含在.apk
?
在应用程序中包含 SQL 命令并让它创建数据库并在首次使用时插入数据?
我看到的缺点是:
可能的 SQLite 版本不匹配可能会导致问题,我目前不知道数据库应该去哪里以及如何访问它。
在设备上创建和填充数据库可能需要很长时间。
有什么建议么?指向有关任何问题的文档的指针将不胜感激。
创建和更新数据库有两种选择。
一种是在外部创建一个数据库,然后将其放在项目的 assets 文件夹中,然后从那里复制整个数据库。如果数据库有很多表和其他组件,这会更快。 通过更改 res/values/strings.xml 文件中的数据库版本号来触发升级。然后通过在外部创建一个新数据库、用新数据库替换 assets 文件夹中的旧数据库、以另一个名称将旧数据库保存在内部存储中、将新数据库从 assets 文件夹复制到内部存储、转移所有将旧数据库(之前已重命名)中的数据转移到新数据库中,最后删除旧数据库。您最初可以使用SQLite Manager FireFox 插件来执行你创建的 sql 语句。
另一种选择是从 sql 文件内部创建数据库。这不是那么快,但如果数据库只有几个表,用户可能不会注意到延迟。通过更改 res/values/strings.xml 文件中的数据库版本号来触发升级。 然后将通过处理升级 sql 文件来完成升级。数据库中的数据将保持不变,除非删除其容器,例如删除表。
下面的示例演示了如何使用这两种方法。
这是一个示例 create_database.sql 文件。内部方法要放在项目的assets文件夹中,或者复制到SQLite Manager的“Execute SQL”中,为外部方法创建数据库。 (注意:注意Android所需表的注释。)
--Android requires a table named 'android_metadata' with a 'locale' column
CREATE 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 文件。内部方法要放在项目的assets文件夹中,或者复制到SQLite Manager的“Execute 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. */
@Override
public 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.
*/
@Override
public 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.
*/
@Override
public 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.
*/
@Override
public 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 outputfile
byte[] buffer = new byte[1024];
int length;
try {
while ((length = fromFile.read(buffer)) > 0) {
toFile.write(buffer, 0, length);
}
}
// Close the streams
finally {
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 comment
if (multiLineComment == null) {
// Check for first multi-line comment type
if (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 依赖项(但 Ant/Eclipse 也可以使用 Jar),并且可以在以下位置找到它的文档:
https ://github.com/jgilfelt/android-sqlite-asset-helper
注意:该项目不再如上述 Github 链接所述维护。
如文档中所述:
将依赖项添加到模块的 gradle 构建文件中:
dependencies {
compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
}
将数据库复制到 assets 目录中,位于名为assets/databases
. 例如:
assets/databases/my_database.db
(或者,您可以将数据库压缩为 zip 文件,例如assets/databases/my_database.zip
。这不是必需的,因为 APK 已经作为一个整体进行了压缩。)
创建一个类,例如:
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);
}
}
将应用程序与数据库文件一起发送对我来说是个好主意。优点是您不需要进行复杂的初始化,如果您的数据集很大,这有时会花费大量时间。
第一步:准备数据库文件
准备好数据库文件。它可以是 .db 文件或 .sqlite 文件。如果您使用 .sqlite 文件,您需要做的就是更改文件扩展名。步骤是一样的。
在本例中,我准备了一个名为 testDB.db 的文件。它有一个表和一些示例数据,如下所示
第 2 步:将文件导入您的项目
如果您没有资产文件夹,请创建资产文件夹。然后将数据库文件复制并粘贴到此文件夹中
第 3 步:将文件复制到应用程序的数据文件夹
您需要将数据库文件复制到应用程序的数据文件夹中,以便与它进行进一步的交互。这是复制数据库文件的一次性操作(初始化)。如果多次调用此代码,data 文件夹中的数据库文件将被 assets 文件夹中的数据库文件覆盖。当您希望将来在应用程序更新期间更新数据库时,此覆盖过程很有用。
请注意,在应用程序更新期间,此数据库文件不会在应用程序的数据文件夹中更改。只有卸载才会删除它。
需要将数据库文件复制到/databases
文件夹中。打开设备文件资源管理器。输入data/data/<YourAppName>/
位置。这是上面提到的应用程序的默认数据文件夹。默认情况下,数据库文件将放置在该目录下的另一个名为 databases 的文件夹中
现在,复制文件的过程很像 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 exists
dbFolder.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
}
然后刷新文件夹以验证复制过程
第四步:创建数据库打开助手
SQLiteOpenHelper
为, 使用 connect、close、path 等创建一个子类。我将其命名为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 place
dataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.OPEN_READWRITE);
return dataBase != null;
}
@Override
public synchronized void close(){
if(dataBase != null) {dataBase.close();}
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public 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();
点击运行按钮并欢呼!
我的解决方案既不使用任何第三方库,也不强迫您在SQLiteOpenHelper
子类上调用自定义方法来在创建时初始化数据库。它还负责数据库升级。所有需要做的就是继承SQLiteOpenHelper
.
android_metadata
的属性。locale
en_US
SQLiteOpenHelper
:SQLiteOpenHelper
。private
方法SQLiteOpenHelper
。此方法包含将数据库内容从“assets”文件夹中的数据库文件复制到应用程序包上下文中创建的数据库的逻辑。onCreate
和onUpgrade
的 onOpen
方法SQLiteOpenHelper
。说够了。这是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 stream
myInput = 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 streams
try {
if (myOutput != null) {
myOutput.close();
}
if (myInput != null) {
myInput.close();
}
} catch (IOException e) {
e.printStackTrace();
throw new Error(TAG + " Error closing streams");
}
}
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "onCreate db");
createDb = true;
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "onUpgrade db");
upgradeDb = true;
}
@Override
public 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:
}
}
}
最后,要获得数据库连接,只需在子类上调用getReadableDatabase()
or ,如果数据库不存在,它将负责创建数据库,从“assets”文件夹中的指定文件复制数据库内容。getWritableDatabase()
SQLiteOpenHelper
简而言之,您可以使用SQLiteOpenHelper
子类访问 assets 文件夹中提供的数据库,就像使用onCreate()
方法中使用 SQL 查询初始化的数据库一样。
2017 年 11 月,Google 发布了Room Persistence Library。
从文档中:
Room 持久性库在 SQLite 之上提供了一个抽象层,以允许流畅的数据库访问,同时利用 SQLite的全部功能。
该库可帮助您在运行您的应用的设备上创建应用数据的缓存。此缓存用作您的应用程序的单一事实来源,无论用户是否有互联网连接,用户都可以在您的应用程序中查看一致的关键信息副本。
Room 数据库在首次创建或打开数据库时有一个回调。您可以使用 create 回调来填充数据库。
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 thread
ioThread {
getInstance(context).dataDao()
.insert(PREPOPULATE_DATA)
}
}
})
.build()
此博客文章中的代码。
目前没有办法预先创建一个 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()。当它们可以被连接时,保存 7000 个“INSERT INTO tbl VALUE”是没有意义的。
在模拟器上大约需要 20 秒,我不知道在真手机上需要多长时间,但它只发生一次,当用户第一次启动应用程序时。
从我所看到的情况来看,您应该运送一个已经具有表设置和数据的数据库。但是,如果您愿意(并且取决于您拥有的应用程序类型),您可以允许“升级数据库选项”。然后你要做的是下载最新的sqlite版本,获取在线托管的文本文件的最新插入/创建语句,执行语句并将数据从旧数据库传输到新数据库。
最后我做到了!!我已经使用了这个链接帮助 Using your own SQLite database in Android applications,但不得不稍微改变一下。
如果你有很多包,你应该把主包名称放在这里:
private static String DB_PATH = "data/data/masterPakageName/databases";
我更改了将数据库从本地文件夹复制到模拟器文件夹的方法!当该文件夹不存在时,它有一些问题。所以首先,它应该检查路径,如果它不存在,它应该创建文件夹。
在前面的代码中,copyDatabase
当数据库不存在并且该checkDataBase
方法导致异常时,该方法永远不会被调用。所以我稍微改变了代码。
如果您的数据库没有文件扩展名,请不要将文件名与一个一起使用。
它对我很好,我希望它对你也有用
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 stream
InputStream myInput = myContext.getAssets().open(DB_NAME);
// Path to the just created empty db
String outFileName = DB_PATH +"/"+ DB_NAME;
File databaseFile = new File( DB_PATH);
// check if databases folder exists, if not create one and its subfolders
if (!databaseFile.exists()){
databaseFile.mkdir();
}
//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();
}
@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) {
}
you to create adapters for your views.
}
我修改了类和问题的答案,并编写了一个允许通过 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/";
else
DB_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;
}
@Override
public synchronized void close() {
if (mDataBase != null)
mDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public 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/";
else
DB_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;
}
@Override
public synchronized void close() {
if (mDataBase != null)
mDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion)
mNeedUpdate = true;
}
}
将数据库传送到 apk 中,然后将其复制到/data/data/...
数据库大小会加倍(apk 中为 1,apk 中为 1 data/data/...
),并且会增加 apk 大小(当然)。所以你的数据库不应该太大。
Android 已经提供了一种版本感知的数据库管理方法。这种方法已在 BARACUS 框架中用于 Android 应用程序。
它使您能够在应用程序的整个版本生命周期内管理数据库,能够将 sqlite 数据库从任何先前版本更新到当前版本。
我不能 100% 确定,但特定设备的热恢复可能使您能够在应用程序中发布准备好的数据库。但我不确定可能特定于某些设备、供应商或设备代的数据库二进制格式。
由于这些东西是 Apache License 2,请随意重用代码的任何部分,可以在 github 上找到
编辑 :
如果您只想传送数据,您可能会考虑在应用程序首次启动时实例化和持久化 POJO。BARACUS 对此提供了内置支持(用于配置信息的内置键值存储,例如“APP_FIRST_RUN”加上一个 after-context-bootstrap 挂钩,以便在上下文上运行启动后操作)。这使您能够将紧密耦合的数据与您的应用程序一起提供;在大多数情况下,这适合我的用例。
如果所需的数据不是太大(我不知道的限制,将取决于很多事情),您也可以从网站/webapp 下载数据(以 XML、JSON 等格式)。接收后,使用接收到的数据执行 SQL 语句,创建表并插入数据。
如果您的移动应用程序包含大量数据,那么以后使用更准确的数据或更改更新已安装应用程序中的数据可能会更容易。
我写了一个库来简化这个过程。
dataBase = new DataBase.Builder(context, "myDb").
// setAssetsPath(). // default "databases"
// setDatabaseErrorHandler().
// setCursorFactory().
// setUpgradeCallback()
// setVersion(). // default 1
build();
它将从assets/databases/myDb.db
文件创建一个数据库。此外,您将获得所有这些功能:
从github克隆它。
我正在使用 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();
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int fromVersion, int toVersion) {
}
}
请注意,代码从资产中的 zip 文件中提取数据库文件
如果您使用的是ROOM ,那么官方文档中已经有一条非常直接的路径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()
如果你不使用 Room,我强烈建议你这样做