如果您的应用程序需要一个数据库,并且它带有内置数据,那么最好的运输方法是什么?我是不是该:
预先创建SQLite数据库并将其包含在
.apk
?中。在应用程序中包括SQL命令,并让它创建数据库并在首次使用时插入数据吗?
我看到的缺点是:
可能的SQLite版本不匹配可能会导致问题,并且我目前不知道数据库应该去哪里以及如何访问它。
在设备上创建和填充数据库可能会花费很长时间。
有什么建议么?有关任何问题的文档指针将不胜感激。
如果您的应用程序需要一个数据库,并且它带有内置数据,那么最好的运输方法是什么?我是不是该:
预先创建SQLite数据库并将其包含在.apk
?中。
在应用程序中包括SQL命令,并让它创建数据库并在首次使用时插入数据吗?
我看到的缺点是:
可能的SQLite版本不匹配可能会导致问题,并且我目前不知道数据库应该去哪里以及如何访问它。
在设备上创建和填充数据库可能会花费很长时间。
有什么建议么?有关任何问题的文档指针将不胜感激。
Answers:
创建和更新数据库有两个选项。
一种是在外部创建数据库,然后将其放置在项目的资产文件夹中,然后从那里复制整个数据库。如果数据库具有许多表和其他组件,则速度会更快。 通过更改res / values / strings.xml文件中的数据库版本号来触发升级。然后,可以通过在外部创建新数据库,用新数据库替换资产文件夹中的旧数据库,将旧数据库以另一个名称保存在内部存储中,将新数据库从资产文件夹复制到内部存储中,将所有将旧数据库(之前已重命名)中的数据存储到新数据库中,最后删除旧数据库。您可以最初使用SQLite Manager FireFox插件执行您的创建sql语句。
另一个选择是从sql文件内部创建数据库。这不是那么快,但是如果数据库只有几个表,那么对于用户来说,延迟可能不会引起注意。通过更改res / values / strings.xml文件中的数据库版本号来触发升级。 然后可以通过处理升级sql文件来完成升级。除删除容器(例如删除表)外,数据库中的数据将保持不变。
下面的示例演示如何使用这两种方法。
这是一个示例create_database.sql文件。它将放置在内部方法的项目资产文件夹中,或复制到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文件。它将放置在内部方法的项目的资产文件夹中,或复制到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依赖项(但是Jar也可用于Ant / Eclipse),并且可以在以下文档中找到它以及文档:https :
//github.com/jgilfelt/android-sqlite-asset-helper
注意:该项目不再按上述Github链接中所述进行维护。
如文档中所述:
将依赖项添加到模块的gradle构建文件中:
dependencies {
compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
}
将数据库复制到资产目录的子目录中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);
}
}
我的解决方案既不使用任何第三方库,也不强迫您在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()
或,它将负责创建数据库,如果数据库不存在,则从“资产”文件夹中的指定文件复制数据库内容。getWritableDatabase()
SQLiteOpenHelper
简而言之,您可以使用SQLiteOpenHelper
子类来访问assets文件夹中附带的数据库,就像使用该onCreate()
方法中使用SQL查询初始化的数据库一样。
对我来说,将应用程序与数据库文件一起交付是个好主意。优点是您不需要进行复杂的初始化,如果您的数据集很大,则有时会花费大量时间。
步骤1:准备数据库文件
准备好数据库文件。它可以是.db文件或.sqlite文件。如果使用.sqlite文件,则只需更改文件扩展名即可。步骤是相同的。
在此示例中,我准备了一个名为testDB.db的文件。它有一个表和一些示例数据,像这样
步骤2:将文件导入项目
如果没有,请创建资产文件夹。然后将数据库文件复制并粘贴到此文件夹中
步骤3:将档案复制到应用程式的资料资料夹
您需要将数据库文件复制到应用程序的数据文件夹中,以便与它进行进一步的交互。这是一次复制数据库文件的操作(初始化)。如果您多次调用此代码,则数据文件夹中的数据库文件将被资产文件夹中的数据库文件覆盖。如果您希望将来在应用程序更新期间更新数据库,则此覆盖过程很有用。
请注意,在应用程序更新期间,该数据库文件不会在应用程序的数据文件夹中更改。只有卸载会删除它。
数据库文件需要复制到/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
}
然后刷新文件夹以验证复制过程
步骤4:创建数据库打开帮助器
创建一个子类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();
点击运行按钮并加油!
2017年11月,Google发布了Room Persistence Library。
从文档中:
Room持久性库在SQ 强文本 Lite上提供了一个抽象层,以允许流利的数据库访问,同时利用SQLite的全部功能 。
该库可帮助您在运行应用程序的设备上创建应用程序数据的缓存。此缓存充当应用程序的唯一事实来源,允许用户查看应用程序内关键信息的一致副本,而不管用户是否有Internet连接。
首次创建或打开数据库时,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()
此博客文章中的代码。
最后我做到了!我已使用此链接帮助在Android应用程序中使用您自己的SQLite数据库,但必须对其进行一些更改。
如果您有许多软件包,则应在此处输入主软件包名称:
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.
}
当前,尚无法预创建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秒钟,我不知道在真正的手机上花费多长时间,但是只有在用户首次启动应用程序时才会发生一次。
INSERT INTO table VALUES(...) VALUES(...) VALUES(...) ...
(1条插入行应具有100个值)。它会更加高效,并将启动时间从20秒减少到2或3秒。
将数据库运送到apk内,然后将其复制到/data/data/...
将会使数据库的大小增加一倍(在apk中为1,在1 in中data/data/...
),并且会增加apk的大小(当然)。因此,您的数据库不应太大。
Android已经提供了一种版本感知的数据库管理方法。BARACUS框架已将这种方法用于Android应用程序。
它使您能够在应用程序的整个版本生命周期中管理数据库,从而能够将sqlite数据库从任何先前版本更新到当前版本。
我不确定100%,但是特定设备的热恢复可能使您能够在应用中发布准备好的数据库。但是我不确定特定于某些设备,供应商或设备一代的数据库二进制格式。
由于这些东西是Apache License 2,请随时重用代码的任何部分,可以在github上找到它们
编辑:
如果只想发送数据,则可以考虑在应用程序首次启动时实例化和持久化POJO。BARACUS为此提供了内置支持(用于配置信息的内置键值存储,例如“ APP_FIRST_RUN”以及后上下文自举程序挂钩,以便在上下文中运行发布后操作)。这使您可以在应用程序中附带紧密耦合的数据。在大多数情况下,这适合我的用例。
我修改了该类和问题的答案,并编写了一个类,该类允许通过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;
}
}
我编写了一个库来简化此过程。
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文件中提取数据库文件