在這一章中咱們將介紹代碼解析資源文件來得到顯示在進度條上的文字的流程。 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中幾個很是核心的概念。咱們如今再來回顧下這幾個核心的概念。