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分支工程配置步驟
- 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進行配置
- 如果想正常編譯外掛,還需要一步,在 Edit Configuration中配置project的屬性,見圖二,新建一個Plugin Configuration,在右側的Use classpath of module中選擇剛剛的GsonFormat(由於剛剛我們成功配置了GsonFormat為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進行配置
第二步:彈窗:展示轉換成功的field class,你可以在此基礎上自定義。 最後:我們得到了我們想要的javaBean 這個外掛的基本功能如上,下面我們簡單分析下原始碼:程式碼(類)的組織方式
主Action是MainAction,作為外掛的入口可以看到他啟動了彈窗JsonDialog。工程中維護了幾個dialog(包括java檔案和form表單檔案),分別對應外掛工作中所有的彈窗,被放入了ui資料夾。
再來看其他資料夾:- 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) plugins.jetbrains.com/plugin/1110… 或者直接搜尋SGsonFormat,install即可使用
總結
二次開發的改動並不大,但是把Studio的plugin開發環境和流程算是熟悉了一遍,plugin外掛的開發可以說你會用java就能上手,只不過他自定義的檔案型別和組織方式需要熟悉。如果有需要的話,做個新的plugin提高工作效率,是個很好的方式。