前言

相信大家在开发过程中,也遇到过下面的这种异常:

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

异常的解释:就是当你尝试打开一个可读可写的数据库时,该数据库已经被关闭,打开失败就会抛出该异常~

异常的原因:在我们开发过程中,会有很多数据需要在本地存储(像我们公司做的是教育软件,用户会产生大量的做题数据!)。如果需要操作大量的数据,SQLite肯定是首选,而且数据库的读写操作并不一定是按顺序去执行的。肯定会存在读和写并存的情况:有的线程在操作写入,有的线程在操作读取;假如有一条线程执行完后关闭了数据库,那么另一条线程就会抛出异常,因为数据库被关闭了。


在开发中,这种异步操作数据库的情况非常多,所以如果没有一个好的解决方法,项目的崩溃或异常数据会非常多。可能有些小伙伴会使用"try...catch..."处理,把异常都捕获,尽量避免了崩溃,但是这种情况会造成数据丢失(数据写入失败或数据读取失败)。那么,我们该如何解决这种问题呢?

下面就讲述下我要和大家说的这种解决方案:使用"AtomicInteger"控制数据库(SQLiteDatabase)的开和关~

先上代码(后面再详解)

首先:创建一个数据库操作对象

//创建一个本地数据库操作对象(SQLiteOpenHelper是android提供的一个帮助类,便于操作数据库)
public class SQLiteDBHelper extends SQLiteOpenHelper { public SQLiteDBHelper(Context context) {
//@ 参数2 name 数据库的文件名称
//@ 参数3 factory to use for creating cursor objects, or null for the default
//@ 参数4 version 数据库版本的控制 number of the database (starting at 1) Android4.0版本之后只能升不能降
super(context, "user.db", null, 1);
} //数据库第一次创建的时候,会执行 onCreate 方法(本地数据库删除后再创建也属于新建)
@Override
public void onCreate(SQLiteDatabase db) {
createTables(db);
} //数据库版本发生改变的时候,会执行 onUpgrade 方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
createTables(db);
} //创建表
private void createTables(SQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT,age INTEGER,sex INTEGER,number INTEGER,address TEXT);");
}
}

其次:创建一个数据开&关的帮助类

/**
* 数据库打开关闭帮助类
* 针对本地自建的数据库
*/
public class DBOpenManager {
private static DBOpenManager dbOpenManager;
private SQLiteDatabase database;
private AtomicInteger atomicInteger;
//用AtomicInteger来解决数据表异步操作的问题
private SQLiteDBHelper dbHelper; //私有化构造器
private DBOpenManager() {
initData();
} //初始化基本数据
private void initData() {
if (dbHelper == null) {
dbHelper = new SQLiteDBHelper(MyApplication.getAppContext());
}
if (atomicInteger == null) {
atomicInteger = new AtomicInteger();
}
} //单例模式获取操作类对象(懒汉式)
public static DBOpenManager getInstance() {
if (dbOpenManager == null) {
synchronized (DBOpenManager.class) {
if (dbOpenManager == null) {
dbOpenManager = new DBOpenManager();
}
}
}
return dbOpenManager;
} //打开数据库. 返回数据库操作对象
public synchronized SQLiteDatabase openDatabase() {
initData();
//查看当前 AtomicInteger 中的 value 值
Log.e("AtomicInteger", "开前:" + atomicInteger.get());
if (atomicInteger.incrementAndGet() == 1) {
try { //获取一个可读可写的数据库操作对象
database = dbHelper.getWritableDatabase();
dbHelper.getReadableDatabase();
} catch (Exception e) {
atomicInteger.set(0);
e.printStackTrace();
}
}
Log.e("AtomicInteger", "开后:" + atomicInteger.get());
return database;
} //关闭数据库
public synchronized void closeDatabase() {
//查看当前 AtomicInteger 中的 value 值
Log.e("AtomicInteger", "关前:" + atomicInteger.get());
if (atomicInteger.decrementAndGet() <= 0) {//避免关闭多次后数据库产生异常
atomicInteger.set(0);
Utils.closeCloseable(database);
database = null;
}
Log.e("AtomicInteger", "关后:" + atomicInteger.get());
}
}

Utils 里面的方法 closeCloseable 如下:

    public static void closeCloseable(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

测试

//new 一个线程延迟1, 打开数据库
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SQLiteDatabase openDatabase = DBOpenManager.getInstance().openDatabase();
boolean isOpen = openDatabase.isOpen();
System.out.println("AtomicInteger +++++++++++++++++++++ " + isOpen);
}
}).start(); //Handler 延迟2秒,关闭数据库
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
DBOpenManager.getInstance().closeDatabase();
}
}, 2000); //new 一个线程也延迟2秒,再次打开数据库
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SQLiteDatabase openDatabase = DBOpenManager.getInstance().openDatabase();
boolean isOpen = openDatabase.isOpen();
System.out.println("AtomicInteger --------------------- " + isOpen);
}
}).start();

ok

模板代码贴完了,下面具体说明下这种方案:

  1. 创建一个数据库操作对象。这一步大家都懂(我就不班门弄斧了

    解决Android数据库异步操作的大问题的更多相关文章

    1. Android数据库专家秘籍(七)经验LitePal查询艺术

      转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/40153833 经过了多篇文章的学习,我们已经把LitePal中的绝大部分内容都掌握 ...

    2. Android数据库框架——ORMLite轻量级的对象关系映射(ORM)Java包

      Android数据库框架--ORMLite轻量级的对象关系映射(ORM)Java包 事实上,我想写数据库的念头已经很久了,在之前写了一个答题系统的小项目那只是初步的带了一下数据库,数据库是比较强大的, ...

    3. 自己动手写Android数据库框架

      前言 相信不少开发人员跟我一样,每次都非常烦恼自己写数据库,并且那些数据库语句也经常记不住.当然网上也有非常多非常好的数据库框架,你能够直接拿来用,可是 非常多时候我们的项目.特别是一个小型的Andr ...

    4. Android数据库高手秘籍(三)——使用LitePal升级表

      转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/39151617 在上一篇文章中,我们学习了LitePal的基本使用方法,体验了使用框 ...

    5. Android数据库高手秘籍(二):创建表和LitePal的基本用法

      原文:http://blog.jobbole.com/77157/ 上一篇文章中我们学习了一些Android数据库相关的基础知识,和几个颇为有用的SQLite命令,都是直接在命令行操作的.但是我们都知 ...

    6. Android权限管理之RxPermission解决Android 6.0 适配问题

      前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...

    7. 深入解析Sqlite的完美替代者,android数据库新王者——Realm

      写在前面: 又到一年一度七夕虐狗节,看着大家忍受着各种朋友圈和QQ空间还有现实生活中的轮番轰炸,我实在不忍心再在这里给大家补刀,所以我觉得今天不虐狗,继续给大家分享有用的. 如果你比较关心androi ...

    8. [Android Pro] 完美Android Cursor使用例子(Android数据库操作)

      reference to : http://www.ablanxue.com/prone_10575_1.html 完美 Android Cursor使用例子(Android数据库操作),Androi ...

    9. 解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题

      解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题http ...

    随机推荐

    1. npm install 安装很慢

      npm install 安装很慢 设置国内镜像 npm config set registry https://registry.npm.taobao.org npm install

    2. 关于js-xlsx的使用

      写在前头,本人是名Java开发人员,偶尔在前端打打酱油,写出的代码或许存在问题,请路过的大神一一指正,不吝感激. 最近公司准备做一些关于Excel 数据导入和导出相关需求,之前有在开源社区看到说比起纯 ...

    3. Python --深入浅出Apriori关联分析算法(二) Apriori关联规则实战

      上一篇我们讲了关联分析的几个概念,支持度,置信度,提升度.以及如何利用Apriori算法高效地根据物品的支持度找出所有物品的频繁项集. Python --深入浅出Apriori关联分析算法(一) 这次 ...

    4. 结合suctf-upload labs-RougeMysql再学习

      这篇主要记录一下这道题目的预期解法 做这道题首先要在自己的vps搭建一个rouge mysql,里面要填写需要读取客户端的文件名,即我们上传的phar文件路径 先搭一个rouge mysql测试看看: ...

    5. Mybatis的一级缓存和二级缓存的理解以及用法

      程序中为什么使用缓存? 先了解一下缓存的概念:原始意义是指访问速度比一般随机存取存储器快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术.对于我们编程来说,所谓的 ...

    6. Leetcode之二分法专题-374. 猜数字大小(374. Guess Number Higher or Lower)

      Leetcode之二分法专题-374. 猜数字大小(374. Guess Number Higher or Lower) 我们正在玩一个猜数字游戏. 游戏规则如下:我从 1 到 n 选择一个数字. 你 ...

    7. 使用SVN钩子强制提交日志和限制提交文件类型

      Subversion本身有很好的扩展性,用户可以通过钩子实现一些自定义的功能.所谓钩子实际上是一种事件机制,当系统执行到某个特殊事件时,会触发我们预定义的动作,这样的特殊事件在Subversion里有 ...

    8. 掀起你的盖头来:浅谈项目管理办公室(PMO)

      [提示]本文为“分享:<PMBOOK>读书笔记系列”由傻瓜(来自人人都是产品经理6群)编写. 之前与大家一定对项目.项目管理.项目集.项目组合等知识进行了简单的学习,如果有不太清楚和不太明 ...

    9. [Error]syntaxerror: non-ascii character '/xd6' in file

      eclipse代码运行时显示:syntaxerror: non-ascii character '/xd6' in file 原因:如果文件里有非ASCII字符,需要在第一行或第二行指定编码声明. 解 ...

    10. E-MAZE_2019牛客暑期多校训练营(第二场)

      题意 给出n行m列的迷宫0可走1不可走,有两个操作,操作1变换点(a,b)的值,操作2查询(1,a)到(n,b)的方案数 题解 设\(F[i][j]\)为第i-1行到达第i行第j列的方案数,若点\(( ...