插件開發(fā)系列
Android Studio插件開發(fā)1之插件介紹與環(huán)境搭建
Android Studio插件開發(fā)2之Action System
Android Studio插件開發(fā)3之Extensions And Extension Points(擴(kuò)展與擴(kuò)展點(diǎn))
如果說(shuō)自己定義Action并實(shí)現(xiàn)相應(yīng)的功能邏輯是造輪子的話,那么實(shí)現(xiàn)Extensions就是使用別人的輪子了,簡(jiǎn)單快速實(shí)現(xiàn)復(fù)雜的功能!
定義
Extensions 和Extension Points是Intellij平臺(tái)提供的一套供插件之間或者是插件與平臺(tái)核心功能之間通信的接口。
Extension points
一個(gè)插件提供給其它插件擴(kuò)展自己功能的入口點(diǎn),通過定義接口來(lái)約定溝通的方法,通俗點(diǎn)說(shuō)就是造好輪子等別人來(lái)用。
Extensions
一個(gè)插件聲明一個(gè)Extension,并通過對(duì)應(yīng)的Extension Point實(shí)現(xiàn)相應(yīng)的功能,通俗的說(shuō)就是復(fù)用別人造好的輪子。
關(guān)系圖
如果文字表達(dá)得不夠明白,看一下以下的說(shuō)明圖應(yīng)該能加深理解,畢竟一圖勝千言

聲明Extension和Extension Point
Extension和Extension Point都需要在plugin.xml聲明,聲明的語(yǔ)法如下
Extension Point
<extensionPoints>
<extensionPoint name="ConcreteExtensionPoint1" beanClass="com.example.BeanClass">
<extensionPoint name="ConcreteExtensionPoint2" interface="com.example.Interface">
</extensionPoints>
- name 此Extension Point的名字
- beanClass/interface 提供的接口,定義與Extension的交互方式
Extension Point聲明的方式有兩種,第一是通過interface的方式,此方式定義一個(gè)java接口供擴(kuò)展方實(shí)現(xiàn);第二是beanClass的方式,其定義一個(gè)java bean,并在擴(kuò)展方聲明Extension的時(shí)候,由擴(kuò)展方提供java bean的屬性,這里不明白的話先看下面Extension的聲明。
Extension
聲明通過interface方式定義的Extension
<extensions defaultExtensionNs="com.intellij">
<appStarter implementation="MyTestPackage.MyTestExtension1" />
</extensions>
Extesion的聲明應(yīng)該被包裹在<extensions>標(biāo)簽下
-
<extensions>標(biāo)簽的defaultExtensionNs屬性的值是對(duì)應(yīng)Extension Point的插件id,但如果這個(gè)Extension Point由Intellij提供,則是“com.intellij”,插件的id可以在plugin.xml文件查看 - 其中的標(biāo)簽的名字是Extension Point的name,例如要用到上面聲明的Extension Point,則是
<ConcreteExtensionPoint1>
這里內(nèi)容很簡(jiǎn)單,就是實(shí)現(xiàn)接口,通過implementation屬性提供接口
聲明通過beanClass方式定義的Extension
<extensions defaultExtensionNs="com.intellij">
<codeInsight.template.postfixTemplateProvider language="JAVA" implementationClass="com.kogitune.intellij.codeinsight.postfix.AndroidPostfixTemplateProvider"/>
</extensions>
這里language和implementationClass實(shí)際上是java bean的屬性,下面是這個(gè)Extension Point的beanClass的內(nèi)容
public class LanguageExtensionPoint<T> extends CustomLoadingExtensionPointBean implements KeyedLazyInstance<T> {
// these must be public for scrambling compatibility
@Attribute("language")
public String language;
@Attribute("implementationClass")
public String implementationClass;
private final NotNullLazyValue<T> myHandler = new NotNullLazyValue<T>() {
@Override
@NotNull
protected T compute() {
try {
return (T)instantiateExtension(implementationClass, ApplicationManager.getApplication().getPicoContainer());
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
};
@NotNull
@Override
public T getInstance() {
return myHandler.getValue();
}
@Override
public String getKey() {
return language;
}
}
其中l(wèi)anguage和implementationClass上加了@Attribute注解,加了這個(gè)注解的屬性需要由Extension在聲明時(shí)提供
<codeInsight.template.postfixTemplateProvider language="JAVA" implementationClass="com.kogitune.intellij.codeinsight.postfix.AndroidPostfixTemplateProvider"/>
實(shí)例
一般情況下都是我們寫的都是功能比較簡(jiǎn)單的小插件,除非是專門寫插件的軟件公司,否則很少情況下會(huì)在插件里定義Extension Point供別人使用,所以重點(diǎn)是怎樣使用別人的Extension Point,尤其是系統(tǒng)提供的Extension Point,那可是系統(tǒng)的核心功能。(好吧是其實(shí)我不會(huì)寫Extension Point(-_-))
通過Extension可以做什么?
先看三張圖



第一張圖是代碼編輯器的錯(cuò)誤提示,第二張圖是live template,第三張圖是系統(tǒng)提供的postfix completion,相信這些功能我們平時(shí)都不少用,其實(shí)這些功能我們都可以通過Extension來(lái)自定義的!
這里提供一個(gè)livetemplate的實(shí)例和推薦一個(gè)postfix completion的項(xiàng)目
live template
想當(dāng)年Eclipse用得飛起的時(shí)候“syso”打印字符串到控制臺(tái)的代碼補(bǔ)全用得非常順手,剛轉(zhuǎn)到Intellij平臺(tái)的時(shí)候還有點(diǎn)不適應(yīng)呢,現(xiàn)在借助live template我們也能實(shí)現(xiàn)一個(gè)Intellij版本的“syso”補(bǔ)全
首先,找到Extension Point,這里的Extension Point是系統(tǒng)提供的defaultLiveTemplatesProvider,這樣我們可以在plugin.xml中聲明我們的Extension
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<defaultLiveTemplatesProvider implementation="MyTemplateProvider"/>
</extensions>
這個(gè)Extension Point是通過interface聲明的,所以提供實(shí)現(xiàn)類MyTemplateProvider如下
import com.intellij.codeInsight.template.impl.DefaultLiveTemplatesProvider;
import org.jetbrains.annotations.Nullable;
public class MyTemplateProvider implements DefaultLiveTemplatesProvider {
@Override
public String[] getDefaultLiveTemplateFiles() {
return new String[]{"templates/mytemplatefile"};
}
@Nullable
@Override
public String[] getHiddenLiveTemplateFiles() {
return new String[0];
}
}
內(nèi)容很簡(jiǎn)單,就是返回一個(gè)文件的路徑,是相對(duì)于resources文件夾的相對(duì)路徑,不要后綴名,絕對(duì)路徑是 projectpath/resources/templates/mytemplatefile.xml
也就是說(shuō)我們只要在這個(gè)文件里按語(yǔ)法定義好live template就好了,重點(diǎn)也是在定義live template上,mytemplatefile.xml的內(nèi)容如下
<templateSet group="other">
<template name="syso" value="System.out.println($END$);"
description="Prints a string to System.out like Eclipse"
toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true"/>
</context>
</template>
</templateSet>
一個(gè)<template>標(biāo)簽定義一個(gè)live template,name屬性和value屬性就是livetemplate的縮寫和縮寫展開對(duì)應(yīng)的內(nèi)容,這里只展示實(shí)現(xiàn)Extension的邏輯,具體怎樣自定義live template可以參考相關(guān)文檔
https://www.jetbrains.com/help/idea/live-templates.html
下面是這個(gè)小demo的運(yùn)行效果

postfix completion
Android Studio和IDEA提供了一部分postfix completion,可惜的是并沒有提過我們?cè)贗DE里自定義postfix completion的入口,但是可以通過插件增加自己的postfix completion,其原理也是利用系統(tǒng)的Extension Point
有興趣的朋友可以看看這個(gè)我參與的插件項(xiàng)目
其中用到的Extension Point是codeInsight.template.postfixTemplateProvider
問答
-
系統(tǒng)有那么多的功能,我怎么知道它有沒有Extension Point或者它的Extension Point是什么?
不用擔(dān)心,Intellij的文檔提供了下面的Extension Point列表
-
有Extension Point,但是不會(huì)用怎么辦?
查看其他插件的源碼,例如那些提供智能補(bǔ)全的插件,都是通過系統(tǒng)的Extension Point完成的,所以可以推測(cè)一下自己常用的插件里有沒有自己需要使用的Extension Point來(lái)學(xué)習(xí)怎樣使用