`
zhiweiofli
  • 浏览: 511678 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

【翻译】Android多线程下安全访问数据库

阅读更多
      为了记录如何线程安全地访问你的Android数据库实例,我写下了这篇小小札记。文章中引用的项目代码请点击这里
      假设你已编写了自己的 SQLiteOpenHelper
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();

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

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

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

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

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

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initialize(Context context, 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 synchronized SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

} 

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

// In your application class
 DatabaseManager.initializeInstance(getApplicationContext());

 // 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

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

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

       示例:

public class DatabaseManager {

    private AtomicInteger mOpenCounter = new AtomicInteger();

    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() {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

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

        }
    }}

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

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

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

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

提示 写道
你应该使用 AtomicInteger 来处理并发的情况

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

 

          本文由zhiweiofli编辑发布,转载请注明出处,谢谢。
0
0
分享到:
评论

相关推荐

    android sqlite多线程和异步加载数据库数据示例

    提供了Android多线程访问数据库的示例,包含了最基本的数据库操作以及采用CursorAdapter异步加载数据库数据示例

    Android中SQLite数据库查看工具

    SQLite是一个进程内的库,...SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。 SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能。 SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API。

    C#解决SQlite并发异常问题的方法(使用读写锁)

    本文实例讲述了C#解决SQlite并发异常问题的方法。分享给大家供大家参考,...作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标。 using System; using System.Collections.Generic; using System.Text;

    Android开发--多线程下载加断点续传

    1.多线程下载: 首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1; ...

    android客户端和服务器分别实现分页

    android端和服务器端(struts2+hibernate)编写,客户端和服务器端分别实现分页,android端采用activitygroup实现,android端分页通过http协议访问oracle数据库 只在第二个界面编写分页,采用多线程编写,附数据库·...

    Android开发与应用——张荣,原书配套课件

    7.4 Android多线程通信机制 7.5 小结 练习 第8章 网络通信 8.1 通过HTTP访问网络 8.1.1 测试用Web服务器 8.1.2 WebView组件 8.1.3 HttpURLConnection 8.2 Socket编程 8.3 数据的解析 8.3.1 JSON...

    Android开发案例驱动教程 配套代码

    10.4.2 Android平台下管理SQLite数据库 216 10.5 编写访问SQLite数据库组件 220 10.5.1 DBHelper类 220 10.5.2 数据插入 222 10.5.3 数据删除 224 10.5.4 数据修改 224 10.5.5 数据查询 227 10.6 案例重构 ...

    RxQuery:一个用于使用RxJava访问数据库的Android库

    RxQuery会为每个查询创建一个锁,因此当在一个查询内连续读取数据库时,您的数据已被另一个线程更改时,您将永远不会陷入困境。 数据更改后,将有一定的延迟来运行可更新的查询。 这样做是为了解决可观察性过度生成...

    老罗android开发视频教程全集百度网盘下载

    Android 是Google开发的基于Linux平台的开源手机操作系统。它包括操作系统、用户界面...【第一版第十四章】老罗Android开发视频--多线程编程(7集) 【第一版第十五章】老罗Android开发视频--百度地图实战开发(10集)

    新版Android开发教程.rar

    � 采用了对有限内存、电池和 CPU 优化过的虚拟机 Dalvik , Android 的运行速度比想象的要快很多。 � 运营商(中国移动等)的大力支持,产业链条的热捧。 � 良好的盈利模式( 3/7 开),产业链条的各方:运营商、...

    Android高级编程--源代码

    和任何其他产品早期的发行版一样,Android的软件和开发库还会经历很多正常的改进和完善。本书的内容和示例提供了如何使用当前SDK来编写优秀的移动程序所需要的基础知识,同时也保持了快速适应未来版本...

    《Android高级编程》

    11.1 Android的安全性 11.1.1 Linux内核安全 11.1.2 权限简介 11.1.3 声明和实施权限 11.1.4 为广播Intent实施权限 11.2 使用AIDL来支持服务IPC 11.3 使用Internet服务 11.4 构建内容丰富的用户界面 11.4.1 使用动画...

    Android程序设计基础

    无论你是业余爱好者还是专业程序员,无论你是自己玩玩还是为了盈利,都应该了解关于Android开发的更多信息。本书将帮助你迅速入门。.  Android的特别之处  如今,市场上已经有了许多移动平台,包括Symbian、...

    Android网络音乐播放器 源码下载

    多线程下载-多个线程分割下载任务提高下载效率并结合数据库实现断点下载; 异步任务AsyncTask执行耗时任务-音乐的收藏(使用到数据库)操作及音乐的搜索等需要访问网络的操作; 自定义view实现圆形专辑图片,滚动...

    Android应用程序开发教程PDF电子书完整版、Android开发学习教程

    • 内容提供器(Content Providers)使得应用程序可以访问另一个应用程序的数据(如联系人数据库),或 者共享它们自己的数据 • 资源管理器(Resource Manager)提供 非代码资源的访问,如本地字符串,图形,和布局...

    android开发秘籍

    11.3 android 的安全机制 241 11.4 android 的进程间通信 242 11.5 android 的备份管理器 247 11.5.1 秘诀95:备份运行时数据 247 11.5.2 秘诀96:备份文件到云端 248 11.5.3 秘诀97:触发备份与还原操作 249 ...

    Android高级编程.pdf

    6.4.3 使用Android数据库 6.5 内容提供器简介 6.5.1 使用内容提供器 6.5.2 本地Android内容提供器 6.5.3 创建一个新的内容提供器 6.5.4 创建和使用地震内容提供器 6.6 小结 第7章 地图、地理编码和基于位置的服务 ...

    精通ANDROID 3(中文版)1/2

    11.1.5 解决多线程问题  11.1.6 有趣的超时  11.1.7 使用HttpURLConnection  11.1.8 使用AndroidHttpClient  11.1.9 使用后台线程(AsyncTask)  11.1.10 使用AsyncTask处理配置更改  11.1.11 使用...

    疯狂Android讲义源码

     13.1.4 加入多线程 483  13.2 使用URL访问网络资源 488  13.2.1 使用URL读取网络资源 489  13.2.2 使用URLConnection  提交请求 490  13.3 使用HTTP访问网络 496  13.3.1 使用HttpURLConnection 496  ...

Global site tag (gtag.js) - Google Analytics