由一个Buffer引发的血案

背景

现在的项目需要是:服务器端使用Servlet+JDBC技术从Oracle服务器读取内容写入SQLite数据库文件,Android客户端下载这个文件并在目标Android系统上使用它。

问题再现

上述需求似乎并不复杂,但在我下载了生成的数据库文件之后,却连基本的SQL语句都执行不了。例如有一个表名为t_name,当我执行下面的代码时,却出现异常(db是从该文件打开的SQLiteDatabase对象):

Cursor cursor = db.rawQuery("select * from t_name", null);

部分异常堆栈信息如下:

232411151.png


关键代码

生成服务器文件的代码和Android客户端读取文件的代码都很简单,这里只贴出下载程序的部分代码:

//source是目标文件的URL
URLConnection conn = source.openConnection();
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
//target 是目标文件名
FileOutputStream fos = new FileOutputStream(target);
byte[] buf = new byte[1024];
int read=0;
while ((read=bis.read(buf))>0){
    fos.write(buf);
}
bis.close();
fos.close();

解决过程

1.首先认为只是偶然现象,卸载项目,重新安装,毫无变化。

2.检查数据库的打开和关闭操作,因为之前也遇到过镜像文件破坏问题,就是由于文件打开之后没有关闭。代码检查完毕,没有这种泄漏问题。

3.怀疑是不是生成的数据库文件有问题。在服务器上新建一个Java SE项目,使用标准JDBC对该文件进行读取,一切正常。

4.认为可能在下载数据库文件时发生了传输错误。清除数据,重新下载,错误信息仍然存在。

5.借助可视化管理工具SQLiteStudio打开服务器文件,查看文件内容,完全正常;使用adb将客户端下载好的文件传回电脑并打开,无法正常查询,收到如下警告。

090509630.png

6.查看下载程序,觉得缓冲区这里似乎有些问题,联想到数据库的大批量自动提交可能出错,如果缓冲区变大一些,会不会减少出错概率呢?考虑到实际需要下载的文件大小为300K左右,于是把上述程序的代码做了稍稍改动,将缓冲区大小改为1024000,即1000K,重新下载文件。然后,看着下载的文件,当时就震惊了:

090346890.jpg

7.问题根源找到,既然缓冲区大了会多写空白符,那么很明显在某次(理论上应该总是最后一次)如果没有把缓冲区读满,上面的代码也会写入空白符到db文件中。数据库镜像错误就不难理解了。

8.修改后的代码如下,修改后问题解决。

//source是目标文件的URL
URLConnection conn = source.openConnection();
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
//target 是目标文件名
FileOutputStream fos = new FileOutputStream(target);
byte[] buf = new byte[1024];
int read=0;
while ((read=bis.read(buf))>0){
    //这行代码是关键代码
    fos.write(buf, 0, read);
}
bis.close();
fos.close();


心得总结

这次的Bug困扰了我两天,最后居然是在近乎意外的情况下找到的问题,深受教训,总结起来大约有以下几点:

  1. 对BufferedInput/OutputStream的方法理解不够深刻,以前总是觉得write(byte[] buf)方法和write(byte[] buf, int offset, int length)方法差不多,只是重载的而已,使用前者懒得传递参数,于是毫不思索就加以使用。今后必将重视这个问题,谨慎对待缓冲区。

  2. 经过此次教训,深刻认识到绝大部分标准类库的方法都是有“存在即合理”的。在使用一个看起来十分方便的方法前,一定要想清楚是否有不妥的地方。

  3. 不应该怀着侥幸心理怀疑网络传输问题,应该先从自身入手。实际上,今天的网络传输出问题的概率几乎是零。

你可能感兴趣的:(android,sqlitedatabase,Malformed)