第三章、获取书籍信息
在第一章与第二章中我们将精力集中在了主线程上,介绍了主线程是如何控制一个进度条的显示和消失,并通过解析资源文件在进度条上显示合适的文字。
从这一章节开始,我们会将精力转到子线程上。本章将介绍后台运行的子线程获取包括文件路径在内等书籍信息的流程。
本章涉及的核心类包括FBReaderApp类、SQLiteBooksDatabase类、Book类、BooksDatabase类、FileInfoSet类
子线程运行的代码其实就是FBReaderApp类initWindow方法中定义的Runnable类中的内容。
正常情况下,因为是第一次进入程序,book变量会是null,代码就会调用getHelpFile方法。然后把帮助文件显示出来。帮助文件是一个格式为fb2的文件,并不是我们常用的epub格式。
为了介绍epub的处理流程,我们就不从默认的处理fb2格式的帮助文件流程入手了,而是假设我们上一次有阅读过一个epub文件,这样Library类的getRecentBook方法就会找到上一次阅读过的epub文件。
我这里选取的用来测试的epub文件是《三体I 地球往事》,权当是向大刘致敬。(这个epub只用作测试,请大家支持正版,多看上有三体合集的购买地址,我自己买的就是这个合集)
回到代码上来,因为参数myArg0都是null,所以book变量也就是null。接下来我们就会进入到Library类的getRecentBook方法。
Library类getRecentBook方法:
这个方法会调用SQLiteBooksDatabase类的loadRecentBookId方法
SQLiteBooksDatabase类loadRecentBookId方法
loadRecentBookId方法其实就是运行了一句sql语句,这句sql语句会从book_index表中取出book_id的值。
看到这句sql语句,我们很自然得就会想去寻找初始化整个Sqlite数据库的代码所在的位置。对sql数据初始化的代码就是SQLiteBooksDatabase类的构造函数
SQLiteBooksDatabase类构造函数
SQLiteBooksDatabase
类的构造函数是在
FBReader
类中的
createApplication
方法中被调用的。
createApplication方法则是被FBReader的父类ZLAndroidActivity中的onCreate方法调用。
还记得我们在第一章一开始就有说过:“进入到FBReader类,此时首触发的是FBReader类的父类AndroidActivity中的onCreate方法。”所以,在程序刚刚开始运行的时候sqlite的数据库就被创建了。
好了,现在我们再来详细介绍下SQLiteBooksDatabase类的构造函数。
openOrCreateDatabase方法就是新建或打开一个books.db的数据库。看到这段代码,我们可以知道每个运行过FBReader程序的手机上都会有一个books.db数据库。那么这个数据库究竟是在哪里呢?数据具体在什么地方其实是否是有openOrCreateDatabase方法的第一个参数决定的。如果这个参数是一个绝对路径,那么数据库就会建立到这个路径里面去;如果这个参数是一个相对路径,那么数据库就会建立到程序默认的文件夹里面去。代码中,openOrCreateDatabase方法第一个参数就是使用了相对路径,所以数据库建立程序默认的文件夹里面的。一般来说,数据库比较小,那么方法在程序的默认的文件夹里面是没有问题的。但如果数据库比较大的话,那么就要考虑是否需要放置到sd卡里面去了。
接下来我们去寻找下FBReader的默认文件夹。
一般来说,安装到手机上的程序都会安装到/data/datad的目录下。要找到这个目录,你的手机必须是已经root过的。现在,我们不妨来找一下这个数据库。
在cmd里面通过adb shell命令(安装完sdk,要记得把platform-tools的路径加到环境变量里面去,否则adb命令就不能运行了)和su命令(没有root的话,就没有su命令),然后通过cd /data/data进入文件夹
所有的安装到手机上的程序一般都会安装到/data/datad的目录下,但是这个目录下的文件夹非常得多,FBReader具体是哪个文件夹呢?
其实FBReader文件夹的名字是由AndroidManifest.xml里面的package属性决定的。这个属性的值是什么,FBReader所在的文件夹名字是什么。
我们现在就可以进入这个文件夹,数据库就是在这个文件夹下的databases文件夹下了
我们可以sqlite3 books.db的命令直接进入数据库查看。
进入数据库之后,使用.tables的命令查看下数据库里面到底有哪些表
但是,使用windows的命令行查看books.db会有字符集的问题。sqlite默认的字符集是UTF-8,我们可以用pragma encoding命令查看默认的字符集。
但是windows的命令行却是GBK的字符集,这样一来数据库里面的中文都会是乱码。
为了避免字符集的问题,建议大家可以使用可视化工具来查看sqlite数据库。我使用的是navicat for sqlite,下载地址是http://download.csdn.net/download/zar19901007/6417605
继续回来说数据库,从我们可以从navicat for sqlite看到表还不少,初始状态下这些表肯定都是没有的,都需要代码创建出来的。而完成创建工作的就是SQLiteBooksDatabase类的migrate方法。
SQLiteBooksDatabase类migrate方法
进入到migrate方法里面,一眼就能看到一个我们第一章中曾经遇到果断的UIUtil类wait方法。这个方法的作用就是在屏幕上显示一个显示等待的进度条,然后从从资源文件中找出特定的字符串,填充到进度条里面去。具体的代码大家可以去查看第一章的内容。
createTables方法负责创建表,创建索引之类的事情则是由一系列的updateTablesX方法来完成了。
回到FBReaderApp类getRecentBook方法:
Book类getById方法
这个方法会从RecentBooks这张表里面返回book_id,然后会调用Book类的getById方法利用这个book_id去获得书籍的详细信息。
Book类的getById方法会调用到SQLiteBooksDatabase的loadBook方法
loadBook方法从Books这张表里面取出书籍的相关信息
接着,loadBook方法调用了BooksDatabase类createBook方法
BooksDatabase类createBook方法
BooksDatabase类createBook方法调用了两个方法:FileInfoSet类的构造方法与BooksDatabase类的createBook方法,我们一个一个来看。
FileInfoSet类构造函数
这个构造函数里面有两个方法,分别是SQLiteBooksDatabase类的loadFileInfos方法和FileInfoSet类load方法。
SQLiteBooksDatabase类的loadFileInfos方法会进入一个while循环,这个循环不断得获得file_id对应的parent_id,一直到parent_id为null。
代码会针对file_id为57和64的项目代码会调用BooksDatabase类的createFileInfo方法,新建一个FileInfo类(此时FileInfo类的构造函数中的parent参数为null)。
创建出来的FileInfo类被加入到了infos变量所指向的ArrayList中
在while循环之后,代码再利用for循环,从infos变量里面依次把元素取出来,重新调用一遍createFileInfo方法,这一次新建FileInfo类是parent参数就不是null了。新建的FileInfo类最后又被放回infos变量所指向的ArrayList中
代码跳出SQLiteBooksDatabase类的loadFileInfos方法后就会进入FileInfoSet类load方法,这个方法更新了myInfosByPair、myInfosById两个属性。
BooksDatabase类createBook方法:
BooksDatabase
类的
createBook
方法首先调用了
FileInfoSet
类的
getFile
方法,这个方法就是从刚刚更新过的
myInfosById
属性里面根据
fileId
,取出对应的
FileInfo
类。
再根据FileInfo类的信息会作为参数被FileInfoSet类的另一个getFile方法。这个方法利用了递归,只要FileInfo的parent属性不是null,递归就会继续。
在这个递归中,会两次调用ZLFile类的createFile方法。
第一次进入createFile方法的时候,第一个参数是null,会建立一个ZLPhysicalFile类,代表/storage/emulated/0/Ebook这个文件夹
第二次进入createFile方法的时候,参数是刚刚建立的ZLPhysicalFile类,此时会建立一个新的ZLPhysicalFile类,这个新的ZLPhysicalFile类将代表epub文件
注意,此时ZLPhysicalFile类构造函数的参数是epub文件的完整路径:“/storage/emulated/0/Ebook/三体1《地球往事》.epub”。构造函数会利用这个完整路径创建一个ZLPhysicalFile类。正因这里设置了File类,所以才能在getInputStream方法中返回FileInputStream类。
另外,ZLPhysicalFile类的构造函数调用了ZLFile类的init方法。这个方法设定了myExtension属性,这个属性在第六章“epub文件处理 -- 解析 container文件与.opf文件”,会被用到。
当FileInfoSet类的getFile方法跳出时,返回的是第二次建立的ZLPhysicalFile类。这个ZLPhysicalFile类将作为参数被BooksDatabase类的createBook方法调用
最终,createBook类返回了一个新创建的Book类。
到这里为止,Library类的getRecentBook方法的流程就走完了,程序获取了book变量指向的Book类。代码以这个类为参数,调用了FBReaderApp类的openBookInternal方法。从这个方法开始,我们就开始对epub文件处理流程了。