SGsonFormat——Android Studio外掛二次開發
Studio主流外掛
我們知道Android+Studio/">Android Studio是基於Intellij的一套IDE環境,Intellij本身為開發者提供了外掛式的開發環境,大大提高了開發效率和IDE可配置化。目前studio的成熟外掛已有很多。
我們這裡先來看看目前已有的主流Studio外掛有哪些,幾乎已經涵蓋了你所有的需求:
ofollow,noindex">https://juejin.im/entry/5998090ff265da248a7a6bde基本可以把外掛的功能分為以下幾類:
1、解決重複性工作:
把studio工作中技術含量低,重複性高的工作,用外掛形式代替。
外掛名稱 | 外掛功能 |
---|---|
GsonFormat | (jsonString自動生成JavaBean類) |
ButterKnife Zelezny | (xml自動生成butterknife的註解程式碼) |
Code Generator | (xml自動生成activity fragment) |
AndroidProguardPlugin | (根據依賴的第三方庫,生成proguard檔案) |
Exynap | (更加擴充套件,把成型、固定的程式碼段,自動生成) |
MVPHelper | (自動生成 M V P 到不同資料夾) |
2、整合studio不包含的功能:
為了開發的方便,將studio本身不具備的功能引入,擴充套件IDE的功能,避免studio和第三方來回切換和資料傳輸的麻煩。
外掛名稱 | 外掛功能 |
---|---|
EventBus3 Intellij | (輔助 索引eventbus 從subscribe到post,提高eventbus可讀性) |
GradleDependenciesHelperPlugin | (gradle依賴自動補全) |
SQLScout | (除錯sqlite) |
FindBugs-IDEA | (findbugs外掛) |
Android Methods Count | (預覽依賴庫中方法數,提前判斷方法數超限) |
各外掛的開發和使用成熟度很高,大部分是免費並且開源的,活躍度也很高。因為studio的使用率極高,而且IntelliJ IDE本身的外掛資源豐富,直接借鑑的外掛也有很多,使得外掛開發的門檻大大降低。
那麼我們自己再遇到重複性高的工作,或者第三方功能需要嵌入時,也建議考慮外掛的方式。
外掛的安裝方法
1、Preferences - plugins - Browse repositories 查詢jetBrains遠端倉庫上的外掛
很多外掛是免費且開源的(github),遠端repositories上對應的plugins都是最新的release版本
我們可以使用beta版本,或者自己對開源外掛進行二次開發,這時就需要安裝本地外掛:
2、Preferences - plugins - Install plugin from disk 查詢本地plugin的jar包
本文會以一個我自己二次開發的plugin為例,記錄下plugin開發的基本流程和值得注意的坑。
外掛開發
studio是基於IntelliJ的二次開發的IDE,所以plugins其實是IntelliJ的外掛,IntelliJ這個IDE本身就可以開發plugins,IntelliJ下載免費版即可,官網下載,community版本夠用。不再贅述。
新建及import工程
新建project很多文章都有講,不贅述,可以參考: https://www.jianshu.com/p/336a07b9d98a
基本是配置IntelliJ sdk、建立plugin project、然後在plugin.xml中配置此外掛即可
重點說下import工程,如果你是二次開發一個外掛,那麼import一個github已有的工程是必須的。以GsonFormat為例( https://github.com/zzz40500/GsonFormat/ )
github工程下,分為兩個分支master和dev_1.2.2其中dev開發分支可以直接用於二次開發。(master分支直接import作為project,需要IDE配置很多東西)
dev分支工程配置步驟
- import project from existing code
注意我們的工程是plugin,選擇的sdk不是jdk1.8(此處同new project),而是IntelliJ IDEA Community,一路‘下一步’這個過程中,有一步已經把src下程式碼作為module放入了project,生成了GsonFormat.iml, -
只是此時GsonFormat.iml中module type是JAVA_MODULE,而不是PLUGIN_MODULE,需要修改。
(project的module設定很重要,決定了project是否可以正常編譯)
此處配置成功的標誌就是 IDE出現了run 和 debug兩個按鈕。如果還不正常,可以進入IDE右上角的按鈕進入project structure進行配置
配置sdk
-
如果想正常編譯外掛,還需要一步,在 Edit Configuration中配置project的屬性,見圖二,新建一個Plugin Configuration,在右側的Use classpath of module中選擇剛剛的GsonFormat(由於剛剛我們成功配置了GsonFormat為PLUGIN_MODULE,否則此處找不到哦)
配置plugin的Module
- 到了這一步,無論是run debug 還是Prepare Plugin Module For Deployment(產出本地plugin jar)都可以了。
分析plugin工程
import成功後,我們看一下plugin工程是怎樣的?
plugin工程中常見以下三類檔案,也是plugin工程較為特有的檔案型別:
-
Action
作為整個外掛的入口類,其入口方式和name等定義在plugin.xml,Action中actionPerformed作為入口方法,初始化當前類,包,傳入到dialog中
-
Dialog 類似於android中的activity,綁定了Form類,用於view的databinding和邏輯
JsonDialog是入口dialog,FieldsDialog是解析jsonstring後展示的dialog,SettingDialog是配置dialog
-
GUI Form 類似於android中xml佈局檔案,只不過此處是swing的拖拽控制元件,Form與Dialog是配對出現,其對應關係在Form配置。
GsonFormat程式碼架構
以GsonFormat plugin為例,具體講清楚plugin工程的組成和實現原理。
(GsonFormat外掛是把jsonString轉變為javaBean的前端外掛,寫業務程式碼的朋友們應該非常熟悉,這款外掛的使用過程是這樣子的:)
第一步:彈窗:輸入你要轉換的jsonString,此處也可以Setting進行配置

image
第二步:彈窗:展示轉換成功的field class,你可以在此基礎上自定義。

image
最後:我們得到了我們想要的javaBean

image
這個外掛的基本功能如上,下面我們簡單分析下原始碼:
程式碼(類)的組織方式
主Action是MainAction,作為外掛的入口可以看到他啟動了彈窗JsonDialog。工程中維護了幾個dialog(包括java檔案和form表單檔案),分別對應外掛工作中所有的彈窗,被放入了ui資料夾。
[圖片上傳失敗...(image-108cd0-1542190836830)]
再來看其他資料夾:
[圖片上傳失敗...(image-4be0b0-1542190836830)]
- DataWriter類負責GsonFormat最後一步寫入class檔案,
- config資料夾中類維護了外掛的settings屬性(屬性使用者可以在SettingsDialog配置),
- entity資料夾內是實體類,classEntity fieldEntity等類都是維護最終生成class中的field及innerclass的實體類。
- process資料夾內是處理類,jsonstring的解析,javaBean封裝等具體的操作都是在這些類中完成的,是外掛的核心類。
處理流程
處理流程的程式碼邏輯是流式的,從MainAction入口開始看起,在JsonDialog中點選確定後,開始解析jsonString。
類JsonUtilsDialog中,點選事件的響應函式作為入口:
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()方法的實現:
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模式(預設virgo模式):virgo模式,就是啟動FieldsDialog,就是我們見到的第二個視窗,使用者自行修改fields的定義,非virgo比較簡單,跳過dialog直接寫入fields到class裡。除非settings自己定義,否則我們一般都使用virgo模式。可以看到無論是否virgo模式與否,都會呼叫createFields方法,區別只是是否顯示FieldsDialog。
3.詳細看下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中資料 String key = listEntityList.get(i); Object type = json.get(key); FieldEntity fieldEntity = createFiled(parentClass, key, type); fieldEntityList.add(fieldEntity); }
通過createFields方法把field放入FieldEntity,DataWriter再根據FieldEntity的內容寫入class中,
4.以上的解析過程,只涉及了一層JavaBean的情況,JavaBean大部分情況下,是要巢狀Bean內部類的,就是JSONObject內部是巢狀jsonobject的,我們繼續來看:
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依次呼叫createField方法,createFiled方法中呼叫了typeByValue方法,在createInnnerClass中 對子json再次進行createFields方法,如此依次遞迴。完成了一層層javabean的解析工作。
可以說Class中包括FieldEntry,而FieldEntry本身也是包含多個子FieldEntry的。FieldEntry可以設定基本型別,也可以設定ClassEntity。
5.最終通過WriterUtil類將FieldEntry寫入到class檔案中,完成了整個的外掛功能。
二次開發的部分
開發中遇到的問題
由於程式碼混淆的原因,開發中經常遇到debug下正常的程式碼,在release包情況下無法正常解析網路資料,因為javabean類中的field混淆後已經不是原來定義的名稱了。而這個問題在提測關口最容易出現。想解決這個問題必須保證javaBean在打包中不被混淆。
如何不被混淆,不同廠商有不同的解決策略(規範):
- 統一放到一個資料夾裡(或者含固定名稱的資料夾),混淆時ignore 這些資料夾。
但是這辦法操作起來不完美,一個是重構後文件夾容易變名字,還有團隊開發時無法保證所有的人都遵守。處理程式碼時都要繃著資料夾名稱這個弦。 - 所有JavaBean extends Serializable(統一基類)
這樣proguard檔案中保證所有Seriallizable的子類不被混淆即可。而且Bundle傳遞引數時javaBean可以直接被用。但是這個辦法也有一個缺點:需要保證所有人遵守這個約定,無法規範這個步驟。
我們的解決方式:
一般我們的介面管理系統中,都可以產生mock的jsonString,客戶端開發會直接利用SGsonFormat外掛將jsonString直接轉為JavaBean,所以基於GsonFormat功能二次開發,讓所有的JavaBean class統一繼承Serializable,這樣兼顧了易用性和統一性。
GsonFormat的二次開發:
統一繼承Serializable的邏輯,應該放入DataWriter寫入的流程中,分析可得:在ClassProcessor中process方法,實際上將classContent的String內容通過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 { //根據classContent建立class String classContent = "public static class " + classEntity.getClassName() + " implements Serializable" + "{}"; generateClass = factory.createClassFromText(classContent, null).getInnerClasses()[0]; } if (generateClass != null) { //遞迴呼叫,建立內部類 for (ClassEntity innerClass : classEntity.getInnerClasss()) { generateClass(factory, innerClass, generateClass, visitor); } if (!Config.getInstant().isSplitGenerate()) { generateClass = (PsiClass) parentClass.add(generateClass); } //建立內部變數 for (FieldEntity fieldEntity : classEntity.getFields()) { generateField(factory, fieldEntity, generateClass, classEntity); } //建立內部變數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即可使用
總結
二次開發的改動並不大,但是把Studio的plugin開發環境和流程算是熟悉了一遍,plugin外掛的開發可以說你會用java就能上手,只不過他自定義的檔案型別和組織方式需要熟悉。如果有需要的話,做個新的plugin提高工作效率,是個很好的方式。