简介
之前一篇介绍了如何去开发一个简单的AnroidStudio插件,这一篇详细讲解如何去做一个生成MVP中我们经常重复用到的通用代码插件。
要求
1.根据定义好的Contract.java文件生成对应的Model.java文件,Presenter.java文件,以及构造Contract内部通用代码
2.生成的代码可以被git作为新生成的文件
一:实现Contract内部代码
首先完成Contract内部代码的编写,这里需要了解插件开发的相关API,参考:API
我的mvp是基于T-MVP利用泛型去实现,所以需要用到Model,View,Presenter,另外还有一个用于存放这几个接口Contract契约类,结构如下:
至于MVP架构如何设计,网上框架多多,这里主要分析如何去编写插件,老规矩,先新建项目MVPDataBind,t填好常用的几个参数,如下:
接着新建一个Action动作,然后在actionPerformed方法中编写,主要实现思路是我们要通过当前获取到的文件得到文件类名,判断文件名是否符合规则,然后通过Document改变当前文件内容:
网上对于Document介绍:Document其实就是Virtual File的内容的字符序列,所以对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 {\n \n }\n\n}";
}
然后是我们要先判断操作的当前文件是否合法,命名必须要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(
"文件名定义不规范",
"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,则会弹框报错:
到此,我们就实现了编写Contract文件里面的内容,这里要介绍一下
AnActionEvent
AnActionEvent 函数和update函数的形参都包含AnActionEvent对象。AnActionEvent对象是我们与IntelliJ IDEA交互的桥梁,我们可以通过AnActionEvent对象获取当前IntelliJ IDEA的各个模块对象,如编辑框窗口对象、项目窗口对象等,获取到这些对象我们就可以做一些定制的效果。
通过AnActionEvent对象的getData函数可以得到IDEA界面各个窗口对象以及各个窗口为实现某些特定功能的对象。getData函数需要传入DataKey
this.editor = (Editor) e.getData(PlatformDataKeys.EDITOR);
二.实现新增文件
Contract文件内容编辑好了,接下来就是要通过插件,新建文件了,插件开发中, 使用PSI Files来表示具体操作的文件.参考:https://www.jianshu.com/p/0117d4b1eb00
对于Java来说, 可以通过JavaDirectoryService#createClass(dir, fileName)来创建类文件, 同时会返回一个PsiClass实例表示该类.然后我们定义接口回调,执行后续的类实现和继承等操作,如下:
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)创建类文件,然后通过查询之前定义得xxxContract得到需要实现的Model接口名,最后用add方法加入到新建的类里面,最后通过setModifierProperty设置类的修饰符为public,生成Presenter代码类似,把实现接口变为继承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'
}
});
文件创建完成之后编译打成jar包,导入AndroidStudio测试效果如下:
基本达到了我们的需求
总结:
对于MVP架构,插件生成我们的常用契约类,对开发效率还是有一定的提升,免去了我们重复建类,命名的问题,不过文件的读写,创建只是插件开发的一点点,要想熟练插件开发,还需要阅读官方文档Api,多参考别人的插件开发实现原理,最后附上插件源码github