6.8 SQLite 相关的异常
在App中,一般都使用SQLite这个数据库,本节介绍的Crash也是围绕着这个主题发生的。SQLite相关的异常大都和IO操作不当有关,由于我们无法猜测用户手机发生崩溃时的状态,所以这类异常是最难修复的。
6.8.1 No transaction is active
异常中的关键字:
android.database.sqlite.SQLiteException:cannot commit–no transaction is active
发生频率:★★★
在事务中,逐条循环插入(for+insert)大量数据时会导致这类崩溃。Android中在SqlLite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作,而且不能保证所有数据都能同时插入。
相应的解决方案是使用SQLLite提供的批量插入语法,一次性地把这些数据都插入到数据库中,如下所示:
public void insertOrUpdateDataBatch() { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { for (String sql : sqls) { db.execSQL(sql); } // 设置事务标志为成功,当结束事务时就会提交事务 db.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); } finally { db.endTransaction(); db.close(); } }
这段代码的成功与否,就在于setTransactionSuccessful()这个方法。在这个方法执行前,所有的execSQL方法都不会更新到数据库;等这个方法执行后,会一次性把所有execSQL方法都执行完成,数据会同步到数据库。不信的话可以在for循环处打个断点,看数据库是否有变化。
6.8.2 忘记关闭Cursor
异常中的关键字:
android.database.CursorWindowAllocationException:Cursor window allocation of 2048 kb failed
发生频率:★★★
这个异常是因为使用SQLLite时忘记释放游标导致的,内存泄漏得多了,就崩溃了。相应的解决办法就是手动关闭Cursor,如下所示:
cursor.close();
6.8.3 数据库被锁定
异常中的关键字:
android.database.sqlite.SQLiteDatabaseLockedException:database is locked
发生频率:★
当我们试图在不同的线程中创建多个连接时,就会抛出这个异常。相应的解决方案是将数据库做成一个单例。 [1]
单例固然能解决单进程操作数据库的情况,但是对于多进程App而言,还是需要ContentProvider。
6.8.4 试图再打开已经关闭的对象
异常中的关键字:
java.lang.IllegalStateException:attempt to re-open an already-closed object
发生频率:★
这个问题是上一个问题的延续。即使做成了单例,如果在不同的线程中创建多个连接,就会报当前的错误信息。
频繁地操作SQLite数据库容易产生这个崩溃。我们习惯于每执行一次数据库操作,都打开和关闭数据库各一次。这就会导致当两个线程同时操作数据库时,比如,A为读数据,B为写数据,当A读完就会关闭数据库,而B这时正在写数据,那么上述Crash就会产生了。
在实际应用中,App中的IM,因为要把聊天信息存放到本地SQLite,最容易看到这类异常,这时好的做法是,在当前聊天室,保持数据库一直处于Open状态,等退出聊天室再执行close方法。
6.8.5 文件加密了或无数据库
异常中的关键字:
SQLiteDatabaseCorruptException:file is encrypted or is not a database
发生频率:★
请注意SQLLite DB文件的版本,如果有两个DB,一个是2.8.17,另一个是3.7.7.1,那么就会出现这个异常。将其统一成一个版本即可。
此外,如果DB破损,也可能出现这种异常。当我们将App安装在SD卡上,多次插拔就会导致部分文件破损。
6.8.6 WebView中SQLLite缓存导致的崩溃
异常中的关键字:
SQLiteDiskIOException:disk I/O error……at
android.webkit.WebViewDatabase$1.run(WebViewDatabase.java:1000)
发生频率:★
注意
这个异常信息中还带有WebViewDatabase的内容,说明我们的程序使用了WebView控件的缓存技术。但是原因不详。有人说把数据库删除了就会崩溃,但我试过了,对WebView是无效的。
由此而谈到Android中WebView的缓存策略。WebView中存在着两种缓存:
·网页数据缓存,存储打开过的页面及资源。
·Html5缓存,即appcache。
缓存数据的构成如图6-4所示。
图6-4 WebView中的cache数据
WebView自带的缓存机制里面,会将url保存在webviewCashe.db中,将url内容保存在webviewCashe文件夹下,比如说图中的10d8d5cd,就是url对应的一张图片,此外html、js等文件也会存下来。
而对于databases目录下的webview.db和webviewCashe.db,都会自动生成1个名为android_metadata的表,只要创建SQLLite数据库中的表,就会自动创建这个表,表中只有一个locale字段,里面存放的是en-US或者zh-CN这样的值(是哪个值取决于Android系统),用于标示语言文化。从数据库中读取的文本是否为乱码,就取决于这个值了。
我的系统是英文的,所以我检查过locale值是en-US;而我同事的中文系统则显示zh-CN。
缓存模式有五种,见表6-1。
表6-1 缓存模式
根据以上几种模式,建议缓存策略为:判断是否有网络,有的话,使用LOAD_DEFAULT,无网络时,使用LOAD_CACHE_ELSE_NETWORK。
6.8.7 磁盘读写错误
异常中的关键字:
android.database.sqlite.SQLiteDiskIOException:disk I/O error(code 1802)
发生频率:★
我曾经认为,在UI线程执行dbHelper.getWritableDatabase()这句话的时候,UI线程会把数据库锁住。但是后来Bugly的“精神哥”告诉我,dbHelper只有在创建数据库、进行事务处理时才会锁住数据库。默认情况下dbHelper会缓存DB实例,执行类似于getWritableDatabase的操作是立即返回的,并不会上锁。
disk I/O error这类异常的抛出,是因为多线程修改DB,比如一个线程在写数据,另一个线程却在删除数据。
6.8.8 android_metadata表不存在
异常中的关键字:
android.database.sqlite.SQLiteException:no such table:android_metadata SQLiteOpenHelper.getReadableDatabase
发生频率:★
开发中需要连接SQLite数据库,当使用如下方法打开数据库时就会抛出上述错误:
SQLiteDatabase database = SQLiteDatabase.openDatabase( PATH, null, SQLiteDatabase.OPEN_READONLY);
解决办法是,将openDatabase方法中最后一个参数修改为SQLiteDatabase.
NO_LOCALIZED_COLLATORS即可。
6.8.9 android_metadata表中的locale字段
异常中的关键字:
android.database.sqlite.SQLiteException:Failed to change locale for db'/data/data/appname/databases/webview.db'to'zh_CN'.
发生频率:★
根据对6.8.8中Crash的分析,我们知道android_metadata这个表中有个locale字段。
这里要介绍的Crash发生在WebView控件生成的缓存数据库中,但是发生的概率极小(个位数)。对此,众说纷坛。甚至有美国人在StackOverFlow上说中国产的手机也报这个异常,只是不能转换为en-US而已。我只能怀疑是ROM的问题。
6.8.10 数据库或磁盘满了
异常中的关键字:
android.database.sqlite.SQLiteFullException:database or disk is full
发生频率:★
当数据库文件存放在内存中时,就和存文件或者SharedPreferences一样,会因为内存满了而报错,只是这次的错误信息更具体,会提示我们数据库/磁盘满了。
[1] 单例的实现请参见:http://zhiwei.neatooo.com/blog/detail?blog=5343818a9d4869f0310000de。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论