AndroidStudio外掛GsonFormat解析及二次開發

challengerwang發表於2018-11-15

AndroidStudio外掛GsonFormat解析及二次開發

Studio主流外掛

我們知道Android Studio是基於Intellij的一套IDE環境,Intellij本身為開發者提供了外掛式的開發環境,大大提高了開發效率和IDE可配置化。目前studio的成熟外掛已有很多。 我們這裡先來看看目前已有的主流Studio外掛有哪些,幾乎已經涵蓋了你所有的需求: juejin.im/entry/59980…

基本可以把外掛的功能分為以下幾類:

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很多文章都有講,不贅述,可以參考:www.jianshu.com/p/336a07b9d… 基本是配置IntelliJ sdk、建立plugin project、然後在plugin.xml中配置此外掛即可

重點說下import工程,如果你是二次開發一個外掛,那麼import一個github已有的工程是必須的。以GsonFormat為例(github.com/zzz40500/Gs… github工程下,分為兩個分支master和dev_1.2.2其中dev開發分支可以直接用於二次開發。(master分支直接import作為project,需要IDE配置很多東西)

dev分支工程配置步驟

  1. import project from existing code 注意我們的工程是plugin,選擇的sdk不是jdk1.8(此處同new project),而是IntelliJ IDEA Community,一路‘下一步’這個過程中,有一步已經把src下程式碼作為module放入了project,生成了GsonFormat.iml,
  2. 只是此時GsonFormat.iml中module type是JAVA_MODULE,而不是PLUGIN_MODULE,需要修改。 (project的module設定很重要,決定了project是否可以正常編譯) 此處配置成功的標誌就是 IDE出現了run 和 debug兩個按鈕。如果還不正常,可以進入IDE右上角的按鈕進入project structure進行配置
    配置sdk
  3. 如果想正常編譯外掛,還需要一步,在 Edit Configuration中配置project的屬性,見圖二,新建一個Plugin Configuration,在右側的Use classpath of module中選擇剛剛的GsonFormat(由於剛剛我們成功配置了GsonFormat為PLUGIN_MODULE,否則此處找不到哦)
    配置plugin的Module
  4. 到了這一步,無論是run debug 還是Prepare Plugin Module For Deployment(產出本地plugin jar)都可以了。

分析plugin工程

import成功後,我們看一下plugin工程是怎樣的? plugin工程中常見以下三類檔案,也是plugin工程較為特有的檔案型別:

  1. Action 作為整個外掛的入口類,其入口方式和name等定義在plugin.xml,Action中actionPerformed作為入口方法,初始化當前類,包,傳入到dialog中

  2. Dialog 類似於android中的activity,繫結了Form類,用於view的databinding和邏輯 JsonDialog是入口dialog,FieldsDialog是解析jsonstring後展示的dialog,SettingDialog是配置dialog

  3. GUI Form 類似於android中xml佈局檔案,只不過此處是swing的拖拽控制元件,Form與Dialog是配對出現,其對應關係在Form配置。

GsonFormat程式碼架構

以GsonFormat plugin為例,具體講清楚plugin工程的組成和實現原理。 (GsonFormat外掛是把jsonString轉變為javaBean的前端外掛,寫業務程式碼的朋友們應該非常熟悉,這款外掛的使用過程是這樣子的:) 第一步:彈窗:輸入你要轉換的jsonString,此處也可以Setting進行配置

AndroidStudio外掛GsonFormat解析及二次開發
第二步:彈窗:展示轉換成功的field class,你可以在此基礎上自定義。
AndroidStudio外掛GsonFormat解析及二次開發
最後:我們得到了我們想要的javaBean
AndroidStudio外掛GsonFormat解析及二次開發
這個外掛的基本功能如上,下面我們簡單分析下原始碼:

程式碼(類)的組織方式

主Action是MainAction,作為外掛的入口可以看到他啟動了彈窗JsonDialog。工程中維護了幾個dialog(包括java檔案和form表單檔案),分別對應外掛工作中所有的彈窗,被放入了ui資料夾。

AndroidStudio外掛GsonFormat解析及二次開發
再來看其他資料夾:
AndroidStudio外掛GsonFormat解析及二次開發

  1. DataWriter類負責GsonFormat最後一步寫入class檔案,
  2. config資料夾中類維護了外掛的settings屬性(屬性使用者可以在SettingsDialog配置),
  3. entity資料夾內是實體類,classEntity fieldEntity等類都是維護最終生成class中的field及innerclass的實體類。
  4. 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在打包中不被混淆。 如何不被混淆,不同廠商有不同的解決策略(規範):

  1. 統一放到一個資料夾裡(或者含固定名稱的資料夾),混淆時ignore 這些資料夾。 但是這辦法操作起來不完美,一個是重構後資料夾容易變名字,還有團隊開發時無法保證所有的人都遵守。處理程式碼時都要繃著資料夾名稱這個弦。
  2. 所有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) plugins.jetbrains.com/plugin/1110… 或者直接搜尋SGsonFormat,install即可使用

總結

二次開發的改動並不大,但是把Studio的plugin開發環境和流程算是熟悉了一遍,plugin外掛的開發可以說你會用java就能上手,只不過他自定義的檔案型別和組織方式需要熟悉。如果有需要的話,做個新的plugin提高工作效率,是個很好的方式。

相關文章