Android上SQLite的最佳实践是什么?

Android上SQLite的最佳实践是什么?,android,database,sqlite,Android,Database,Sqlite,在Android应用程序中对SQLite数据库执行查询时,什么是最佳实践 从AsyncTask的doInBackground运行INSERT、DELETE和select查询是否安全?还是应该使用UI线程?我认为数据库查询可能是“繁重的”,不应该使用UI线程,因为它会锁定应用程序-导致(ANR) 如果我有几个异步任务,它们应该共享一个连接还是各自打开一个连接 对于这些场景,有什么最佳实践吗?数据库具有非常灵活的多线程功能。我的应用程序从多个不同的线程同时命中了它们的DBs,而且效果很好。在某些情况

在Android应用程序中对SQLite数据库执行查询时,什么是最佳实践

从AsyncTask的doInBackground运行INSERT、DELETE和select查询是否安全?还是应该使用UI线程?我认为数据库查询可能是“繁重的”,不应该使用UI线程,因为它会锁定应用程序-导致(ANR)

如果我有几个异步任务,它们应该共享一个连接还是各自打开一个连接


对于这些场景,有什么最佳实践吗?

数据库具有非常灵活的多线程功能。我的应用程序从多个不同的线程同时命中了它们的DBs,而且效果很好。在某些情况下,我有多个进程同时命中数据库,这也很好


异步任务-在可能的情况下使用相同的连接,但如果必须,可以从不同的任务访问数据库

从多个线程插入、更新、删除和读取通常都可以,但Brad的不正确。你必须小心如何创建和使用你的连接。有些情况下,即使数据库没有损坏,更新调用也会失败

基本答案。

SqliteOpenHelper对象保留一个数据库连接。它似乎为您提供了读写连接,但实际上并非如此。调用只读,您将获得写入数据库连接

因此,一个helper实例,一个db连接。即使您从多个线程使用它,也要一次使用一个连接。SqliteDatabase对象使用java锁保持访问序列化。因此,如果100个线程有一个db实例,那么对实际磁盘数据库的调用将被序列化

因此,一个助手,一个db连接,用java代码序列化。一个线程,1000个线程,如果您使用它们之间共享的一个helper实例,那么所有db访问代码都是串行的。生活是美好的

如果同时尝试从实际的不同连接写入数据库,则其中一个连接将失败。它不会等到第一次完成后再写。它不会写下你的变化。更糟糕的是,如果没有在SQLiteDatabase上调用正确版本的insert/update,则不会出现异常。你只需要在日志中得到一条消息,就这样

那么,多线程?使用一个助手。时期如果您知道只有一个线程将被写入,那么您可能能够使用多个连接,并且您的读取速度会更快,但请注意。我没试过那么多

下面是一篇更详细的博客文章和一个示例应用程序

  • (更新链接6/18/2012)
  • 关于GitHub
Gray和我实际上正在完成一个基于Ormlite的ORM工具,该工具与Android数据库实现协同工作,并遵循我在博客文章中描述的安全创建/调用结构。那应该很快就会出来。看一看


同时,还有一篇后续博文:

还可以通过前面提到的锁定示例的2点0检查拨叉:

  • 关于GitHub

我对SQLiteDatabase API的理解是,如果您有一个多线程的应用程序,那么您不能有超过1个指向单个数据库的SQLiteDatabase对象

当然可以创建对象,但是如果不同的线程/进程(也)开始使用不同的SQLiteDatabase对象(比如我们在JDBC连接中使用的对象),则插入/更新会失败

这里唯一的解决方案是坚持使用1个SQLiteDatabase对象,每当startTransaction()在多个线程中使用时,Android会管理不同线程之间的锁定,并且一次只允许1个线程具有独占更新访问权限

此外,您还可以从数据库中执行“读取”,并在不同的线程中使用相同的SQLiteDatabase对象(而另一个线程写入),并且不会出现数据库损坏,即“读取线程”在“写入线程”提交数据之前不会从数据库中读取数据,尽管两者都使用相同的SQLiteDatabase对象

这与JDBC中连接对象的方式不同,在JDBC中,如果在读线程和写线程之间传递(使用相同的)连接对象,那么我们可能也会打印未提交的数据


在我的企业应用程序中,我尝试使用条件检查,以便UI线程不必等待,而BG线程(独占地)持有SQLiteDatabase对象。我尝试预测UI操作,并将BG线程的运行延迟“x”秒。还可以维护PriorityQueue来管理分发SQLiteDatabase连接对象,以便UI线程首先获得它。

经过几个小时的努力,我发现每个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();
}
for(int x=0;x
任命如下:

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();
db=新的DBAdapter(此);
对于(int x=0;x
每次循环迭代时创建一个新的DBAdapter是我通过he将字符串输入数据库的唯一方法
public class DatabaseHelper extends SQLiteOpenHelper { ... }
 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}
 // In your application class
 DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
public class DatabaseManager {

    private int mOpenCounter;

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        mOpenCounter++;
        if(mOpenCounter == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        mOpenCounter--;
        if(mOpenCounter == 0) {
            // Closing database
            mDatabase.close();

        }
    }

}
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
public synchronized SQLiteDatabase openDatabase() {
    if(mOpenCounter.incrementAndGet() == 1) {
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    return mDatabase;
}
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();
                }
        } 
    }
}
@Entity
public class User {
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.
}
@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);
}
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}