AndroidStudio插件GsonFormat解析及二次開發

Studio主流插件

咱們知道Android Studio是基於Intellij的一套IDE環境,Intellij自己爲開發者提供了插件式的開發環境,大大提升了開發效率和IDE可配置化。目前studio的成熟插件已有不少。 咱們這裏先來看看目前已有的主流Studio插件有哪些,幾乎已經涵蓋了你全部的需求: juejin.im/entry/59980…前端

基本能夠把插件的功能分爲如下幾類:

一、解決重複性工做:

把studio工做中技術含量低,重複性高的工做,用插件形式代替。java

插件名稱 插件功能
GsonFormat (jsonString自動生成JavaBean類)
ButterKnife Zelezny (xml自動生成butterknife的註解代碼)
Code Generator (xml自動生成activity fragment)
AndroidProguardPlugin (根據依賴的第三方庫,生成proguard文件)
Exynap (更加擴展,把成型、固定的代碼段,自動生成)
MVPHelper (自動生成 M V P 到不一樣文件夾)
二、集成studio不包含的功能:

爲了開發的方便,將studio自己不具有的功能引入,擴展IDE的功能,避免studio和第三方來回切換和數據傳輸的麻煩。node

插件名稱 插件功能
EventBus3 Intellij (輔助 索引eventbus 從subscribe到post,提升eventbus可讀性)
GradleDependenciesHelperPlugin (gradle依賴自動補全)
SQLScout (調試sqlite)
FindBugs-IDEA (findbugs插件)
Android Methods Count (預覽依賴庫中方法數,提早判斷方法數超限)

各插件的開發和使用成熟度很高,大部分是免費而且開源的,活躍度也很高。由於studio的使用率極高,並且IntelliJ IDE自己的插件資源豐富,直接借鑑的插件也有不少,使得插件開發的門檻大大下降。 那麼咱們本身再遇到重複性高的工做,或者第三方功能須要嵌入時,也建議考慮插件的方式。android

插件的安裝方法

一、Preferences - plugins - Browse repositories 查找jetBrains遠程倉庫上的插件 不少插件是免費且開源的(github),遠程repositories上對應的plugins都是最新的release版本 咱們可使用beta版本,或者本身對開源插件進行二次開發,這時就須要安裝本地插件:git

二、Preferences - plugins - Install plugin from disk 查找本地plugin的jar包github

本文會以一個我本身二次開發的plugin爲例,記錄下plugin開發的基本流程和值得注意的坑。sql


插件開發

studio是基於IntelliJ的二次開發的IDE,因此plugins實際上是IntelliJ的插件,IntelliJ這個IDE自己就能夠開發plugins,IntelliJ下載免費版便可,官網下載,community版本夠用。再也不贅述。json

新建及import工程

新建project不少文章都有講,不贅述,能夠參考:www.jianshu.com/p/336a07b9d… 基本是配置IntelliJ sdk、建立plugin project、而後在plugin.xml中配置此插件便可bash

重點說下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進行配置

第二步:彈窗:展現轉換成功的field class,你能夠在此基礎上自定義。
最後:咱們獲得了咱們想要的javaBean
這個插件的基本功能如上,下面咱們簡單分析下源碼:

代碼(類)的組織方式

主Action是MainAction,做爲插件的入口能夠看到他啓動了彈窗JsonDialog。工程中維護了幾個dialog(包括java文件和form表單文件),分別對應插件工做中全部的彈窗,被放入了ui文件夾。

再來看其餘文件夾:

  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提升工做效率,是個很好的方式。

相關文章
相關標籤/搜索