Studio主流插件
我們知道Android Studio是基于Intellij的一套IDE環(huán)境,Intellij本身為開發(fā)者提供了插件式的開發(fā)環(huán)境,大大提高了開發(fā)效率和IDE可配置化。目前studio的成熟插件已有很多。
我們這里先來看看目前已有的主流Studio插件有哪些,幾乎已經(jīng)涵蓋了你所有的需求:
https://juejin.im/entry/5998090ff265da248a7a6bde
基本可以把插件的功能分為以下幾類:
1、解決重復(fù)性工作:
把studio工作中技術(shù)含量低,重復(fù)性高的工作,用插件形式代替。
插件名稱 | 插件功能 |
---|---|
GsonFormat | (jsonString自動生成JavaBean類) |
ButterKnife Zelezny | (xml自動生成butterknife的注解代碼) |
Code Generator | (xml自動生成activity fragment) |
AndroidProguardPlugin | (根據(jù)依賴的第三方庫,生成proguard文件) |
Exynap | (更加擴(kuò)展,把成型、固定的代碼段,自動生成) |
MVPHelper | (自動生成 M V P 到不同文件夾) |
2、集成studio不包含的功能:
為了開發(fā)的方便,將studio本身不具備的功能引入,擴(kuò)展IDE的功能,避免studio和第三方來回切換和數(shù)據(jù)傳輸?shù)穆闊?/p>
插件名稱 | 插件功能 |
---|---|
EventBus3 Intellij | (輔助 索引eventbus 從subscribe到post,提高eventbus可讀性) |
GradleDependenciesHelperPlugin | (gradle依賴自動補(bǔ)全) |
SQLScout | (調(diào)試sqlite) |
FindBugs-IDEA | (findbugs插件) |
Android Methods Count | (預(yù)覽依賴庫中方法數(shù),提前判斷方法數(shù)超限) |
各插件的開發(fā)和使用成熟度很高,大部分是免費(fèi)并且開源的,活躍度也很高。因?yàn)閟tudio的使用率極高,而且IntelliJ IDE本身的插件資源豐富,直接借鑒的插件也有很多,使得插件開發(fā)的門檻大大降低。
那么我們自己再遇到重復(fù)性高的工作,或者第三方功能需要嵌入時,也建議考慮插件的方式。
插件的安裝方法
1、Preferences - plugins - Browse repositories 查找jetBrains遠(yuǎn)程倉庫上的插件
很多插件是免費(fèi)且開源的(github),遠(yuǎn)程repositories上對應(yīng)的plugins都是最新的release版本
我們可以使用beta版本,或者自己對開源插件進(jìn)行二次開發(fā),這時就需要安裝本地插件:
2、Preferences - plugins - Install plugin from disk 查找本地plugin的jar包
本文會以一個我自己二次開發(fā)的plugin為例,記錄下plugin開發(fā)的基本流程和值得注意的坑。
插件開發(fā)
studio是基于IntelliJ的二次開發(fā)的IDE,所以plugins其實(shí)是IntelliJ的插件,IntelliJ這個IDE本身就可以開發(fā)plugins,IntelliJ下載免費(fèi)版即可,官網(wǎng)下載,community版本夠用。不再贅述。
新建及import工程
新建project很多文章都有講,不贅述,可以參考:http://www.lxweimin.com/p/336a07b9d98a
基本是配置IntelliJ sdk、創(chuàng)建plugin project、然后在plugin.xml中配置此插件即可
重點(diǎn)說下import工程,如果你是二次開發(fā)一個插件,那么import一個github已有的工程是必須的。以GsonFormat為例(https://github.com/zzz40500/GsonFormat/)
github工程下,分為兩個分支master和dev_1.2.2其中dev開發(fā)分支可以直接用于二次開發(fā)。(master分支直接import作為project,需要IDE配置很多東西)
dev分支工程配置步驟
- import project from existing code
注意我們的工程是plugin,選擇的sdk不是jdk1.8(此處同new project),而是IntelliJ IDEA Community,一路‘下一步’這個過程中,有一步已經(jīng)把src下代碼作為module放入了project,生成了GsonFormat.iml, -
只是此時GsonFormat.iml中module type是JAVA_MODULE,而不是PLUGIN_MODULE,需要修改。
(project的module設(shè)置很重要,決定了project是否可以正常編譯)
此處配置成功的標(biāo)志就是 IDE出現(xiàn)了run 和 debug兩個按鈕。如果還不正常,可以進(jìn)入IDE右上角的按鈕進(jìn)入project structure進(jìn)行配置
配置sdk -
如果想正常編譯插件,還需要一步,在 Edit Configuration中配置project的屬性,見圖二,新建一個Plugin Configuration,在右側(cè)的Use classpath of module中選擇剛剛的GsonFormat(由于剛剛我們成功配置了GsonFormat為PLUGIN_MODULE,否則此處找不到哦)
配置plugin的Module - 到了這一步,無論是run debug 還是Prepare Plugin Module For Deployment(產(chǎn)出本地plugin jar)都可以了。
分析plugin工程
import成功后,我們看一下plugin工程是怎樣的?
plugin工程中常見以下三類文件,也是plugin工程較為特有的文件類型:
Action
作為整個插件的入口類,其入口方式和name等定義在plugin.xml,Action中actionPerformed作為入口方法,初始化當(dāng)前類,包,傳入到dialog中Dialog 類似于android中的activity,綁定了Form類,用于view的databinding和邏輯
JsonDialog是入口dialog,F(xiàn)ieldsDialog是解析jsonstring后展示的dialog,SettingDialog是配置dialogGUI Form 類似于android中xml布局文件,只不過此處是swing的拖拽控件,F(xiàn)orm與Dialog是配對出現(xiàn),其對應(yīng)關(guān)系在Form配置。
GsonFormat代碼架構(gòu)
以GsonFormat plugin為例,具體講清楚plugin工程的組成和實(shí)現(xiàn)原理。
(GsonFormat插件是把jsonString轉(zhuǎn)變?yōu)閖avaBean的前端插件,寫業(yè)務(wù)代碼的朋友們應(yīng)該非常熟悉,這款插件的使用過程是這樣子的:)
第一步:彈窗:輸入你要轉(zhuǎn)換的jsonString,此處也可以Setting進(jìn)行配置
第二步:彈窗:展示轉(zhuǎn)換成功的field class,你可以在此基礎(chǔ)上自定義。
最后:我們得到了我們想要的javaBean
這個插件的基本功能如上,下面我們簡單分析下源碼:
代碼(類)的組織方式
主Action是MainAction,作為插件的入口可以看到他啟動了彈窗JsonDialog。工程中維護(hù)了幾個dialog(包括java文件和form表單文件),分別對應(yīng)插件工作中所有的彈窗,被放入了ui文件夾。
[圖片上傳失敗...(image-108cd0-1542190836830)]
再來看其他文件夾:
[圖片上傳失敗...(image-4be0b0-1542190836830)]
- DataWriter類負(fù)責(zé)GsonFormat最后一步寫入class文件,
- config文件夾中類維護(hù)了插件的settings屬性(屬性用戶可以在SettingsDialog配置),
- entity文件夾內(nèi)是實(shí)體類,classEntity fieldEntity等類都是維護(hù)最終生成class中的field及innerclass的實(shí)體類。
- process文件夾內(nèi)是處理類,jsonstring的解析,javaBean封裝等具體的操作都是在這些類中完成的,是插件的核心類。
處理流程
處理流程的代碼邏輯是流式的,從MainAction入口開始看起,在JsonDialog中點(diǎn)擊確定后,開始解析jsonString。
類JsonUtilsDialog中,點(diǎn)擊事件的響應(yīng)函數(shù)作為入口:
editTP.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent keyEvent) {
super.keyReleased(keyEvent);
if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
onOK();
}
}
});
1.解析的過程,主要在ConvertBridge類完成,由run方法作入口,開始解析jsonSTR
onOK()方法的實(shí)現(xiàn):
private void onOK() {
//省略部分:get PsiClass generateClass:
new ConvertBridge(
this, errorLB, jsonSTR, mFile, mProject, generateClass,
mClass, generateClassName).run();
}
2.ConvertBridge類中run方法,通過parseJson方法,開始解析jsonString,
public void parseJson(JSONObject json) {
if (Config.getInstant().isVirgoMode()) {
//省略代碼:裝配mGenerateEntity對象
//createFields 解析jsonString核心方法
mGenerateEntity.setFields(createFields(json, fieldList, mGenerateEntity));
FieldsDialog fieldsDialog = new FieldsDialog(mJsonUtilsDialog, mGenerateEntity, mFactory,
mGeneratClass, currentClass, mFile, project, generateClassName);
} else {
mGenerateEntity.setFields(createFields(json, fieldList, mGenerateEntity));
WriterUtil writerUtil = new WriterUtil(null, null, mFile, project, mGeneratClass);
writerUtil.mInnerClassEntity = mGenerateEntity;
writerUtil.execute();
}
}
分為Virgo模式和非Virgo模式(默認(rèn)virgo模式):virgo模式,就是啟動FieldsDialog,就是我們見到的第二個窗口,用戶自行修改fields的定義,非virgo比較簡單,跳過dialog直接寫入fields到class里。除非settings自己定義,否則我們一般都使用virgo模式。可以看到無論是否virgo模式與否,都會調(diào)用createFields方法,區(qū)別只是是否顯示FieldsDialog。
3.詳細(xì)看下createFields方法做了什么。
for (int i = 0; i < list.size(); i++) {
String key = list.get(i);
Object type = json.get(key);
if (type instanceof JSONArray) {
//將jsonArray放入listEntityList
listEntityList.add(key);
continue;
}
FieldEntity fieldEntity = createFiled(parentClass, key, type);
fieldEntityList.add(fieldEntity);
}
for (int i = 0; i < listEntityList.size(); i++) {
//解析listEntityList中數(shù)據(jù)
String key = listEntityList.get(i);
Object type = json.get(key);
FieldEntity fieldEntity = createFiled(parentClass, key, type);
fieldEntityList.add(fieldEntity);
}
通過createFields方法把field放入FieldEntity,DataWriter再根據(jù)FieldEntity的內(nèi)容寫入class中,
4.以上的解析過程,只涉及了一層JavaBean的情況,JavaBean大部分情況下,是要嵌套Bean內(nèi)部類的,就是JSONObject內(nèi)部是嵌套jsonobject的,我們繼續(xù)來看:
private FieldEntity typeByValue(InnerClassEntity parentClass, String key, Object type) {
if (type instanceof JSONObject) {
InnerClassEntity classEntity = checkInnerClass((JSONObject) type);
if (classEntity == null) {
//省略代碼
} else {
FieldEntity fieldEntity = new FieldEntity();
fieldEntity.setKey(key);
fieldEntity.setTargetClass(classEntity);
fieldEntity.setType("%s");
nodeBean = fieldEntity;
}
}
createFields方法中對每個fieldEntity依次調(diào)用createField方法,createFiled方法中調(diào)用了typeByValue方法,在createInnnerClass中 對子json再次進(jìn)行createFields方法,如此依次遞歸。完成了一層層javabean的解析工作。
可以說Class中包括FieldEntry,而FieldEntry本身也是包含多個子FieldEntry的。FieldEntry可以設(shè)置基本類型,也可以設(shè)置ClassEntity。
5.最終通過WriterUtil類將FieldEntry寫入到class文件中,完成了整個的插件功能。
二次開發(fā)的部分
開發(fā)中遇到的問題
由于代碼混淆的原因,開發(fā)中經(jīng)常遇到debug下正常的代碼,在release包情況下無法正常解析網(wǎng)路數(shù)據(jù),因?yàn)閖avabean類中的field混淆后已經(jīng)不是原來定義的名稱了。而這個問題在提測關(guān)口最容易出現(xiàn)。想解決這個問題必須保證javaBean在打包中不被混淆。
如何不被混淆,不同廠商有不同的解決策略(規(guī)范):
- 統(tǒng)一放到一個文件夾里(或者含固定名稱的文件夾),混淆時ignore 這些文件夾。
但是這辦法操作起來不完美,一個是重構(gòu)后文件夾容易變名字,還有團(tuán)隊(duì)開發(fā)時無法保證所有的人都遵守。處理代碼時都要繃著文件夾名稱這個弦。 - 所有JavaBean extends Serializable(統(tǒng)一基類)
這樣proguard文件中保證所有Seriallizable的子類不被混淆即可。而且Bundle傳遞參數(shù)時javaBean可以直接被用。但是這個辦法也有一個缺點(diǎn):需要保證所有人遵守這個約定,無法規(guī)范這個步驟。
我們的解決方式:
一般我們的接口管理系統(tǒng)中,都可以產(chǎn)生mock的jsonString,客戶端開發(fā)會直接利用SGsonFormat插件將jsonString直接轉(zhuǎn)為JavaBean,所以基于GsonFormat功能二次開發(fā),讓所有的JavaBean class統(tǒng)一繼承Serializable,這樣兼顧了易用性和統(tǒng)一性。
GsonFormat的二次開發(fā):
統(tǒng)一繼承Serializable的邏輯,應(yīng)該放入DataWriter寫入的流程中,分析可得:在ClassProcessor中process方法,實(shí)際上將classContent的String內(nèi)容通過PsiElementFactory寫入class文件中,所以修改String classContent既可。
protected void generateClass(PsiElementFactory factory, ClassEntity classEntity, PsiClass parentClass, IProcessor visitor) {
onStartGenerateClass(factory, classEntity, parentClass, visitor);
PsiClass generateClass = null;
if (classEntity.isGenerate()) {
if (Config.getInstant().isSplitGenerate()) {
try {
generateClass = PsiClassUtil.getPsiClass(
parentClass.getContainingFile(), parentClass.getProject(), classEntity.getQualifiedName());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} else {
//根據(jù)classContent創(chuàng)建class
String classContent =
"public static class " + classEntity.getClassName() + " implements Serializable" + "{}";
generateClass = factory.createClassFromText(classContent, null).getInnerClasses()[0];
}
if (generateClass != null) {
//遞歸調(diào)用,創(chuàng)建內(nèi)部類
for (ClassEntity innerClass : classEntity.getInnerClasss()) {
generateClass(factory, innerClass, generateClass, visitor);
}
if (!Config.getInstant().isSplitGenerate()) {
generateClass = (PsiClass) parentClass.add(generateClass);
}
//創(chuàng)建內(nèi)部變量
for (FieldEntity fieldEntity : classEntity.getFields()) {
generateField(factory, fieldEntity, generateClass, classEntity);
}
//創(chuàng)建內(nèi)部變量getter setter方法
generateGetterAndSetter(factory, generateClass, classEntity);
generateConvertMethod(factory, generateClass, classEntity);
}
}
onEndGenerateClass(factory, classEntity, parentClass, generateClass, visitor);
if (Config.getInstant().isSplitGenerate()) {
formatJavCode(generateClass);
}
}
插件下載地址:(該插件已提交repository) https://plugins.jetbrains.com/plugin/11100-sgsonformat/update/49532
或者直接搜索SGsonFormat,install即可使用
總結(jié)
二次開發(fā)的改動并不大,但是把Studio的plugin開發(fā)環(huán)境和流程算是熟悉了一遍,plugin插件的開發(fā)可以說你會用java就能上手,只不過他自定義的文件類型和組織方式需要熟悉。如果有需要的話,做個新的plugin提高工作效率,是個很好的方式。