在Android应用程序中对SQLite数据库执行查询时,什么被认为是最佳实践?
从AsyncTask的doInBackground中运行插入、删除和选择查询是否安全?或者我应该使用UI线程?我认为数据库查询可能是“沉重的”,不应该使用UI线程,因为它可以锁定应用程序-导致应用程序无响应(ANR)。
如果我有几个asynctask,他们应该共享一个连接还是应该各自打开一个连接?
对于这些场景有什么最佳实践吗?
在Android应用程序中对SQLite数据库执行查询时,什么被认为是最佳实践?
从AsyncTask的doInBackground中运行插入、删除和选择查询是否安全?或者我应该使用UI线程?我认为数据库查询可能是“沉重的”,不应该使用UI线程,因为它可以锁定应用程序-导致应用程序无响应(ANR)。
如果我有几个asynctask,他们应该共享一个连接还是应该各自打开一个连接?
对于这些场景有什么最佳实践吗?
当前回答
在纠结了几个小时之后,我发现每次db执行只能使用一个db helper对象。例如,
for(int x = 0; x < someMaxValue; x++)
{
db = new DBAdapter(this);
try
{
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
db.close();
}
相对于:
db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{
try
{
// ask the database manager to add a row given the two strings
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
}
db.close();
每次循环迭代时创建一个新的DBAdapter是我通过助手类将字符串放入数据库的唯一方法。
其他回答
数据库在多线程方面非常灵活。我的应用程序从许多不同的线程同时击中它们的db,它做得很好。在某些情况下,我有多个进程同时访问数据库,这也很好。
你的异步任务-当你可以使用相同的连接,但如果你必须,它可以从不同的任务访问数据库。
对于长时间运行的操作(50ms+),使用Thread或AsyncTask。测试你的应用,看看在哪里。大多数操作(可能)不需要线程,因为大多数操作(可能)只涉及几行。使用线程进行批量操作。 线程之间为磁盘上的每个DB共享一个SQLiteDatabase实例,并实现一个计数系统来跟踪打开的连接。
对于这些场景有什么最佳实践吗?
在所有类之间共享一个静态字段。我过去常常保持一个单例,用于那些需要共享的东西。还应该使用计数方案(通常使用AtomicInteger)来确保不会过早关闭数据库或让数据库保持打开状态。
我的解决方案:
我写的旧版本可以在https://github.com/Taeluf/dev/tree/main/archived/databasemanager上找到,并且没有维护。如果您想了解我的解决方案,请查看代码并阅读我的注释。我的笔记通常都很有用。
将代码复制/粘贴到名为DatabaseManager的新文件中。(或从github下载) 扩展DatabaseManager和实现onCreate和onUpgrade就像你通常会。您可以为一个DatabaseManager类创建多个子类,以便在磁盘上拥有不同的数据库。 实例化您的子类并调用getDb()来使用SQLiteDatabase类。 为您实例化的每个子类调用close()
复制/粘贴的代码:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import java.util.concurrent.ConcurrentHashMap;
/** Extend this class and use it as an SQLiteOpenHelper class
*
* DO NOT distribute, sell, or present this code as your own.
* for any distributing/selling, or whatever, see the info at the link below
*
* Distribution, attribution, legal stuff,
* See https://github.com/JakarCo/databasemanager
*
* If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
*
* Do not sell this. but use it as much as you want. There are no implied or express warranties with this code.
*
* This is a simple database manager class which makes threading/synchronization super easy.
*
* Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
* Instantiate this class once in each thread that uses the database.
* Make sure to call {@link #close()} on every opened instance of this class
* If it is closed, then call {@link #open()} before using again.
*
* Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
*
* I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
*
*
*/
abstract public class DatabaseManager {
/**See SQLiteOpenHelper documentation
*/
abstract public void onCreate(SQLiteDatabase db);
/**See SQLiteOpenHelper documentation
*/
abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**Optional.
* *
*/
public void onOpen(SQLiteDatabase db){}
/**Optional.
*
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
/**Optional
*
*/
public void onConfigure(SQLiteDatabase db){}
/** The SQLiteOpenHelper class is not actually used by your application.
*
*/
static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {
DatabaseManager databaseManager;
private AtomicInteger counter = new AtomicInteger(0);
public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
super(context, name, null, version);
this.databaseManager = databaseManager;
}
public void addConnection(){
counter.incrementAndGet();
}
public void removeConnection(){
counter.decrementAndGet();
}
public int getCounter() {
return counter.get();
}
@Override
public void onCreate(SQLiteDatabase db) {
databaseManager.onCreate(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
databaseManager.onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onOpen(SQLiteDatabase db) {
databaseManager.onOpen(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
databaseManager.onDowngrade(db, oldVersion, newVersion);
}
@Override
public void onConfigure(SQLiteDatabase db) {
databaseManager.onConfigure(db);
}
}
private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();
private static final Object lockObject = new Object();
private DBSQLiteOpenHelper sqLiteOpenHelper;
private SQLiteDatabase db;
private Context context;
/** Instantiate a new DB Helper.
* <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
*
* @param context Any {@link android.content.Context} belonging to your package.
* @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
* @param version the database version.
*/
public DatabaseManager(Context context, String name, int version) {
String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
synchronized (lockObject) {
sqLiteOpenHelper = dbMap.get(dbPath);
if (sqLiteOpenHelper==null) {
sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
dbMap.put(dbPath,sqLiteOpenHelper);
}
//SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
db = sqLiteOpenHelper.getWritableDatabase();
}
this.context = context.getApplicationContext();
}
/**Get the writable SQLiteDatabase
*/
public SQLiteDatabase getDb(){
return db;
}
/** Check if the underlying SQLiteDatabase is open
*
* @return whether the DB is open or not
*/
public boolean isOpen(){
return (db!=null&&db.isOpen());
}
/** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
* <br />If the new counter is 0, then the database will be closed.
* <br /><br />This needs to be called before application exit.
* <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
*
* @return true if the underlying {@link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
*/
public boolean close(){
sqLiteOpenHelper.removeConnection();
if (sqLiteOpenHelper.getCounter()==0){
synchronized (lockObject){
if (db.inTransaction())db.endTransaction();
if (db.isOpen())db.close();
db = null;
}
return true;
}
return false;
}
/** Increments the internal db counter by one and opens the db if needed
*
*/
public void open(){
sqLiteOpenHelper.addConnection();
if (db==null||!db.isOpen()){
synchronized (lockObject){
db = sqLiteOpenHelper.getWritableDatabase();
}
}
}
}
我知道响应晚了,但在android中执行sqlite查询的最佳方式是通过自定义内容提供程序。这样,UI就与数据库类(扩展SQLiteOpenHelper类的类)解耦了。此外,查询在后台线程(游标加载器)中执行。
在多线程中插入、更新、删除和读取通常是可以的,但是Brad的答案是不正确的。你必须小心如何创建和使用你的联系。在某些情况下,即使数据库没有损坏,更新调用也会失败。
最基本的答案。
SqliteOpenHelper对象保留一个数据库连接。它看似为您提供了读写连接,但实际上并非如此。调用read-only,无论如何都将获得写数据库连接。
一个helper实例,一个db连接。即使您在多个线程中使用它,也只能一次使用一个连接。SqliteDatabase对象使用java锁来保持访问序列化。因此,如果100个线程拥有一个db实例,则对实际磁盘上数据库的调用将被序列化。
一个helper,一个db连接,在java代码中序列化。一个线程,1000个线程,如果你使用一个helper实例在它们之间共享,你所有的db访问代码都是串行的。生活是美好的。
如果您试图同时从不同的实际连接写入数据库,其中一个将失败。它不会等到第一个完成后再写。它将不编写您的更改。更糟糕的是,如果您没有在SQLiteDatabase上调用正确的insert/update版本,则不会出现异常。您只会在LogCat中收到一条消息,仅此而已。
多线程?使用一个帮手。时期。如果你知道只有一个线程在写,你可以使用多个连接,你的读取速度会更快,但买家要小心。我还没测试那么多。
这里有一篇博客文章提供了更多的细节和一个示例应用程序。
Android Sqlite锁定(更新链接6/18/2012) android - database - lock - collisions - example by touchlab on GitHub
Gray和我实际上正在包装一个ORM工具,基于他的Ormlite,它与Android数据库实现原生工作,并遵循我在博客文章中描述的安全创建/调用结构。应该很快就能出来了。来看看。
与此同时,还有一篇后续博文:
单个SQLite连接
同样签出前面提到的锁定示例的2point0的fork:
Android-Database-Locking-Collisions-Example by 2point0 on GitHub
在纠结了几个小时之后,我发现每次db执行只能使用一个db helper对象。例如,
for(int x = 0; x < someMaxValue; x++)
{
db = new DBAdapter(this);
try
{
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
db.close();
}
相对于:
db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{
try
{
// ask the database manager to add a row given the two strings
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
}
db.close();
每次循环迭代时创建一个新的DBAdapter是我通过助手类将字符串放入数据库的唯一方法。