天天看點

《深入了解OSGi:Equinox原理、應用與最佳實踐》一3.2 Bundle狀态及轉換

“狀态”是bundle在運作期的一項動态屬性,不同狀态的bundle具有不同的行為。生命周期層規範定義了bundle生命周期過程之中的6種狀态,分别是:uninstalled(未安裝)、installed(已安裝)、resolved(已解析)、starting(啟動中)、stopping(停止中)、active(已激活),它們的含義為:

uninstalled,未安裝狀态。處于未安裝狀态的bundle導出的package和包含的其他資源都是不可使用的。但是osgi容器中代表這個bundle的對象執行個體仍然可以操作,在某些場景,比如自省(introspection)中這個對象還是可用的。uninstalled的狀态值為整型數1。

installed,已安裝狀态。bundle處于已安裝狀态就意味着它已經通過osgi架構的有效性校驗(有效性校驗的内容可參見第2章中子產品層定義的介紹)并産生了bundle id,但這時還未對它定義的依賴關系進行解析處理。installed的狀态值為整型數2。

resolved,已解析狀态。bundle處于已解析狀态說明osgi架構已經根據中繼資料資訊中描述的依賴關系成功地在類名空間中找到它所有的依賴包,這時它導出的package就可以被其他bundle導入使用。resolved的狀态值為整型數4。

starting,啟動中狀态。bundle處于啟動中狀态說明它的bundleactivator的start()方法已經被調用,但是還沒執行結束。如果start()方法正常執行結束,bundle将自動轉換到active狀态; 否則,如果start()方法抛出了異常,bundle将退回到resolved狀态。starting的狀态值為整型數8。

stopping,停止中狀态。bundle處于停止中狀态說明它的bundleactivator的stop()方法已經被調用,但是還沒執行結束。無論stop()是正常結束還是抛出了異常,在這個方法退出之後,bundle的狀态都将轉為resolved。stopping的狀态值為整型數16。

active,bundle處于激活狀态,說明bundleactivator的start()方法已經執行完畢,如果沒有其他動作,bundle将繼續維持active狀态。active的狀态值為整型數32。

osgi規範定義的部分api接口對bundle的狀态有要求,隻有處于特定狀态的bundle才能調用這些api。在代碼中可以使用bundle接口中的getstate()方法來檢測bundle目前的狀态,示例如下:

目前bundle的狀态值采用由整型數存儲的位掩碼(bit-mask)來表示,而沒有直接采用jdk 1.5後提供的枚舉類型。從osgi r4.3規範開始,規範中定義的标準接口中已經開始使用了部分jdk1.5的語言特性,主要是泛型支援,在對應的實作r4.3規範的equinox 3.7架構的中繼資料資訊中,運作環境項也更新到了“j2se-1.5”。不過,其他jdk 1.5中的新語言特性,如注解和枚舉,仍然沒有被采用。這主要是考慮到已有代碼的相容性,java基于擦除法實作的範型可以在編譯時直接使用javac的“-target jsr14”轉換出能部署在jdk 1.4環境下的代碼,而注解、枚舉等還需要其他backport運作庫支援才可以。

bundle狀态是動态可變的,上述6種狀态可以在特定條件下互相轉換,圖3-1描述了狀态轉換的規則和所需的條件,随後筆者将詳細解釋這些狀态的轉換過程。

《深入了解OSGi:Equinox原理、應用與最佳實踐》一3.2 Bundle狀态及轉換

osgi規範定義了bundlecontext接口的installbundle()方法來安裝新的bundle,方法參數為要安裝的bundle的bundle location。但是osgi規範沒有詳細規定bundle的安裝過程應當如何進行,隻是很籠統地要求osgi架構在實作這個方法時,至少要完成生成新的bundle id、對中繼資料資訊進行有效性校驗、生成bundle對象執行個體這些工作。我們不妨從equinox的代碼中看看bundle安裝過程是如何進行的,具體分析如下。

1)根據bundle location判定bundle是否已經安裝過,如果已安裝,那麼直接傳回之前安裝的bundle執行個體。

2)根據bundle location生成bundledata、bundleinstall等通路對象,這時需要完成下面幾個關鍵的動作:

産生新的bundle id;

讀取manifest.mf檔案中的内容,并且對其進行有效性校驗(見2.4.3節),但不對内容進行具體的解析。

更新bundle的lastmodified等資訊(在bundle更新檢測中會使用到)。

3)檢查将要安裝的bundle是否與系統中已有的bundle重名,bundle的符号名稱和版本确定其唯一辨別,osgi不允許将兩個符号名稱和版本完全一樣的bundle安裝到系統中。

4)根據bundle類型(fragment、bundle host等)确定bundle對象的實作類,初始化bundle對象執行個體。

5)将bundle執行個體加入到osgi架構的bundle repository之中,在這個過程結束之後,新安裝的bundle在equinox console和其他bundle中就已經可見了。

6)釋出bundle的installed狀态轉換事件。

雖然bundle安裝需要經過以上6個步驟,但是從equinox console或其他bundle的角度觀察,一個bundle的安裝過程是個原子過程,即要麼bundle已經安裝了,要麼bundle還沒有安裝,不會觀察到bundle“正在安裝之中”的狀态,也不會出現bundle安裝了一半的情況。并且,bundle的installed狀态是一個持久狀态,如果沒有外部作用(改變啟動級别、調用start()方法或解除安裝bundle),bundle将一直維持這個狀态。

解析過程是osgi架構根據bundle的manifest.mf檔案中描述的中繼資料資訊分析處理bundle依賴關系的過程。在實際開發中,經常會遇到“某個bundle安裝不上”這類問題,這種“安裝不上”在大多數情況下不是指“安裝過程”失敗,而更多是指解析過程中的處理失敗,抛出了異常。接下來我們了解一下osgi架構是如何實作解析過程的。

1)先把osgi架構中所有已安裝的bundle(包括未解析的)按照以下規則進行排序,以便在某個package有多個導出源可供選擇時确定依賴包的優先級關系(下面的package優先級依次遞減)。

已經解析的bundle導出的package優先于未解析的bundle導出的package。

具有更高版本的package優先于低版本的package。

具有較低bundle id的bundle導出的package優先于較高bundle id的bundle導出的package。

2)對将要解析的bundle執行一些基本的檢查校驗,确定它們是否符合被解析的基本條件,包括以下檢查項:

檢查osgi容器提供的執行環境是否能滿足bundle的需要;

檢查目前系統是否能滿足在bundle中定義的本地代碼(native code)的需求(例如bundle中的本地代碼是以*.so形式釋出的就隻能用于linux系統,是基于x86指令集的就無法用于arm架構的機器等)。

3)将fragment bundle附加到宿主bundle之中,在對任何一個bundle解析之前,要保證架構中所有的fragment bundle都已經正确附加到宿主上。

4)確定osgi容器能提供所有bundle中繼資料資訊中聲明的依賴項,如require-capability、import-package和require-bundle中聲明的依賴内容,并且這些依賴項都要符合要求,具體要求如下:

bundle聲明導入的package在容器中能夠比對到符合版本範圍要求的提供者;

bundle聲明導入的package包含了提供者在導出時所有聲明要求包含的強制屬性;

如果bundle聲明導入的package定義了屬性,那麼這些屬性的值必須與導出時聲明的屬性值相比對。

如果某個package的導入者與提供者都依賴于同一個package,那麼導入者必須滿足導出package時使用uses參數聲明的限制。

equinox對上述檢查采用“fast-fail”方式實作,即檢查到異常,校驗程式立即中斷并抛出異常,這些代碼實作在resolverimpl.resolvebundle()中。由于代碼量較大,考慮版面關系,不再貼出具體代碼,讀者可自行閱讀源碼。

5)将bundle的狀态調整為已解析的狀态。這裡不僅要修改bundle對象的getstate()傳回值,更重要的是要将前面計算出來的bundle各個依賴項的提供者記錄在bundle對象執行個體上,并且把bundle所能提供的導出包和capabilities記錄到osgi容器之中。

這部分的實作主要在resolverimpl.stateresolvebundle()之中,代碼較多,考慮版面關系,讀者可自行閱讀源碼。

啟動過程即執行bundle的activator.start()方法的過程,在此方法執行期間,bundle的狀态為starting。如果成功執行完這個方法,那麼bundle的狀态會轉變為active,而且将一直保持這個狀态直到bundle被停止。

在bundle啟動時,osgi架構需要通過調用class.newinstance()方法來建立activator類的執行個體,是以bundle的activator類必須保證有一個預設的(即不帶參數的)構造函數。

在啟動bundle之前,osgi架構首先對它進行解析。如果在osgi架構試圖啟動一個bundle時這個bundle還沒有被解析,架構會自動嘗試對bundle進行解析。如果解析失敗,架構會在start()方法中抛出一個bundleexception異常(即使還沒有真正運作start()方法)。在這種情況下,osgi架構會記住這個bundle“已啟動過”,但它的狀态仍然保持為installed,一旦條件滿足,bundle變為可以解析之後,它就應該自動啟動(當然,這個bundle還要滿足後面将介紹的啟動級别的要求)。如果解析本身是成功的,但是start()方法本身的代碼抛出了異常導緻啟動失敗,那麼bundle應當退回到resolved狀态。

前面曾經拿pc類比過osgi子產品化系統:如果把pc機看成一個osgi系統,把組成pc的cpu、記憶體、鍵盤、滑鼠等部件看做子產品,這些子產品中有一些(如cpu等)是必須停機更換的,另外一些(如鍵盤、滑鼠等)可以支援熱插拔。與熱插拔類似,osgi架構中同樣可以支援子產品的動态更新。

bundle的更新過程其實就是重新從bundle檔案加載類、生成新的bundle對象執行個體的過程。osgi規範在bundle接口中定義了以下兩種方法來更新bundle。

bundle.update():從bundle原來的位置進行更新。

bundle.update(inputstream):通過一個指定的輸入流擷取bundle新的内容進行更新。

更新bundle意味着在代碼中使用bundle id、bundle位置和名稱等方式來擷取bundle對象執行個體時,能夠擷取到包含新資訊的bundle對象。但是如果已經有代碼在bundle更新前讀取了舊的資訊并保持持有狀态,那麼持有的狀态資訊自然是不可能自動重新整理的。是以某個bundle是否支援熱插拔,osgi隻提供了技術上的支援,如果開發人員在編寫bundle代碼時就沒有考慮過對狀态的管理更新,那麼所做出來的bundle仍然是無法動态更新的。

另外,如果在新的bundle中修改了原bundle所導出package或改變了導出package的版本号,那麼對于已經存在的、甚至是更新之後才安裝的bundle來說,原來導出的舊版本package都依然是可用狀态,直到調用了packageadmin服務的refreshpackages()方法或osgi架構重新啟動之後,這些舊版本的package才會停止導出。

由于更新bundle不意味着舊bundle立即消失廢棄,換句話說,與舊bundle有關的對象執行個體、舊bundle在java虛拟機中形成的類型和類加載器都不會立刻消失,是以,如果有大量更新bundle的操作,開發人員還應注意java虛拟機方法區的記憶體占用壓力,避免造成記憶體洩露問題。

下面對equinox架構在更新bundle時的代碼進行分析。

1)檢查bundle目前是否處于可更新的狀态,還沒有安裝、狀态為uninstalled的bundle是不能被更新的。

2)确定bundle更新的資料來源。這個來源可以是使用者指定的一個輸入流,如果使用者沒有指定,預設通過bundle location建立一個輸入流。

3)根據輸入流建立bundle對象執行個體,執行個體建立的過程與安裝bundle時是一樣的。

4)将新bundle對象執行個體的資訊重新整理到調用update()方法的bundle對象中,并更新osgi架構的bundle repository。

5)将新的bundle對象的狀态轉換回更新之前的狀态。因為剛剛更新完的bundle是處于installed狀态的,如果更新前是active、starting、stoping等狀态的,需要自動切換回原來的狀态。這時執行的邏輯跟對應的狀态變換過程是一緻的。

6)釋出bundle的updated狀态轉換事件。

bundle的停止過程是啟動過程的逆向轉換,此時osgi架構會自動調用bundle的activator類的stop()方法。在stop()方法執行期間,bundle的狀态為stopping。當stop()方法成功執行完畢後bundle的狀态轉變為resolved。

我們一般用stop()方法來釋放bundle中申請的資源、終止bundle啟動的線程等。在執行完bundle的stop()方法後,其他bundle就不能再使用該bundle的上下文狀态(bundlecontext對象)。如果在bundle生命周期内在osgi架構中注冊了任何服務,那麼在停止該bundle之後,架構必須自動登出它注冊的所有服務。但是如果bundle在stop()方法中還注冊了新的服務,這些服務就不會自動登出。

需要注意的是,即使bundle已經停止,它導出的package仍然是可以使用的,無論對停止前還是停止後安裝的bundle都是如此。這意味着其他bundle可以執行停止狀态的bundle中的代碼,bundle的設計者需要保證這樣做是符合預期、沒有危害的。一般來說,停止狀态的bundle隻導出接口是比較合理的做法。為了盡可能保證不執行代碼,導出接口的類構造器(方法)中也不應該包含可執行的代碼。

調用bundle.uninstall()方法可以實作bundle的解除安裝,此時該bundle的狀态會轉變為uninstalled。osgi架構應盡可能釋放被解除安裝的bundle所占用的資源,盡可能把架構還原成該bundle安裝前的樣子。如果該bundle導出了package,并且這個package被其他bundle導入過,那麼對于這些bundle來說,原來導入的package都是可用的,直到調用了packageadmin的refreshpackages()方法或架構重新啟動之後才會被解除安裝掉。這與停用和更新bundle時遺留的舊package不一樣,這些舊package在解除安裝後會變為不可見,解除安裝之後才安裝到osgi架構的bundle是不能導入它們的。