【原創(chuàng)】Eclipse插件開發(fā):Eclipse中的圖片資源管理
Eclipse中的圖片資源管理
朱興(zhu_xing@live.cn)
【概述】
在本文中,將討論如下內(nèi)容:
1、 系統(tǒng)資源,為后面討論圖片資源做一鋪墊
2、 SWT中的圖片資源管理
3、 Display hook銷毀機(jī)制,JFace中圖片資源管理的重要基礎(chǔ)
4、 JFace中的ImageDescriptor
5、 JFace中的圖片資源管理(ImageRegistry)
6、 JFace中圖片資源管理ImageRegistry所適用的場(chǎng)景和使用規(guī)則
7、 Eclipse中插件share images機(jī)制
8、 在Eclipse插件開發(fā)或者開發(fā)RCP程序時(shí),使用圖片資源需要的注意事項(xiàng)
【系統(tǒng)資源】
眾所周知,Java開發(fā)人員在使用SWT/JFACE的時(shí)候,并不能借助于Java內(nèi)置的垃圾回收機(jī)制來徹底完成系統(tǒng)資源的清理(Java虛擬機(jī)只能幫助我們釋放虛擬機(jī)內(nèi)存中的系統(tǒng)資源句柄引用對(duì)象)。在SWT中系統(tǒng)資源對(duì)象的定級(jí)類型是org.eclipse.swt.graphics.Resource,在類型明確說明了“Resources created by the application must be disposed”,這也讓我們想起了關(guān)于Image使用的一句名言“誰創(chuàng)建,誰負(fù)責(zé)”,當(dāng)然,這個(gè)原則也同樣適用于其他類型的系統(tǒng)資源。
我們之所以如此關(guān)注系統(tǒng)資源的使用,尤其是臭名昭著的圖片資源,主要是因?yàn)槲覀兣铝讼到y(tǒng)資源泄漏引起的系統(tǒng)crash的問題。例如org.eclipse.swt.SWTError: No more handles異常有可能在我們?cè)噲D創(chuàng)建圖片資源的時(shí)候發(fā)生,這說明當(dāng)前系統(tǒng)句柄已經(jīng)不足,造成這個(gè)問題的罪魁禍?zhǔn)桩?dāng)然是我們寫代碼的人。
【SWT中的圖片資源管理】
我們直接看一下SWT中圖片資源類型的定義(org.eclipse.swt.graphics.Image),在類型說明中明確指出了:“Application code must explicitly invoke the Image.dispose() method to release the operating system resources managed by each instance when those instances are no longer required”。我們?cè)倏匆幌铝硗庖粋€(gè)我們熟悉的類型org.eclipse.swt.graphics.ImageData,我們可以將其看作是Image對(duì)應(yīng)的元數(shù)據(jù)模型對(duì)象,描述了具體創(chuàng)建Image需要的信息。
通過上面的說明,我們發(fā)現(xiàn)SWT唯一告訴我們的是:自己創(chuàng)建的圖片資源,自己負(fù)責(zé)去銷毀,通過調(diào)用Image.dispose()。那我們?cè)谑褂?/span>SWT的時(shí)候,應(yīng)該如何釋放圖片資源呢?
我們知道SWT的widget在銷毀的時(shí)候,也會(huì)銷毀子widget,所以,覆寫你自己的Component對(duì)應(yīng)的dispose方法,將你使用的系統(tǒng)資源銷毀。目前,也只能這樣了~_~。如果覺得不滿意,接著看下面的Display hook銷毀機(jī)制。
【Display hook銷毀機(jī)制】
在Display device中,我們看了如下一個(gè)hook接口:
/**
*Causesthe<code>run()</code>methodoftherunnableto
*beinvokedbytheuser-interfacethreadjustbeforethe
*receiverisdisposed.
*/
public void disposeExec (Runnable runnable) {
//注冊(cè)用戶自定義runnable,在display release的時(shí)候回調(diào)此runnable
將runnable注冊(cè)到disposeList
}
disposeList中的線程會(huì)在display release的時(shí)候被調(diào)用,如下:
/**
*Releasesanyinternalresourcesbacktotheoperating
*systemandclearsallfieldsexceptthedevicehandle.
*/
protected void release () {
……
//會(huì)執(zhí)行用戶注冊(cè)的銷毀線程
if (disposeList != null) {
for (int i=0; i<disposeList.length; i++) {
if (disposeList [i] != null) disposeList [i].run ();
}
}
……
}
看來,SWT并沒有把事情做絕了,還是給開發(fā)者留下一條后路的。Display允許開發(fā)者注冊(cè)一個(gè)自定義線程hook到Display的release過程,開發(fā)者可以用如下方式來確保開發(fā)者使用的系統(tǒng)資源在Display release的時(shí)候被銷毀:
display.disposeExec(new Runnable() {
public void run() {
//銷毀系統(tǒng)資源的邏輯代碼
image.dispose();
…….
}
});
以上方式其實(shí)也是JFace中圖片資源管理(ImageRegistry、ResourceManager)能夠確保Display release的時(shí)候能夠徹底釋放被ImageRegistry托管的圖片資源。
到這里回顧一下,SWT中資源釋放的途徑吧:
1、 覆寫相應(yīng)Component對(duì)應(yīng)的dispose方法。這有別于Display的hook機(jī)制,因?yàn)槠淠軌蛟?/span>Display運(yùn)行期間(未被release之前)就釋放掉系統(tǒng)資源,最好的方式。
2、 利用Display的hook機(jī)制,確保在Display被release的時(shí)候能夠銷毀資源。注意,請(qǐng)不要過多依賴此方式,因?yàn)楹苋菀自斐稍?/span>Display被release之前,已經(jīng)發(fā)生了系統(tǒng)crash的問題。
【JFace中圖片資源管理--ImageDescriptor】
前面我們已經(jīng)見過SWT中的Image和ImageData類型了,在繼續(xù)下面的內(nèi)容之前,我們先看一下在JFace中我們最常用來創(chuàng)建圖片資源的一個(gè)工廠類:ImageDescriptor。在ImageDescriptor的類型說明中告訴我們,有兩種使用ImageDescriptor創(chuàng)建圖片的方式,分別通過createImage和createResource接口,“There are two ways to get an Image from an ImageDescriptor. The method createImage will always return a new Image which must be disposed by the caller. Alternatively, createResource() returns a shared Image. When the caller is done with an image obtained from createResource, they must call destroyResource() rather than disposing the Image directly.”。分析如下:
首先看一下createResource方式,ImageDescriptor是一種DeviceResourceDescriptor,后者的對(duì)外操作如下:
/**
*Createstheresourcedescribedbythisdescriptor
*/
public abstract Object createResource(Device device) throws DeviceResourceException;
/**
*Undoeseverythingthatwasdonebyapreviouscalltocreate(...)
*/
public abstract void destroyResource(Object previouslyCreatedObject);
這也就是說,ImageDescriptor提供了createResource / destroyResource接口來負(fù)責(zé)創(chuàng)建和銷毀Image資源。請(qǐng)注意這邊的一點(diǎn),在孤立使用ImageDescriptor(沒有配合ResourceRegistry使用,例如ImageRegistry)的時(shí)候,用戶還是要負(fù)責(zé)通過調(diào)用destroyResource來釋放創(chuàng)建的資源。
其次來看一下createImage的方式:
/**
*The returnedimagemustbeexplicitlydisposedusingtheimage'sdispose call.Theimagewillnotbeautomaticallygarbagecollected. */
public Image createImage(boolean returnMissingImageOnError, Device device) {}
這也就是說,ImageDescriptor提供的createImage的方式,也需要用戶來顯示的銷毀資源。那createImage和createResource兩種方式之間的差別是什么呢?稍微分析一下ImageDescriptor的這兩種創(chuàng)建方式的實(shí)現(xiàn),我們就可以看出來差別:
1、 createImage每次都創(chuàng)建一個(gè)全新的圖片資源(圖片資源的創(chuàng)建是很耗時(shí)的~_~)
2、 createResource的方式采用了緩存的方式復(fù)用已經(jīng)創(chuàng)建過的資源,并不是每次都創(chuàng)建一個(gè)全新的資源。這一點(diǎn)雖然帶來了性能的提高,但是并沒有解決圖片資源釋放的問題,倒是給開發(fā)者留下了一種假象,造成了隨便使用ImageDescriptor的問題,反而造成了大量的圖片資源(當(dāng)然,更多的是由于調(diào)用createImage的方式造成的,因?yàn)槊看味紕?chuàng)建一個(gè)全新的圖片資源)沒有釋放。
到現(xiàn)在為止,我們看到JFace已經(jīng)對(duì)SWT中的圖片資源的管理做了一個(gè)小的補(bǔ)充:提供了ImageDescriptor.createResource的方式,可以利用緩存效果,能夠減少不必要的圖片系統(tǒng)資源創(chuàng)建,而且效率有所提高。關(guān)于如何釋放,可以參考SWT中覆寫Component.dispose的方式,例如在label provider使用的圖片資源,可以覆寫對(duì)于provider的dispose方法,JFace框架會(huì)自動(dòng)調(diào)用。
【JFace中圖片資源管理--ImageRegistry & ResourceManager】
下面,我們接著看一下JFace中的ImageRegistry的實(shí)現(xiàn)原理。
首先我們看一下JFace中的資源管理門面類(façade class)JFaceResources,我們由它來獲取我們的JFace ImageRegistry:
public static ImageRegistry getImageRegistry() {
if (imageRegistry == null) {
imageRegistry = new ImageRegistry(getResources(Display.getCurrent()));
}
return imageRegistry;
}
public static ResourceManager getResources(final Display toQuery) {
ResourceManager reg = (ResourceManager)registries.get(toQuery);
if (reg == null) {
final DeviceResourceManager mgr = new DeviceResourceManager(toQuery);
//向Display hook了銷毀線程
toQuery.disposeExec(new Runnable() {
public void run() {
mgr.dispose();
registries.remove(toQuery);
}
});
}
return reg;
}
分析了一下ResourceManager(DeviceResourceManager)的實(shí)現(xiàn),我們發(fā)現(xiàn):DeviceResourceManager就是對(duì)DeviceResourceDescriptor(ImageDescriptor)進(jìn)行了引用計(jì)數(shù)管理。通過JFaceResources.getResources利用了前面說的Display的hook銷毀機(jī)制(注意,如果不通過JFaceResources.getResources來獲取ResourceManager,則不會(huì)默認(rèn)享受Display的hook銷毀機(jī)制,需要自己向Display注冊(cè)),確保由被托管ImageDescriptor創(chuàng)建的殘留在系統(tǒng)中的圖片資源在Display release的時(shí)候會(huì)被徹底銷毀。核心方法如下:
create(DeviceResourceDescriptor descriptor)
//如果是首次注冊(cè),創(chuàng)建引用技數(shù),allocate資源并對(duì)資源進(jìn)行緩存
//如果是已經(jīng)注冊(cè),增加引用技數(shù),直接返回緩存的系統(tǒng)資源
destroy(DeviceResourceDescriptor descriptor) //將
//如果引用技術(shù)==1,通過調(diào)用deallocate徹底銷毀資源
//如果引用技術(shù)>1,削減引用計(jì)數(shù)(系統(tǒng)資源不會(huì)被銷毀)
那就是說,如果一個(gè)ImageDescriptor被ResourceManager托管了,那由它創(chuàng)建的資源(注意:通過ImageDescriptor.createResource的方式)由兩種銷毀的途徑:
1、 如果不通過JFaceResources.getResources的方式,單獨(dú)使用ResourceManager,則只能利用ResourceManager的引用計(jì)數(shù)管理來銷毀資源(引用計(jì)數(shù)為0時(shí)),通過顯示調(diào)用ResourceManager.destroy來削減引用計(jì)數(shù)。
2、 如果通過JFaceResources.getResources來使用ResourceManager,則除了能夠使用到引用計(jì)數(shù)管理資源,同時(shí)也默認(rèn)使用了Display的hook銷毀機(jī)制,JFace的ImageRegistry也很好的利用了這一點(diǎn)。
現(xiàn)在回頭看一下ImageRegistry提供的核心操作,著重分析一下ImageRegistry在利用了ResourceManager對(duì)ImageDescriptor進(jìn)行管理的基礎(chǔ)上,做了那些補(bǔ)充:
put(String key, Image image) //注冊(cè)image
put(String key, ImageDescriptor descriptor) //注冊(cè)descriptor
Image get(String key) //獲取imge
ImageDescriptor getDescriptor(String key) //獲取descriptor
remove(String key) //取消注冊(cè)
dipose() //銷毀資源
通過對(duì)ImageRegistry簡(jiǎn)要的分析之后,我們的結(jié)論如下:
1、 如果以put(String key, ImageDescriptor descriptor)的方式注冊(cè),ImageRegistry直接講descriptor委托給ResourceManager委托管理,自己并不承擔(dān)管理任務(wù)。而且,ImageRegistry對(duì)這種方式注冊(cè)的ImageDescriptor所創(chuàng)建的系統(tǒng)圖片資源的銷毀也委托給ResourceManager進(jìn)行,并不是在以上自己的dispose方法中進(jìn)行,而是在ResourceManager.dispose方法中進(jìn)行。
2、 如果以put(String key, Image image)的方式注冊(cè),ImageRegistry做了部分的補(bǔ)充管理,其將image包裝進(jìn)自己的OriginalImageDescriptor(ImageRegistry的一個(gè)內(nèi)部類,繼承自ImageDescriptor,對(duì)圖片資源本身增加引用計(jì)數(shù))實(shí)現(xiàn)中,并對(duì)image本身進(jìn)行了引用計(jì)數(shù)管理。同時(shí),對(duì)這種方式注冊(cè)的圖片資源的銷毀是ImageRegistry自己承擔(dān)的,在自身的dispose方法中完成。(注意,在ImageRegistry的構(gòu)造方法中,將ImageRegistry.dispose封裝為一個(gè)runnable注冊(cè)到了ResourceManage的dispose過程中,而ResourceManage.dispose已經(jīng)在JFaceResources.getResources方法中被hook到了Display的資源銷毀過程中)。
3、 通過1和2的結(jié)論,JFace ImageRegistry對(duì)系統(tǒng)資源的銷毀已經(jīng)做了兩手準(zhǔn)備,
其并不希望用戶自己來銷毀資源(無論是通過Image.dispose還是ImageDescriptor.destoryResource,或者ImageRegistry.dispose),當(dāng)然,ImageRegistry允許通過remove接口來取消注冊(cè)。
JFaceResources +提供hook機(jī)制 ImageRegistry +自己管理部分資源 ResourceManager +管理ImageDescriptor及其創(chuàng)建的資源
【ImageRegistry的適用場(chǎng)景和使用規(guī)則】
通過上面的實(shí)現(xiàn)原理分析,我們知道ImageRegistry并不歡迎用戶來過多地參與圖片資源的釋放過程,所以ImageRegistry適用于如下場(chǎng)景:
1、 決定共享和高度復(fù)用的圖片資源。這種資源一般是被使用的特別頻繁,同時(shí),不急于銷毀,只要在Display release的時(shí)候銷毀掉就可以了,所以既可以利用到圖片資源本身緩存的優(yōu)勢(shì)(減少物理創(chuàng)建的次數(shù)),又可以利用其Display的hook銷毀機(jī)制,確保會(huì)被銷毀。
2、 用戶可以直接使用ImageRegistry(不通過JFaceResources.getImageRegistry的方式使用),復(fù)用部分ImageRegistry的管理功能,開發(fā)自己的緩存策略,但是,要確保自己會(huì)在合適的地方調(diào)用ImageRegistry.dispose方法來銷毀registry。Eclipse Workbench中的shared images機(jī)制就用了這一點(diǎn)。
ImageRegistry的使用規(guī)則如下:
1、 誰創(chuàng)建,誰負(fù)責(zé)。具體圖片資源的創(chuàng)建是由ImageRegistry負(fù)責(zé)的,用戶既然托管了,就不應(yīng)該再干預(yù)資源的釋放。而且,注冊(cè)進(jìn)ImageRegistry的資源是共享的,一個(gè)用戶釋放了,會(huì)影響到其他用戶的使用。當(dāng)然,對(duì)于比較熟悉JFace ImageRegistry原理的開發(fā)者,可以參與到引用計(jì)數(shù)的管理,通過這種方式,以安全的、不影響其他用戶使用的方式來間接參與釋放的過程。
2、 非共享圖片資源請(qǐng)不要交由ImageRegistry托管。對(duì)于一個(gè)僅限于局部使用而且使用并不是十分頻繁的圖片資源,這樣做不會(huì)帶來什么好處,而且,尤其是對(duì)于不能參與到引用計(jì)數(shù)管理的初級(jí)用戶,這樣做反而會(huì)使得一個(gè)本可以馬上釋放的圖片資源反而會(huì)一直占用,直到Display release的時(shí)候才銷毀。
3、 要投入精力對(duì)ImageRegistry的key值進(jìn)行管理,否則,會(huì)引起混亂。因?yàn)?/span>ImageRegistry本質(zhì)上可以看作Eclipse平臺(tái)中的一個(gè)全局對(duì)象,對(duì)其含有的key列表的管理是再所難免。
【Eclipse中插件share images機(jī)制】
在Eclipse,一個(gè)插件可以暴露(expose)自己的圖片資源,以便提供給需要的插件使用,我們就稱它為插件之間的share images機(jī)制吧。上面提到過了,這其實(shí)是部分復(fù)用了JFace ImageRegistry的管理機(jī)制。
如何共享(可以參照Workbench插件的share images實(shí)現(xiàn)):
1、 按照默認(rèn)約定,創(chuàng)建一個(gè)ISharedImages接口,提供有意義key值
2、 實(shí)現(xiàn)自己創(chuàng)建的ISharedImages接口,并結(jié)合ImageRegistry來管理圖片資源;并提供顯示的dipose公共接口,負(fù)責(zé)釋放自己管理的圖片資源
3、 在自己的插件中暴露ISharedImages
4、 在合適時(shí)機(jī),調(diào)用ISharedImages.dispose來釋放資源。這個(gè)時(shí)機(jī)一般選擇在Plugin stop的時(shí)候比較合適,當(dāng)然,也可以選擇在其他時(shí)機(jī)。
如何使用:
1、 獲取目標(biāo)插件的ISharedImages實(shí)現(xiàn),并通過ISharedImages提供的key值來獲取特定的圖片資源。以workbench插件share images為例:
PlatformUI.getWorkbench().getSharedImages().getImage(key)
2、暴露圖片資源的插件負(fù)責(zé)圖片資源的創(chuàng)建和銷毀,其他插件不要參與銷毀過程。換句話說,還是要遵守誰創(chuàng)建、誰負(fù)責(zé)的原則。以workbench插件share images為例:
在workbench close的時(shí)候,會(huì)間接調(diào)用ISharedImages.dispose()。
【Eclipse中使用圖片資源的經(jīng)驗(yàn)總結(jié)】
1、 堅(jiān)持“誰創(chuàng)建,誰負(fù)責(zé)”的原則。分為如下:
a) 如果是用戶自己創(chuàng)建的,請(qǐng)自己釋放。例如通過覆寫Component對(duì)于的dispose方法、通過覆寫label provider對(duì)應(yīng)的dispose方法等等,這對(duì)于一些適用于局部的圖片資源較為適合;當(dāng)然,也可以變態(tài)利用Display的hook釋放機(jī)制(但是,一般對(duì)于長(zhǎng)期使用的資源才會(huì)這樣做?。。。?。
b) 如果是通過JFaceResources.getImageRegistry的方式使用ImageRegistry時(shí),請(qǐng)不要釋放資源,讓ImageRegistry自己解決。一般使用于比較頻繁使用的全局共享圖片資源,例如想保持風(fēng)格統(tǒng)一的圖片資源等。
c) 如果是使用了IShareImages的機(jī)制,請(qǐng)?zhí)峁﹫D片資源的插件自己負(fù)責(zé)釋放。如何使用這種機(jī)制,最好參照eclipse中已有的實(shí)現(xiàn),保持風(fēng)格統(tǒng)一,“有樣學(xué)樣”吧。
2、 正確認(rèn)識(shí)系統(tǒng)資源泄漏引起的crash問題的原因。導(dǎo)致原因有兩種:
a) 首先,是沒有釋放,導(dǎo)致泄漏。請(qǐng)參照上面的“誰創(chuàng)建,誰負(fù)責(zé)”的原則。
b) 其次,是釋放的過晚,導(dǎo)致積累過多。例如本來應(yīng)該立即釋放的資源,反而通過ImageRegistry進(jìn)行了托管,同時(shí)有沒有控制引用計(jì)數(shù)的管理,導(dǎo)致到了Display release的時(shí)候才釋放資源。同樣道理,本來不需要暴露給其他插件貢獻(xiàn)的圖片資源,反而暴露了,導(dǎo)致釋放過完等。
3、 正確認(rèn)識(shí)系統(tǒng)資源的創(chuàng)建和銷毀所帶來的時(shí)間消耗,這是從系統(tǒng)性能的角度考慮。例如,可以用ImageDescriptor.createResource的方式替換原始的new Image的方式,減少創(chuàng)建資源過于頻繁和銷毀資源過于頻繁所帶來的時(shí)間占用。對(duì)于需要長(zhǎng)期使用的貢獻(xiàn)資源,可以使用ImageRegistry的方式等等。
4、 對(duì)于特殊的場(chǎng)景,可以在參考以上原理(例如JFace中的圖片管理的實(shí)現(xiàn)原理分析)的基礎(chǔ)上自己實(shí)現(xiàn)圖片資源的管理策略。這對(duì)團(tuán)隊(duì)開發(fā)產(chǎn)品的情況下尤其適用,一方面可以優(yōu)化管理策略,使之更切近團(tuán)隊(duì)?wèi)?yīng)用;再者,可以減少JFace ImageRegsitry使用的復(fù)雜度,并減少誤用。例如,我們可以把插件間share images的機(jī)制看成是對(duì)JFace ImageRegsitry的靈活使用。
5、 無論使用那種管理策略(無論是來自eclipse還是其他),使用這前一定要仔細(xì)看API說明,并簡(jiǎn)要分析一下實(shí)現(xiàn)原理。對(duì)于做上規(guī)模的插件產(chǎn)品/應(yīng)用來講,畢竟對(duì)圖片這種系統(tǒng)資源的管理太重要了?。?!對(duì)于做較為簡(jiǎn)單的開發(fā),基本上本著“誰創(chuàng)建、誰負(fù)責(zé)”的原則,用完之后在自己感覺合適的地方銷毀掉就可以了,完全可以不去碰JFace中的ImageRegistry那套東東,引來不必要的負(fù)責(zé)度和復(fù)用,尤其是對(duì)于新手來說。
PS:文章是昨天趕出來的,沒有細(xì)看。有什么錯(cuò)誤之處,歡迎大家指出~_~
附件是本文的word文檔 word格式文檔
本博客中的所有文章、隨筆除了標(biāo)題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請(qǐng)注明出處,謝謝!
posted on 2008-08-06 10:30 zhuxing 閱讀(3187) 評(píng)論(3) 編輯 收藏 所屬分類: Eclipse Plug-in & OSGI