Android多线程下安全访问数据库

Android SQLiteAndroid 数据库操作数据库多线程操作 

      为了记录如何线程安全地访问你的Android数据库实例,我写下了这篇小小札记。文章中引用的项目代码请点击这里

      假设你已编写了自己的 SQLiteOpenHelper

Java代码 

 收藏代码

  1. public class DatabaseHelper extends SQLiteOpenHelper { ... }  

        现在你想在不同的线程中对数据库进行写数据操作:

Java代码 

 收藏代码

  1. // Thread 1  
  2.  Context context = getApplicationContext();  
  3.  DatabaseHelper helper = new DatabaseHelper(context);  
  4.  SQLiteDatabase database = helper.getWritableDatabase();  
  5.  database.insert(…);  
  6.  database.close();  
  7.   
  8.  // Thread 2  
  9.  Context context = getApplicationContext();  
  10.  DatabaseHelper helper = new DatabaseHelper(context);  
  11.  SQLiteDatabase database = helper.getWritableDatabase();  
  12.  database.insert(…);  
  13.  database.close();  

        然后在你的Logcat中将输出类似下面的日志信息,而你的写数据操作将会无效。

Java代码 

 收藏代码

  1. android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)  

       上面问题的出现,源于你每创建一个 SQLiteOpenHelper  对象时,实际上也是在新建一个数据库连接。如果你尝试通过多个连接同时对数据库进行写数据操作,其一定会失败。

        为确保我们能在多线程中安全地操作数据库,我们需要保证只有一个数据库连接被占用。

        我们先编写一个负责管理单个 SQLiteOpenHelper 对象的单例 DatabaseManager 。 

Java代码 

 收藏代码

  1. public class DatabaseManager {  
  2.   
  3.     private static DatabaseManager instance;  
  4.     private static SQLiteOpenHelper mDatabaseHelper;  
  5.   
  6.     public static synchronized void initialize(Context context, SQLiteOpenHelper helper) {  
  7.         if (instance == null) {  
  8.             instance = new DatabaseManager();  
  9.             mDatabaseHelper = helper;  
  10.         }  
  11.     }  
  12.   
  13.     public static synchronized DatabaseManager getInstance() {  
  14.         if (instance == null) {  
  15.             throw new IllegalStateException(DatabaseManager.class.getSimpleName() +  
  16.                     " is not initialized, call initialize(..) method first.");  
  17.         }  
  18.   
  19.         return instance;  
  20.     }  
  21.   
  22.     public synchronized SQLiteDatabase getDatabase() {  
  23.         return new mDatabaseHelper.getWritableDatabase();  
  24.     }  
  25.   
  26. }   

        为了能在多线程中进行写数据操作,我们得修改一下代码,具体如下: 

Java代码 

 收藏代码

  1. // In your application class  
  2.  DatabaseManager.initializeInstance(getApplicationContext());  
  3.   
  4.  // Thread 1  
  5.  DatabaseManager manager = DatabaseManager.getInstance();  
  6.  SQLiteDatabase database = manager.getDatabase()  
  7.  database.insert(…);  
  8.  database.close();  
  9.   
  10.  // Thread 2  
  11.  DatabaseManager manager = DatabaseManager.getInstance();  
  12.  SQLiteDatabase database = manager.getDatabase()  
  13.  database.insert(…);  
  14.  database.close();  

        然后又导致另个崩毁

Java代码 

 收藏代码

  1. java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase  

        既然我们只有一个数据库连接,Thread1 和 Thread2 对方法 getDatabase() 的调用就会取得一样的 SQLiteDatabase 对象实例。之后的事情就是,当 Thread1 尝试管理数据库连接时,Thread2 却仍然在使用该数据库连接。这也就是导致 IllegalStateException 崩毁的原因。

      因此我们只能在确保数据库没有再被占用的情况下,才去关闭它。在 stackoveflow 上有一些讨论推荐“永不关闭”你的 SQLiteDatabase 。  如果你这样做,你的logcat将会出现以下的信息,因此我不认为这是一个好主意。

Java代码 

 收藏代码

  1. Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed  

       示例:

Java代码 

 收藏代码

  1. public class DatabaseManager {  
  2.   
  3.     private AtomicInteger mOpenCounter = new AtomicInteger();  
  4.   
  5.     private static DatabaseManager instance;  
  6.     private static SQLiteOpenHelper mDatabaseHelper;  
  7.     private SQLiteDatabase mDatabase;  
  8.   
  9.     public static synchronized void initializeInstance(SQLiteOpenHelper helper) {  
  10.         if (instance == null) {  
  11.             instance = new DatabaseManager();  
  12.             mDatabaseHelper = helper;  
  13.         }  
  14.     }  
  15.   
  16.     public static synchronized DatabaseManager getInstance() {  
  17.         if (instance == null) {  
  18.             throw new IllegalStateException(DatabaseManager.class.getSimpleName() +  
  19.                     " is not initialized, call initializeInstance(..) method first.");  
  20.         }  
  21.   
  22.         return instance;  
  23.     }  
  24.   
  25.     public synchronized SQLiteDatabase openDatabase() {  
  26.         if(mOpenCounter.incrementAndGet() == 1) {  
  27.             // Opening new database  
  28.             mDatabase = mDatabaseHelper.getWritableDatabase();  
  29.         }  
  30.         return mDatabase;  
  31.     }  
  32.   
  33.     public synchronized void closeDatabase() {  
  34.         if(mOpenCounter.decrementAndGet() == 0) {  
  35.             // Closing database  
  36.             mDatabase.close();  
  37.   
  38.         }  
  39.     }}  

         然后你可以怎样子去调用它:

Java代码 

 收藏代码

  1. SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();  
  2. database.insert(...);  
  3. // database.close(); Don't close it directly!  
  4. DatabaseManager.getInstance().closeDatabase(); // correct way  

         以后每当你需要使用数据库连接,你可以通过调用类 DatabaseManager 的方法openDatabase()。在方法里面,内置一个标志数据库被打开多少次的计数器。如果计数为1,代表我们需要打开一个新的数据库连接,否则,数据库连接已经存在。

在方法 closeDatabase() 中,情况也一样。每次我们调用 closeDatabase() 方法,计数器都会递减,直到计数为0,我们就需要关闭数据库连接了。

提示 写道

你应该使用 AtomicInteger 来处理并发的情况

          现在你可以线程安全地使用你的数据库连接了。

发布了353 篇原创文章 · 获赞 53 · 访问量 19万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 创作都市 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览