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

从AsyncTask的doInBackground中运行插入、删除和选择查询是否安全?或者我应该使用UI线程?我认为数据库查询可能是“沉重的”,不应该使用UI线程,因为它可以锁定应用程序-导致应用程序无响应(ANR)。

如果我有几个asynctask,他们应该共享一个连接还是应该各自打开一个连接?

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


当前回答

德米特罗的回答很适合我。 我认为最好将函数声明为同步的。至少在我的情况下,它会调用空指针异常,否则,例如getWritableDatabase尚未返回在一个线程和opendatabase在另一个线程同时调用。

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

其他回答

在经历了一些问题之后,我想我已经明白为什么我走错了。

我写了一个数据库包装类,其中包括一个close(),它调用helper close作为open()的镜像,它调用getWriteableDatabase,然后迁移到ContentProvider。ContentProvider的模型不使用sqlitedatdatabase .close(),我认为这是一个很大的线索,因为代码确实使用了getWriteableDatabase在某些情况下,我仍然在做直接访问(屏幕验证查询,所以我迁移到一个getWriteableDatabase/rawQuery模型。

我使用了一个单例,在关闭的文档中有一些不祥的注释

关闭任何打开的数据库对象

(我的粗体)。

因此,当我使用后台线程访问数据库时,它们与前台同时运行,就会出现间歇性崩溃。

因此,我认为close()强制数据库关闭,而不管是否有其他线程持有引用——因此close()本身不是简单地撤销匹配的getWriteableDatabase,而是强制关闭任何打开的请求。大多数情况下,这不是问题,因为代码是单线程的,但在多线程的情况下,总是有机会打开和关闭不同步。

Having read comments elsewhere that explains that the SqLiteDatabaseHelper code instance counts, then the only time you want a close is where you want the situation where you want to do a backup copy, and you want to force all connections to be closed and force SqLite to write away any cached stuff that might be loitering about - in other words stop all application database activity, close just in case the Helper has lost track, do any file level activity (backup/restore) then start all over again.

虽然这听起来像一个好主意,尝试和关闭在受控的方式,现实是Android保留垃圾你的虚拟机的权利,所以任何关闭都是减少缓存更新不被写入的风险,但不能保证如果设备是有压力的,如果你已经正确释放游标和数据库引用(不应该是静态成员),那么助手将关闭数据库无论如何。

所以我的看法是:

使用getWriteableDatabase从单例包装器打开。(我使用了一个派生的应用程序类来提供应用程序上下文,从而解决了对上下文的需求)。

永远不要直接接近。

永远不要将结果数据库存储在任何没有明显作用域并依赖引用计数来触发隐式close()的对象中。

如果进行文件级处理,请暂停所有数据库活动,然后调用close,以防出现失控线程,假设您编写了适当的事务,那么失控线程将失败,关闭的数据库至少将拥有适当的事务,而不是部分事务的文件级副本。

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

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

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

你也可以从数据库中“读取”,并在不同的线程中使用相同的SQLiteDatabase对象(而另一个线程写入),永远不会有数据库损坏,即“读线程”不会从数据库中读取数据,直到“写线程”提交数据,尽管两者都使用相同的SQLiteDatabase对象。

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

在我的企业应用程序中,我尝试使用条件检查,以便UI线程永远不必等待,而BG线程持有SQLiteDatabase对象(独占)。我试图预测UI动作和推迟BG线程从运行'x'秒。还可以维护PriorityQueue来管理分发SQLiteDatabase Connection对象,以便UI线程首先获得它。

我知道响应晚了,但在android中执行sqlite查询的最佳方式是通过自定义内容提供程序。这样,UI就与数据库类(扩展SQLiteOpenHelper类的类)解耦了。此外,查询在后台线程(游标加载器)中执行。

您可以尝试应用谷歌I/O 2017上宣布的新架构方法。

它还包括新的ORM库Room

它包含三个主要组件:@Entity, @Dao和@Database

User.java

@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.
}

UserDao.java

@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);
}

AppDatabase.java

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

德米特罗的回答很适合我。 我认为最好将函数声明为同步的。至少在我的情况下,它会调用空指针异常,否则,例如getWritableDatabase尚未返回在一个线程和opendatabase在另一个线程同时调用。

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