Eclipse 运行时定义插件(org.eclipse.osgi和org.eclipse.core.runtime),其它所有插件都依赖于它们。运行时负责定义插件的结构以及它们包含的实现详细信息(bundle和类载入器)。运行时还负责查找和执行主要的 Eclipse应用程序以及维护插件的注册表、它们的扩展和扩展点。
运行时还提供了一批实用程序(例如,记录、调试跟踪选项、适配器、首选项库和并发性基础结构)。当然,作为最小的内核,一旦创建了使用运行时并执行某些种类任务的插件,该运行时就使人感兴趣。与 Atlas一样,运行时插件坚实地驻留在插件堆的底部,成为 Eclipse的强大基础。
当用户启动使用 Eclipse 开发的应用程序时,平台运行时引擎就会启动。运行时实现平台使用的基本插件模型和基础结构。它保持跟踪所有已安装的插件和它们提供的功能。
插件是一种结构化组件,它将代码(和/或文档)添加至系统并以结构化方式描述它。插件可以定义扩展点,即,其它插件可以扩展的严格定义的功能点。当插件向扩展点添加实现时,我们就认为它向平台添加了扩展。这些扩展和扩展点是在插件的清单文件(plugin.xml)中声明的。
使用常见的扩展模型提供了结构化方式供插件描述可以采用何种方式扩展它们以及客户机插件描述它们提供的扩展。定义扩展点与定义其它任何 API很相似。唯一的区别就是扩展点是使用 XML而不是使用代码特征符来声明的。类似地,客户机插件使用 XML来向系统描述它的特定扩展。
运行时的总体目标是最终用户不应该为已安装但未使用的插件消耗内存或性能。平台扩展模型的说明性质允许运行时引擎确定哪些扩展点和扩展已经由插件提供了但是以前还没有运行过。这样,可以安装许多插件,但是将不会激活它们,直到根据用户的活动请求了插件所提供的功能为止。这是提供灵活可靠的平台的一个重要特征。
支持插件的机制是通过使用 OSGi 框架实现的。从这一点上来说,插件是与 OSGibundle相同的。bundle 及其相关联的类指定并实现 Java类装入的过程、先决条件管理和 bundle的生命周期。在此讨论的其余部分,除非是讨论框架中的特定类,否则我们将交替使用术语插件和bundle。
Plugin类表示正在平台中运行的插件。通过它可以很方便地将插件的生命周期方面的内容和总体语义集中起来。插件可以实现其生命周期的启动和停止方面的特殊功能。每个生命周期方法都包括对BundleContext(它可以提供更多信息)的引用。
生命周期的启动部分是值得特别讨论的。我们已看到有关插件的信息可以从插件的清单文件中获的而不必运行任何插件的代码。通常,工作台中的某些用户操作会导致需要启动插件的一系列事件。从实现角度来看,只有在需要装入包含在插件中的类时,才会启动该插件。
start 方法可以很方便地实现插件的初始化和注册行为。但是,认识到可以在许多不同的情况下启动插件是很重要的。象获取修饰对象的图标一样简单的操作就可以导致装入插件的某一个类,这样就会启动插件。过早进行初始化会导致在需要装入插件的代码和数据之前很早就装入它们。因此,一定要仔细查看插件的初始化任务并考虑启动时执行初始化的替代方法。
生命周期管理是 OSGi “bundle”术语和平台的“插件”术语汇合的位置。当插件启动时,会为该插件提供对BundleContext(从它可以获得与插件相关的信息)的引用。BundleContext还可以用来查明系统中的其它 bundle/插件。
BundleContext.getBundles()可以用来获得系统中的所有 bundle的集合。可以注册BundleEvent 的侦听器,以便插件知道何时在其生命周期状态中另一个 bundle发生了更改。有关更多信息,请参阅BundleContext和BundleEvent的 Javadoc。
在 3.0 之前,提供了插件注册表(IPluginRegistry)来提供类似的信息。例如,可以查询它以获得系统中所有插件的插件描述符。现在,不推荐使用此注册表,应该使用BundleContext来达到此目的。平台注册表现在专用于提供有关扩展和扩展点的信息。
BundleActivator接口定义在Plugin中实现的启动和停止行为。尽管Plugin类很容易实现此功能,但是,插件开发者可以完全自由地在适合于插件设计的任何类中实现BundleActivator的接口。实际上,如果插件没有特定的生命周期管理需要,它就根本不需要实现此接口。
在每个插件中都存在一个由框架管理的 OSGi bundle。Bundle是 OSGi 模块单元。从根本上来说,bundle只是安装在平台中的文件(资源和代码)的集合。每个 bundle都具有它自己的 Java类载入器,并且包括用于启动、停止和卸载它本身的协议。从 Eclipse平台的角度来说,Bundle只不过是一个实现类。插件开发者不扩展 bundle 类,但是使用Plugin或其它BundleActivator实现来表示插件。
当运行时插件和运行时工具对插件的“bundle”方面感兴趣时,插件关心已由插件定义了哪些扩展点以及插件添加了哪些扩展是非常正常的。此信息由平台扩展注册表IExtensionRegistry提供。
为什么插件可能想知道提供了哪些扩展?具体示例将帮助显示对此信息的需要以及用于获取信息的协议。
重新调用工作台显示视图对话框,该对话框将显示已经安装在平台中的所有可用视图。
我们知道,所有已添加的视图的类别名称和视图名称是在为org.eclipse.ui.views添加扩展的任何插件的plugin.xml 文件中指定的。但是,工作台如何找到此信息呢?从平台扩展注册表中查找。以下代码是基于显示视图对话框的工作台实现的简化片段:
...
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint point = registry.getExtensionPoint("org.eclipse.ui.views");
if (point == null) return;
IExtension[] extensions = point.getExtensions();
for (int i = 0; i < extensions.length; i++)
readExtension(extensions[i]); //get the information about each extension
...
我们在上面已经知道可以从Platform类中获得该注册表。IExtensionRegistry中的协议用来查找名为org.eclipse.ui.views的扩展点。可以使用IExtensionRegistry、IExtensionPoint和IExtension中定义的协议来找到注册表中有关特定扩展点或扩展的信息。这些类的 Javadoc 提供了有关注册表协议的详细信息。
一旦找到了感兴趣的扩展定义,就可以使用IConfigurationElement中的协议来检查扩展的各个属性。
org.eclipse.core.runtime.preferences包提供了用于存储插件的首选项的基础结构。首选项通常映射至工作台首选项页面上受用户控制的设置,尽管这不是底层基础结构所必需的。插件首选项是键/值对,其中键描述首选项的名称,而值是几种不同类型的其中一种(boolean、double、float、int、long或 string)。可以存储首选项,并由平台从文件系统中检索到。已保存的首选项的准确位置取决于首选项的作用域。
首选项的作用域与首选项的存储位置紧密相关。插件开发者可以决定为哪些标准作用域申请它们的首选项,也可以定义对于它们的插件有意义的新作用域。让我们首先查看一下由平台运行时定义的作用域:
可以将整个首选项库看作是各个节点的层次结构,层次结构中的每个主要分支都表示特定的作用域。任何特定节点的子代都取决于是如何定义该作用域的。对于实例和配置作用域,子节点是特定插件的首选项,如首选项限定符(通常是插件的标识)指定的那样。
如果所有这些让您听起来感到很迷惑,不用担心。如果您不在意作用域和节点,则不需要考虑任何特定作用域或者树的哪个节点实际包含您的首选项值。当您查询首选项值时,首选项 API将按正确的顺序(实例、配置和缺省值)自动遍历节点,并使用所提供的限定符和首选项名称来查找实际包含该值的节点。
首选项是使用 IPreferencesService协议来访问的。可以通过使用Platform类来访问平台的缺省首选项服务。
...
IPreferencesService service = Platform.getPreferencesService();
...
一旦获得了首选项服务,就可以使用在IPreferencesService中提供的任何get... 方法按名称查询首选项值。例如,以下片段查询“com.example.myplugin”插件中的“MyPreference”首选项的值。
...
IPreferencesService service = Platform.getPreferencesService();
boolean value = service.getBoolean("com.example.myplugin", "MyPreference", true, null);
//do something with the value.
...
查询方法中的最后一个参数是在搜索首选节点时要使用的作用域上下文的数组。如果该数组为null,则平台假定应该使用缺省作用域搜索顺序,并估计适当的首选节点。如果传递了作用域上下文的数组,这将确定应该用来查找首选节点的作用域查找顺序。如果使用指定的作用域找不到节点,则始终参考缺省作用域查找顺序。
如果插件需要更准确地控制作用域搜索顺序,则可以使用表示作用域的类来访问表示特定作用域中的首选项的实际节点。通过使用这种方法,可以创建节点的数组,它指定要求的特定搜索顺序。以下片段查询上面使用的同一首选项的首选项服务,但是搜索插件的配置作用域,接着是插件的实例作用域。当为搜索顺序指定节点时,不会考虑缺省作用域。即,平台将只搜索已提供的那些节点。
...
IPreferencesService service = Platform.getPreferencesService();
Preferences configurationNode = new ConfigurationScope().getNode("com.example.myplugin");
Preferences instanceNode = new InstanceScope().getNode("com.example.myplugin");
Preferences[] nodes = new Preferences[] {configurationNode, instanceNode};
stringValue = service.get("MyPreference", "true", nodes);
//do something with the value.
...
插件还可以通过首选项树节点来实现它自己的遍历。可以从首选项服务中获得首选项树的根节点。作用域类可以用来进一步遍历树。以下片段遍历到特定节点,并从节点本身检索首选项值。
...
IPreferencesService service = Platform.getPreferencesService();
Preferences root = service.getRootNode();
Preferences myInstanceNode = root.node(InstanceScope.SCOPE).node("com.example.myplugin");
if (myInstanceNode != null) {
value = node.getBoolean("MyPreference", "true");
//do something with the value.
}
...
插件可以使用 org.eclipse.core.runtime.preferences扩展来定义它们自己的专用作用域。在此扩展中,插件定义新作用域的名称以及可以为新作用域创建首选节点的类。(可选)它可以指定初始化该作用域的缺省首选项值的类的名称。当插件定义新的作用域时,由该插件负责实现任何新的作用域的遍历顺序(相对于平台遍历顺序)。我们将使用项目范围的首选项的特定示例来更详细地说明此功能。