Eclipse插件开发:Eclipse中的图片资源管理(引用)

参考网址:http://www.blogjava.net/zhuxing/archive/2008/08/06/220360.html

【概述】

在本文中,将讨论如下内容:

1、  系统资源,为后面讨论图片资源做一铺垫

2、  SWT 中的图片资源管理

3、  Display hook 销毁机制, JFace 中图片资源管理的重要基础

4、  JFace 中的 ImageDescriptor

5、  JFace 中的图片资源管理( ImageRegistry

6、  JFace 中图片资源管理 ImageRegistry 所适用的场景和使用规则

7、  Eclipse 中插件 share images 机制

8、  Eclipse 插件开发或者开发 RCP 程序时,使用图片资源需要的注意事项

【系统资源】

众所周知, Java 开发人员在使用 SWT/JFACE 的时候,并不能借助于 Java 内置的垃圾回收机制来彻底完成系统资源的清理( Java 虚拟机只能帮助我们释放虚拟机内存中的系统资源句柄引用对象)。在 SWT 中系统资源对象的定级类型是 org.eclipse.swt.graphics.Resource ,在类型明确说明了“ Resources created by the application must be disposed ”,这也让我们想起了关于 Image 使用的一句名言“谁创建,谁负责 ”,当然,这个原则也同样适用于其他类型的系统资源。

我们之所以如此关注系统资源的使用,尤其是臭名昭著的图片资源,主要是因为我们怕了系统资源泄漏引起的系统 crash 的问题。例如 org.eclipse.swt.SWTError: No more handles异常有可能在我们试图创建图片资源的时候发生,这说明当前系统句柄已经不足,造成这个问题的罪魁祸首当然是我们写代码的人。

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 ”。我们再看一下另外一个我们熟悉的类型 org.eclipse.swt.graphics.ImageData ,我们可以将其看作是 Image 对应的元数据模型对象,描述了具体创建 Image 需要的信息。

       通过上面的说明,我们发现 SWT 唯一告诉我们的是:自己创建的图片资源,自己负责去销毁,通过调用 Image.dispose() 。那我们在使用 SWT 的时候,应该如何释放图片资源呢?

       我们知道 SWT widget 在销毁的时候,也会销毁子 widget ,所以,覆写你自己的 Component 对应的 dispose 方法,将你使用的系统资源销毁。目前,也只能这样了 ~_~ 。如果觉得不满意,接着看下面的 Display hook 销毁机制。

Display hook 销毁机制】

       Display device 中,我们看了如下一个 hook 接口:

       /**

      * Causes the <code> run() </code> method of the runnable to

     * be invoked by the user - interface thread just before the

     * receiver is disposed.  

     */

       public void disposeExec (Runnable runnable) {

              //注册用户自定义 runnable,在 display release的时候回调此 runnable

              runnable注册到 disposeList

       }

       disposeList中的线程会在 display release的时候被调用,如下:

       /**

  * Releases any internal resources back to the operating

  * system and clears all fields except the device handle.

*/

       protected void release () {

              ……

              //会执行用户注册的销毁线程

              if (disposeList != null) {

                     for (int i=0; i<disposeList.length; i++) {

                            if (disposeList [i] != null) disposeList [i].run ();

                     }

              }

              ……

       }

看来, SWT 并没有把事情做绝了,还是给开发者留下一条后路的。 Display 允许开发者注册一个自定义线程 hook Display release 过程,开发者可以用如下方式来确保开发者使用的系统资源在 Display release 的时候被销毁:

display.disposeExec(new Runnable() {

              public void run() {

                     //销毁系统资源的逻辑代码

                     image.dispose();

                     …….

              }    

       });

以上方式其实也是 JFace 中图片资源管理( ImageRegistry ResourceManager )能够确保 Display release 的时候能够彻底释放被 ImageRegistry 托管的图片资源。

       到这里回顾一下, SWT 中资源释放的途径吧:

1、  覆写相应 Component 对应的 dispose 方法。这有别于 Display hook 机制,因为其能够在 Display 运行期间(未被 release 之前)就释放掉系统资源,最好的方式。

2、  利用 Display hook 机制,确保在 Display release 的时候能够销毁资源。注意,请不要过多依赖此方式,因为很容易造成在 Display release 之前,已经发生了系统 crash 的问题。

JFace 中图片资源管理-- ImageDescriptor

       前面我们已经见过 SWT 中的 Image ImageData 类型了,在继续下面的内容之前,我们先看一下在 JFace 中我们最常用来创建图片资源的一个工厂类: ImageDescriptor。在 ImageDescriptor的类型说明中告诉我们,有两种使用 ImageDescriptor创建图片的方式,分别通过 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,后者的对外操作如下:

        /**

        * Creates the resource described by this descriptor

         */

        public abstract Object createResource(Device device) throws DeviceResourceException;

        /**

       * Undoes everything that was done by a previous call to create(...)

         */

        public abstract void destroyResource(Object previouslyCreatedObject);

这也就是说, ImageDescriptor提供了 createResource / destroyResource接口来负责创建和销毁 Image资源。请注意这边的一点,在孤立使用 ImageDescriptor (没有配合 ResourceRegistry 使用,例如 ImageRegistry )的时候,用户还是要负责通过调用 destroyResource 来释放创建的资源

       其次来看一下 createImage的方式:

              /**

* The returned image must be explicitly disposed using the image's dispose call. The image will not be automatically garbage collected. */

              public Image createImage(boolean returnMissingImageOnError, Device device) {}

这也就是说, ImageDescriptor提供的 createImage的方式,也需要用户来显示的销毁资源。那 createImage createResource两种方式之间的差别是什么呢?稍微分析一下 ImageDescriptor的这两种创建方式的实现,我们就可以看出来差别:

1、 createImage 每次都创建一个全新的图片资源 (图片资源的创建是很耗时的 ~_~

2、  createResource 的方式采用了缓存的方式复用已经创建过的资源 ,并不是每次都创建一个全新的资源。这一点虽然带来了性能的提高,但是并没有解决图片资源释放的问题,倒是给开发者留下了一种假象,造成了随便使用 ImageDescriptor的问题,反而造成了大量的图片资源(当然,更多的是由于调用 createImage的方式造成的,因为每次都创建一个全新的图片资源)没有释放。

到现在为止,我们看到 JFace 已经对 SWT 中的图片资源的管理做了一个小的补充:提供了 ImageDescriptor.createResource 的方式,可以利用缓存效果,能够减少不必要的图片系统资源创建,而且效率有所提高。关于如何释放,可以参考 SWT 中覆写 Component.dispose 的方式,例如在 label provider 使用的图片资源,可以覆写对于 provider dispose 方法, JFace 框架会自动调用。

      

JFace 中图片资源管理-- ImageRegistry & ResourceManager

下面,我们接着看一下 JFace 中的 ImageRegistry 的实现原理。

首先我们看一下 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 )的实现,我们发现: DeviceResourceManager 就是对 DeviceResourceDescriptor ImageDescriptor )进行了引用计数管理。通过 JFaceResources.getResources 利用了前面说的 Display hook 销毁机制(注意,如果不通过 JFaceResources.getResources 来获取 ResourceManager ,则不会默认享受 Display hook 销毁机制,需要自己向 Display 注册 ),确保由被托管 ImageDescriptor 创建的残留在系统中的图片资源在 Display release 的时候会被彻底销毁。核心方法如下:

create(DeviceResourceDescriptor descriptor)  

//如果是首次注册,创建引用技数, allocate资源并对资源进行缓存

//如果是已经注册,增加引用技数,直接返回缓存的系统资源

       destroy(DeviceResourceDescriptor descriptor) //

       //如果引用技术 ==1,通过调用 deallocate彻底销毁资源

       //如果引用技术 >1,削减引用计数(系统资源不会被销毁)

             

那就是说,如果一个 ImageDescriptor ResourceManager 托管了,那由它创建的资源(注意:通过 ImageDescriptor.createResource 的方式)由两种销毁的途径:

1、            如果不通过 JFaceResources.getResources 的方式,单独使用 ResourceManager ,则只能利用 ResourceManager 的引用计数管理来销毁资源(引用计数为 0 时),通过显示调用 ResourceManager.destroy 来削减引用计数。

2、            如果通过 JFaceResources.getResources 来使用 ResourceManager ,则除了能够使用到引用计数管理资源,同时也默认使用了 Display hook 销毁机制, JFace ImageRegistry 也很好的利用了这一点。

现在回头看一下 ImageRegistry 提供的核心操作,着重分析一下 ImageRegistry 在利用了 ResourceManager ImageDescriptor 进行管理的基础上,做了那些补充:

put(String key, Image image)              //注册 image

put(String key, ImageDescriptor descriptor) //注册 descriptor

Image get(String key)                    //获取 imge

ImageDescriptor getDescriptor(String key)   //获取 descriptor

remove(String key)                      //取消注册

dipose()                               //销毁资源

通过对 ImageRegistry 简要的分析之后,我们的结论如下:

1、  如果以 put(String key, ImageDescriptor descriptor) 的方式注册, ImageRegistry 直接讲 descriptor 委托给 ResourceManager 委托管理,自己并不承担管理任务。而且, ImageRegistry 对这种方式注册的 ImageDescriptor 所创建的系统图片资源的销毁也委托给 ResourceManager 进行,并不是在以上自己的 dispose 方法中进行,而是在 ResourceManager.dispose 方法中进行。

2、  如果以 put(String key, Image image) 的方式注册, ImageRegistry 做了部分的补充管理,其将 image 包装进自己的 OriginalImageDescriptor ImageRegistry 的一个内部类,继承自 ImageDescriptor ,对图片资源本身增加引用计数 实现中,并对 image 本身进行了引用计数管理。同时,对这种方式注册的图片资源的销毁是 ImageRegistry 自己承担的,在自身的 dispose 方法中完成。(注意,在 ImageRegistry 的构造方法中,将 ImageRegistry.dispose 封装为一个 runnable 注册到了 ResourceManage dispose 过程中,而 ResourceManage.dispose 已经在 JFaceResources.getResources 方法中被 hook 到了 Display 的资源销毁过程中)。

3、  通过 1 2 的结论, JFace ImageRegistry 对系统资源的销毁已经做了两手准备,

其并不希望用户自己来销毁资源(无论是通过 Image.dispose 还是 ImageDescriptor.destoryResource ,或者 ImageRegistry.dispose ),当然, ImageRegistry 允许通过 remove 接口来取消注册。

             

JFaceResources

+提供 hook 机制

ImageRegistry

+自己管理部分资源

ResourceManager

+管理 ImageDescriptor 及其创建的资源

        

ImageRegistry 的适用场景和使用规则】

通过上面的实现原理分析,我们知道 ImageRegistry 并不欢迎用户来过多地参与图片资源的释放过程,所以 ImageRegistry 适用于如下场景:

1、  决定共享和高度复用的图片资源。这种资源一般是被使用的特别频繁,同时,不急于销毁,只要在 Display release 的时候销毁掉就可以了,所以既可以利用到图片资源本身缓存的优势(减少物理创建的次数),又可以利用其 Display hook 销毁机制,确保会被销毁。

2、  用户可以直接使用 ImageRegistry (不通过 JFaceResources.getImageRegistry 的方式使用),复用部分 ImageRegistry 的管理功能,开发自己的缓存策略,但是,要确保自己会在合适的地方调用 ImageRegistry.dispose 方法来销毁 registry Eclipse Workbench 中的 shared images 机制就用了这一点。

ImageRegistry 的使用规则如下:

1、  谁创建,谁负责 。具体图片资源的创建是由 ImageRegistry 负责的,用户既然托管了,就不应该再干预资源的释放。而且,注册进 ImageRegistry 的资源是共享的,一个用户释放了,会影响到其他用户的使用。当然,对于比较熟悉 JFace ImageRegistry 原理的开发者,可以参与到引用计数的管理,通过这种方式,以安全的、不影响其他用户使用的方式来间接参与释放的过程。

2、  非共享图片资源请不要交由 ImageRegistry 托管。对于一个仅限于局部使用而且使用并不是十分频繁的图片资源,这样做不会带来什么好处,而且,尤其是对于不能参与到引用计数管理的初级用户,这样做反而会使得一个本可以马上释放的图片资源反而会一直占用,直到 Display release 的时候才销毁。

3、  要投入精力对 ImageRegistry key 值进行管理,否则,会引起混乱。因为 ImageRegistry 本质上可以看作 Eclipse 平台中的一个全局对象,对其含有的 key 列表的管理是再所难免。

Eclipse 中插件 share images 机制】

       Eclipse ,一个插件可以暴露( expose )自己的图片资源,以便提供给需要的插件使用,我们就称它为插件之间的 share images 机制吧。上面提到过了,这其实是部分复用了 JFace ImageRegistry 的管理机制。

       如何共享(可以参照 Workbench 插件的 share images 实现):

1、  按照默认约定,创建一个 ISharedImages 接口,提供有意义 key

2、  实现自己创建的 ISharedImages 接口,并结合 ImageRegistry 来管理图片资源;并提供显示的 dipose 公共接口,负责释放自己管理的图片资源

3、  在自己的插件中暴露 ISharedImages

4、  在合适时机,调用 ISharedImages.dispose 来释放资源。这个时机一般选择在 Plugin stop 的时候比较合适,当然,也可以选择在其他时机。

如何使用:

1、  获取目标插件的 ISharedImages 实现,并通过 ISharedImages 提供的 key 值来获取特定的图片资源。以 workbench 插件 share images 为例:

PlatformUI.getWorkbench().getSharedImages().getImage(key)

如:PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_PROJECT)

2、 暴露图片资源的插件负责图片资源的创建和销毁,其他插件不要参与销毁过程。换句话说,还是要遵守谁创建、谁负责的原则。 workbench 插件 share images 为例:

workbench close 的时候,会间接调用 ISharedImages.dispose()

Eclipse 中使用图片资源的经验总结】

1、  坚持“谁创建,谁负责”的原则 。分为如下:

a)         如果是用户自己创建的,请自己释放。例如通过覆写 Component 对于的 dispose 方法、通过覆写 label provider 对应的 dispose 方法等等,这对于一些适用于局部的图片资源较为适合;当然,也可以变态利用 Display hook 释放机制(但是,一般对于长期使用的资源才会这样做!!!)。

b)        如果是通过 JFaceResources.getImageRegistry 的方式使用 ImageRegistry 时,请不要释放资源,让 ImageRegistry 自己解决。一般使用于比较频繁使用的全局共享图片资源,例如想保持风格统一的图片资源等。

c)        如果是使用了 IShareImages 的机制,请提供图片资源的插件自己负责释放。如何使用这种机制,最好参照 eclipse 中已有的实现,保持风格统一,“有样学样”吧。

2、  正确认识系统资源泄漏引起的 crash 问题的原因 。导致原因有两种:

a)         首先,是没有释放 ,导致泄漏。请参照上面的“谁创建,谁负责”的原则。

b)        其次,是释放的过晚 ,导致积累过多。例如本来应该立即释放的资源,反而通过 ImageRegistry 进行了托管,同时有没有控制引用计数的管理,导致到了 Display release 的时候才释放资源。同样道理,本来不需要暴露给其他插件贡献的图片资源,反而暴露了,导致释放过完等。

3、  正确认识系统资源的创建和销毁所带来的时间消耗,这是从系统性能的角度考虑。例如,可以用 ImageDescriptor.createResource 的方式替换原始的 new Image 的方式,减少创建资源过于频繁和销毁资源过于频繁所带来的时间占用。对于需要长期使用的贡献资源,可以使用 ImageRegistry 的方式等等。

4、  对于特殊的场景,可以在参考以上原理(例如 JFace 中的图片管理的实现原理分析)的基础上自己实现图片资源的管理策略 。这对团队开发产品的情况下尤其适用,一方面可以优化管理策略,使之更切近团队应用;再者,可以减少 JFace ImageRegsitry 使用的复杂度,并减少误用。例如,我们可以把插件间 share images 的机制看成是对 JFace ImageRegsitry 的灵活使用。

5、  无论使用那种管理策略(无论是来自 eclipse 还是其他),使用这前一定要仔细看 API 说明,并简要分析一下实现原理。对于做上规模的插件产品 / 应用来讲,毕竟对图片这种系统资源的管理太重要了!!!对于做较为简单的开发,基本上本着“谁创建、谁负责”的原则,用完之后在自己感觉合适的地方销毁掉就可以了,完全可以不去碰 JFace 中的 ImageRegistry 那套东东,引来不必要的负责度和复用,尤其是对于新手来说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值