在这一章中咱们将介绍代码解析资源文件来得到显示在进度条上的文字的流程。 node
解析流程中涉及的核心类有ZMLZMLProcessor、ZLXMLParser、ZLXMLReader三个类以及ZLTreeResource类。 数组
ZMLZMLProcessor、ZLXMLParser、ZLXMLReader这三个类是读取xml文件的核心类。 数据结构
关于xml文件的读取流程,咱们在第四章“XML文件处理 -- 读取”中还会详细介绍。不过,第四章中介绍的读取流程比这一章节中介绍的流程要来得复杂。复杂的缘由是,这章中涉及读取的资文件都是正常的xml文件,读取时无需做特殊处理。而第四章中介绍的读取流程是针对epub文件内部的xml文件,epub文件是压缩过的(关于epub文件的内部组成能够参考第四章“epub文件处理 -- epub文件内部组成”),因此必须先把epub文件中的一部分解压成正常的xml文件,而后才能开始正常读取流程。 app
说到这里,咱们有必要先来介绍一下关于FBReader程序本身定义的三种文件格式类以及资源文件、epub文件又都对应着哪类文件格式。 ui
FBReader的自定义文件格式类分别在org.geometerplus.zlibrary.core.filesystem包与org.amse.ys.zip包里面。 spa
org.geometerplus.zlibrary.core.filesystem包里面, 调试
ZLFile类是基类,ZLResourceFile、ZLPhysicalFile、ZLArchiveFile是ZLFile类的子类,ZLZipEntryFile是ZLArchiveFile的子类。 xml
ZLResourceFile类专门用来处理资源文件,这一章中要解析的assets文件夹下的资源文件均可以ZLResourceFile类来处理。 递归
ZLPhysicalFile类专门用来处理普通文件,eoub文件就能够用一个ZLPhysicalFile类来表明。 接口
ZLZipEntryFile类用来处理epub文件内部的xml文件,这个类会在第五章“epub文件处理 -- 解压epub文件”中出现。
这三个文件类都实现了getInputStream抽象方法,不用的文件类会经过这个方法得到针对当前文件类的字节流类。
AndroidAssetsFile类(ZLResourceFile类的子类)的getInputStream方法会返回AssetInputStream类,这个类能够将资源文件转换成byte数组。
ZLPhysicalFile类的getInputStream方法会返回FileInputStream类,这个类能够将普通的文件转换成byte数组。
ZLZipEntryFile类的getInputStream方法会返回FileInputStream类,这个类能够将epub内部压缩过的xml文件转换成能够正常解析的byte数组
转换的流程请会在第五章“epub文件处理 -- 解压epub文件”中详细介绍。
继续回到读取xml文件的核心类ZMLZMLProcessor、ZLXMLParser、ZLXMLReader。
这三个核心类的调用顺序通常是这样的:
1、ZLXMLReaderAdapter抽象类的子类(ResourceTreeReader类)里面的read方法调用ZLXMLProcessor类的read方法
2、ZLXMLProcessor类的read方法经过AndroidAssetsFile类(ZLResourceFile类的子类)的getInputStream方法获取一个针对资源文件的字节流类(AssetInputStream类),并以这个字节流类为参数初始化了一个针对资源文件的字符流类。接着,就调用了ZLXMLParser类的doIt方法。
3、 ZLXMLParser类的doIt方法利用字符流类将文件转换成一个char数组。再利用for循环迭代byte数组的过程当中,doIt方法又反过来调用ZLXMLReaderAdapter抽象类的子类(ResourceTreeReader类)的startElementHandler与endElementHandler方法对byte数组中元素所表明的不一样节点进行操做。
PS:当你们读到第六章“epub文件处理 -- 解析 container文件与.opf文件”的时候,咱们还会再来回顾这三个核心类的调用顺序。
介绍完读取xml文件的三个核心类以后,再来介绍下ZLTreeResource类。ZLTreeResource类是ZLResource类的子类,ZLTreeResource类多了myChildren和myRoot属性。ZLTreeResource类多出的两个属性能够用来表示母节点以及子节点。读取xml文件的三个核心类会配合ZLTreeResource类将xml资源文件转换成一个层级数据结构。
核心类已经介绍完毕了,下面来看下详细的源码。
首先回到UIUtil类中的wait方法,这个方法调用了ZLResource类中的resourse静态方法。
resource方法中又调用了ZLTressResource类中的buildTree方法。
buildTree方法中定义了ourRoot属性,做为母节点。同时,还设置了ourLanguag和our'Country两个属性,还记得我以前说的assets/resourses/application这个资源文件夹中默认文件uk.xml吗?就是在这里设置的。接着buildTree方法调用了ZLTreeResource类中loadData方法(无参数loadData以及两参数loadData)。
无参数的loadData方法首先初始化了ResourceTreeReader类,而后又调用了两参数的loadData方法,这个方法中调用了ResourceTreeReader类中的readDocument方法。
readDocument方法中设置了myStack属性,以后全部的资源文件数据都会加载到myStack属性之中。再接着就是ResourceTreeReader类的read方法了,这个方法负责读取xml格式的资源文件。
看到ResourceTreeReader类(ZLXMLProcessor类的子类)的read的方法,你们应该是很熟悉的吧。由于咱们刚刚介绍过这个方法。三个读取xml文件的调用顺序就是从“ZLXMLReader接口某一个实现类(ResourceTreeReader类)里面的read方法”开始的。在调用顺序的第三步,资源文件会被一个字符流类转换成一个byte数组。而后利用for循环迭代byte数组,经过ResourceTreeReader类的startElementHandler与endElementHandler方法对byte数组中元素所表明的不一样节点进行操做。
在继续介绍解析流程以前,咱们先来介绍下xml格式的资源文件集体的内容是如何的。资源文件的位置在application和zlibrary两个文件夹里面。
咱们来看一下读取的xml格式的资源文件的大体结构:
文件都是有不一样层级的node标签组成的。
如今,咱们继续解析流程。
咱们以前有说过,readDocument方法中设置了myStack变量,以后全部的资源文件数据都会加载到myStack属性之中。注意,myStack变量其实就是一个ArrayList。
如今,咱们不妨来模拟一下单步调试,看看myStack变量的变化。
假设,waiMessage是第一个节点,程序刚刚读到第一个节点。
代码读取到waitMessage节点开始标签右边的“>”时候,会触发ResourceTreeReader类中的startElementHandler方法。
此时,代码就会获得name的值(132行),但value为nul(135行),由于waitMessage的节点没有value的属性嘛。
接下来就是得到peek变量(137行)。请注意,myStack一开始是有加入一个root变量的。因此,myStack变量所指向ArrayList的是有一个元素的。这个元素至关于一个“根节点”,而此时咱们就将得到这个“根节点”。
而后,程序将新建的HashMap赋值给了表明“根节点”的peek变量的myChildren的属性(144行),这就至关于为“根节点”增长一个空的“子节点”。而后代码又初始化了一个ZLTreeResource类(150行),这个类其实就表明了waitMessage的节点。接下来,代码将这个类赋值给了node变量,并将node加入到刚刚建立的HashMap(152行)。这样,就至关于waitMessage节点代替了空的“子节点”,成为了“根节点”的“子节点”了。最后,代码又将表明waitMessage节点的node加入了myStack变量所指向ArrayList里面(159行)。
继续往下,程序开始读取下面的downloadingFile节点开始标签右边的“>”的时候,一样的流程会再走一遍。与前一次的的区别是,程序这一次会直接得到刚刚被加入myStack变量所的指向ArrayList里面的表明waitMessage节点的node变量(137行),但此时表明waiMessage节点的peek变量的myChildren属性是null,这就表明底waitMessage节点下是没有任何“子节点”的。接着,代码初始化了一个children变量,也就是初始化了一个HashMap(142行),而后将表明downloadingFile节点的node加入了children变量所指向的HashMap里面,这就至关于给waiMessage节点加入一个空的“子节点”(144行)。此时,node被设置为null(141行),程序因而初始化一个空的node(150行),也就是初始化一个ZLTreeResource类,而后把downloadingFile节点的name、value信息赋给node变量所指向的ZLTreeResource类。接着,表明downloadingFile节点的node变量加入到了children变量指向的HashMap(152行),这就至关于downloadingFile节点代替了空的“子节点”,成为了waiMessage节点的“子节点”了。最后,与以前同样,代码又将表明downloadingFile节点的node变量加入了myStack变量所指向的ArrayList里面(160行)。
接下来,程序会读到表明downloadingFile节点结束标签里的“/”(表明waiMessage节点结束标签里的“/”一直要到这个节点里面的全部子节点所有被读取完毕后才会被读到),因而ResourceTreeReader类中的endElementHandler方法被调用。这个方法的做用就是将myStack变量里面所指向的ArrayList里的最后一个元素删除了。
这个动做其实就是把刚刚加入myStack变量的表明downloadingFile节点的node变量删除了。删除这个节点的做用是什么呢?它的做用就是,当下一次再进入startElementHandler方法是,代码会从ArrayList中读出表明waitMessage节点的node,而后表明“子节点”的node就会加到这个node的myChildren属性里面。当waitMessage节点全部的子节点都读完的时候,waiMessage节点的结束斜杠就会触发endElementHandler方法,从而将表明waiMessage节点的node从myStack变量所指向的ArrayList里面删除。这样一来,waitMessage节点以后的节点就会被直接加到表明根节点的node的myChildren里面去,至关于与waitMessage节点同样成为根节点的子节点。
总结来讲,startElementHandler方法里面的增长元素与endElementHandler方法中的删除元素配合起来就将xml文件里面的数据以一种层级式的结构读取到了内存里面。
Ok,资源文件已经被读取出来,并以层级结构存储在内存中了。接下来就从这个结构中寻找要在屏幕上要显示的字,其实就是loadingBook这个节点的值。
UIUtil类的wait方法会调用ZLTreeResource类getResource方法来获取这个节点的值
如今回到ZLTreeResource类中的resource方法,如今咱们已经从ZLTreeResource类的buildTree方法中跳出,开始对对根节点调用getResource方法,寻找匹配节点
ZLTreeResource类getResource方法:这个方法其实就是一层一层找每一个节点的子节点有没有想要的节点。其实,若是节点的名字都不重复的话,这里直接使用递归也是能够的。
最后,再补充下,默认的资源文件时uk.xml,程序是怎么知道咱们的手机应该是使用zh.xml的中文资源文件呢?其实就是在ZLTreeResource类的updateLanguage方法。这个方法
好了,到这为止,第一个章节就结束了。在这个章节中,咱们作的事情其实并很少,只是得到了加载书籍时,显示在屏幕上的字;可是在这个过程咱们却已经接触了FBReader中几个很是核心的概念。咱们如今再来回顾下这几个核心的概念。