簡介
之前一篇介紹了如何去開發(fā)一個簡單的AnroidStudio插件,這一篇詳細講解如何去做一個生成MVP中我們經(jīng)常重復(fù)用到的通用代碼插件艰争。
要求
1.根據(jù)定義好的Contract.java文件生成對應(yīng)的Model.java文件,Presenter.java文件灌具,以及構(gòu)造Contract內(nèi)部通用代碼
2.生成的代碼可以被git作為新生成的文件
一:實現(xiàn)Contract內(nèi)部代碼
首先完成Contract內(nèi)部代碼的編寫卧波,這里需要了解插件開發(fā)的相關(guān)API,參考:API
我的mvp是基于T-MVP利用泛型去實現(xiàn)淆游,所以需要用到Model,View,Presenter,另外還有一個用于存放這幾個接口Contract契約類,結(jié)構(gòu)如下:
至于MVP架構(gòu)如何設(shè)計隔盛,網(wǎng)上框架多多犹菱,這里主要分析如何去編寫插件,老規(guī)矩吮炕,先新建項目MVPDataBind,t填好常用的幾個參數(shù)腊脱,如下:
接著新建一個Action動作,然后在actionPerformed方法中編寫龙亲,主要實現(xiàn)思路是我們要通過當(dāng)前獲取到的文件得到文件類名陕凹,判斷文件名是否符合規(guī)則,然后通過Document改變當(dāng)前文件內(nèi)容:
網(wǎng)上對于Document介紹:Document其實就是Virtual File的內(nèi)容的字符序列鳄炉,所以對Document的各種操作都是基于普通文本的杜耙。Document的可操作的方法并不多,主要是Document是基于字符序列的拂盯,操作起來難度有點大佑女。事實上我們對Document的使用也比較少,通常都是一些信息的簡單獲取谈竿。
代碼如下:
private void createContractContent() {
int lastIndex = this.content.lastIndexOf("}");
this.content = this.content.substring(0, lastIndex);
MessagesCenter.showDebugMessage(this.content, "debug");
final String content = this.getContractContent();
WriteCommandAction.runWriteCommandAction(this.editor.getProject(), new Runnable() {
public void run() {
MVPDataBind.this.editor.getDocument().setText(content);
}
});
}
private String getContractContent() {
return this.content.trim() + "\n\n interface View extends BaseView {\n \n }\n\n interface Model extends BaseModel {\n \n }\n\n abstract class Presenter extends BasePresenter<Model, View> {\n \n }\n\n}";
}
然后是我們要先判斷操作的當(dāng)前文件是否合法,命名必須要xxxContract.java:
private boolean checkCanUse() {
this.content = this.editor.getDocument().getText();
String[] words = this.content.split(" ");
String[] var2 = words;
int var3 = words.length;
for (int var4 = 0; var4 < var3; ++var4) {
String word = var2[var4];
if (word.contains("Contract")) {
String className = word.substring(0, word.indexOf("Contract"));
this.classModel.setFunctionName(className);
this.classModel.setClassName(word);
MessagesCenter.showDebugMessage(className, "class name");
}
}
if (TextUtils.isEmpty(this.classModel.getFunctionName())) {
Messages.showErrorDialog(
"文件名定義不規(guī)范",
"Error");
// MessagesCenter.showErrorMessage("Create failed ,Can't found 'Contract' in your class name,your class name must contain 'Contract'", "error");
return false;
} else {
return true;
}
}
如果只是Contract团驱,則會彈框報錯:
到此,我們就實現(xiàn)了編寫Contract文件里面的內(nèi)容空凸,這里要介紹一下
AnActionEvent
AnActionEvent 函數(shù)和update函數(shù)的形參都包含AnActionEvent對象嚎花。AnActionEvent對象是我們與IntelliJ IDEA交互的橋梁,我們可以通過AnActionEvent對象獲取當(dāng)前IntelliJ IDEA的各個模塊對象呀洲,如編輯框窗口對象紊选、項目窗口對象等啼止,獲取到這些對象我們就可以做一些定制的效果。
通過AnActionEvent對象的getData函數(shù)可以得到IDEA界面各個窗口對象以及各個窗口為實現(xiàn)某些特定功能的對象丛楚。getData函數(shù)需要傳入DataKey<T>對象族壳,用于指明想要獲取的IDEA中的哪個對象。在CommonDataKeys已經(jīng)定義好各個IDEA對象對應(yīng)的DataKey<T>對象趣些,不僅僅CommonDataKeys中定義了DataKey<T>對象,為了添加更多的DataKey<T>對象并且兼容等贰您,又提供了PlatformDataKeys類坏平,PlatformDataKeys類是CommonDataKeys子類,也就是說锦亦,只要是CommonDataKeys有的舶替,PlatformDataKeys類都有。這里我們通過getData獲取到當(dāng)前的編輯框用于操作編輯框的內(nèi)容:
this.editor = (Editor) e.getData(PlatformDataKeys.EDITOR);
二.實現(xiàn)新增文件
Contract文件內(nèi)容編輯好了杠园,接下來就是要通過插件顾瞪,新建文件了,插件開發(fā)中, 使用PSI Files來表示具體操作的文件.參考:http://www.reibang.com/p/0117d4b1eb00
對于Java來說, 可以通過JavaDirectoryService#createClass(dir, fileName)來創(chuàng)建類文件, 同時會返回一個PsiClass實例表示該類.然后我們定義接口回調(diào)抛蚁,執(zhí)行后續(xù)的類實現(xiàn)和繼承等操作陈醒,如下:
public void generateFile(final PsiDirectory directory, final String fileName, final String type, final onFileGeneratedListener listener) {
WriteCommandAction.runWriteCommandAction(myProject, new Runnable() {
@Override
public void run() {
PsiClass[] psiClasses = myShortNamesCache.getClassesByName(fileName, myProjectScope);//NotNull
PsiClass psiClass;
PsiJavaFile javaFile;
if (psiClasses.length != 0) {//if the class already exist.
psiClass = psiClasses[0];
javaFile = (PsiJavaFile) psiClass.getContainingFile();
javaFile.delete();//then delete the old one
}//and re-generate one
psiClass = myDirectoryService.createClass(directory, fileName, type);
javaFile = (PsiJavaFile) psiClass.getContainingFile();
PsiPackage psiPackage = myDirectoryService.getPackage(directory);
javaFile.setPackageName(psiPackage.getQualifiedName());
listener.onJavaFileGenerated(javaFile, psiClass);
}
});
這里是生成xxxModel文件的操作,先通過myDirectoryService.createClass(directory, fileName, type)創(chuàng)建類文件瞧甩,然后通過查詢之前定義得xxxContract得到需要實現(xiàn)的Model接口名钉跷,最后用add方法加入到新建的類里面,最后通過setModifierProperty設(shè)置類的修飾符為public肚逸,生成Presenter代碼類似爷辙,把實現(xiàn)接口變?yōu)槔^承Presenter子類,代碼如下:
fileGenerator.generateFile(myCurrentDir, className + "Model", JavaTemplateUtil.INTERNAL_CLASS_TEMPLATE_NAME, new FileGenerator.onFileGeneratedListener() {
@Override
public void onJavaFileGenerated(PsiJavaFile javaFile, PsiClass psiClass) {
PsiClass contractClass = fileGenerator.myShortNamesCache.getClassesByName(fileGenerator.myPrefix + "Contract", fileGenerator.myProjectScope)[0];
PsiClass model = contractClass.findInnerClassByName("Model", false);//don't need to search base
psiClass.getImplementsList().add(fileGenerator.myFactory.createClassReferenceElement(model));
psiClass.getModifierList().setModifierProperty("public", true);//force 'public interface myPrefixContract'
}
});
fileGenerator.generateFile(myCurrentDir, className + "Presenter", JavaTemplateUtil.INTERNAL_CLASS_TEMPLATE_NAME, new FileGenerator.onFileGeneratedListener() {
@Override
public void onJavaFileGenerated(PsiJavaFile javaFile, PsiClass psiClass) {
PsiClass contractClass = fileGenerator.myShortNamesCache.getClassesByName(fileGenerator.myPrefix + "Contract", fileGenerator.myProjectScope)[0];
PsiClass model = contractClass.findInnerClassByName("Presenter", false);//don't need to search base
psiClass.getExtendsList().add(fileGenerator.myFactory.createClassReferenceElement(model));
psiClass.getModifierList().setModifierProperty("public", true);//force 'public interface myPrefixContract'
}
});
文件創(chuàng)建完成之后編譯打成jar包朦促,導(dǎo)入AndroidStudio測試效果如下:
基本達到了我們的需求
總結(jié):
對于MVP架構(gòu)膝晾,插件生成我們的常用契約類,對開發(fā)效率還是有一定的提升务冕,免去了我們重復(fù)建類血当,命名的問題,不過文件的讀寫,創(chuàng)建只是插件開發(fā)的一點點洒疚,要想熟練插件開發(fā)歹颓,還需要閱讀官方文檔Api,多參考別人的插件開發(fā)實現(xiàn)原理油湖,最后附上插件源碼github