您的位置:首页 > 移动开发 > Android开发

Android Studio插件开发 MVP框架代码生成插件

2016-12-12 16:33 501 查看

一、概述

在使用Android Studio的时候多少都会使用插件来提高开发效率(偷懒),常用的一些:GsonFormat、ButterKnife等等;因为项目中引入mvp这种架构,写各种类是一件很重复繁琐的事,所以当时就写了这个插件,现在正好结合这篇文章拿出来说一下。这篇文章会教你根据自己的项目结构,生成一个mvp框架代码的插件,看一下运行的效果:



可以看到,只需要输入作者和模块的名称就可以生成一个符合官网版本的mvp框架代码结构,同时会生成base目录。

如果学会了编写插件,这样自己有好创意的时候就可以去实现了,官网有具体的步骤,可以去看一下:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html

废话不多说直接开始。

二、开发环境搭建

开发工具需要IntelliJ IDEA,用过studio都知道它是在IntelliJ IDEA基础上开发的,所以IntelliJ IDEA上手很简单。下载地址:https://www.jetbrains.com/idea/  下载community版本安装即可。开始编写:

1.创建一个plugin项目

File->New->Project 然后选择一个plugin项目,如下图:



点击Next,输入项目的名称即可,这里取名MvpCreate。

建立工程后看一下项目结构:



其中plugin.xml为项目的说明文件,会包含一些项目的版权、作者和声明。其中主要标签为<actions>,这里会是插件的入口。

src目录下存放主要的代码。

2.创建一个菜单

我们知道Android Studio有很多菜单,例如常用的Build菜单,里面有各种各样的功能,同时还有快捷键支持,如下:



现在我们开始建一个自己的菜单,选择src->New->Action,如下:



然后填写这个Action的信息:



说明一下需要填写的属性:

Action ID:代表这个Action的唯一标示。

Class Name:类名

Name:这个插件在菜单上的名称

Description:关于这个插件的描述信息

Groups:代表这个插件会出现的位置。比如想让这个插件出现在Code菜单下的第一次选项,我在图中选择CodeMenu(Code),右边Anchor选择First

Keyboard Shortcuts:快捷键设置。图中设置Alt+T。

点击OK后会生成一个MvpCreateAction类:



同时看一下plugin.xml这个文件,会发现<actions>标签下多出来了一个<action>标签,里面包含了我们刚刚填写的信息。

这样一个插件就生成了。把这个插件部署到studio已经可以使用,具体如何部署接下来会讲,继续往下看。

三、编写插件代码

先说一下思路:

生成代码我们可以使用一些模板,这个模板可以是一个txt文件,在模板里面添加代码,提取出需要替换的代码,然后通过流读取模板文件,最后生成类文件。基本上就是这样的思路,接下来具体实现:

1.新建模板文件

我们按照官网的mvp风格来编写整体的框架,这里按功能模块分包,包含Activity、Fragment、Presenter、Contract,然后加上base类,BasePresenter、BaseView、BaseActivity、BaseFragment。看一下:



看一下TemplateContract.txt这个文件的内部细节:



代码里面的$packagename、$basepackagename、$author、$description、$date、$name这些字符都是可以动态替换的。这些模板文件的具体代码细节在文末的源码中查看,当然也可以根据自己的需要写入自己的代码。

2.新建dialog

上面的模板已经建好,现在我们需要一个对话框,在对话框里面输入作者和模块的名字。效果看一下:



很简单的效果图,和创建Action一样,Dialog可以直接在src下右键New->Dialog取名MyDialog,然后会在src目录下生生成一个MyDialog.java和MyDialog.form的文件,点击这个MyDialog.form文件会进入一个可视化界面,通过拖拽就可以实现一个用户界面:



然后看一下MyDialog.java这个类的代码:

import javax.swing.*;
import java.awt.event.*;

public class MyDialog extends JDialog {
private JPanel contentPane;
private JButton buttonOK;
private JButton buttonCancel;
private JTextField textField1;
private JTextField textField2;

private DialogCallBack mCallBack;

public MyDialog(DialogCallBack callBack) {
this.mCallBack = callBack;
setTitle("Mvp Create Helper");
setContentPane(contentPane);
setModal(true);
getRootPane().setDefaultButton(buttonOK);
setSize(300, 150);
setLocationRelativeTo(null);
buttonOK.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onOK();
}
});

buttonCancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onCancel();
}
});

// call onCancel() when cross is clicked
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
onCancel();
}
});

// call onCancel() on ESCAPE
contentPane.registerKeyboardAction(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onCancel();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
}

private void onOK() {
// add your code here
if (null != mCallBack){
mCallBack.ok(textField1.getText().trim(), textField2.getText().trim());
}
dispose();
}

private void onCancel() {
// add your code here if necessary
dispose();
}

public interface DialogCallBack{
void ok(String author, String moduleName);
}
}
这段代码很简单,最主要的是49行的onOK()方法,在这里添加按钮的回调。这个dialog可以直接使用了。

3.生成代码实现

好了现在去看看MvpCreateAction这个类的具体实现:

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MvpCreateAction extends AnAction {

private Project project;
//包名
private String packageName = "";
private String mAuthor;//作者
private String mModuleName;//模块名称

private enum  CodeType {
Activity, Fragment, Contract, Presenter, BaseView, BasePresenter, MvpBaseActivity, MvpBaseFragment
}

@Override
public void actionPerformed(AnActionEvent e) {
project = e.getData(PlatformDataKeys.PROJECT);
packageName = getPackageName();
init();
refreshProject(e);
}

/**
* 刷新项目
* @param e
*/
private void refreshProject(AnActionEvent e) {
e.getProject().getBaseDir().refresh(false, true);
}

/**
* 初始化Dialog
*/
private void init(){
MyDialog myDialog = new MyDialog(new MyDialog.DialogCallBack() {
@Override
public void ok(String author, String moduleName) {
mAuthor = author;
mModuleName = moduleName;
createClassFiles();
Messages.showInfoMessage(project,"create mvp code success","title");
}
});
myDialog.setVisible(true);

}

/**
* 生成类文件
*/
private void createClassFiles() {
createClassFile(CodeType.Activity);
createClassFile(CodeType.Fragment);
createClassFile(CodeType.Contract);
createClassFile(CodeType.Presenter);
createBaseClassFile(CodeType.BaseView);
createBaseClassFile(CodeType.BasePresenter);
createBaseClassFile(CodeType.MvpBaseActivity);
createBaseClassFile(CodeType.MvpBaseFragment);
}

/**
* 生成base类
* @param codeType
*/
private void createBaseClassFile(CodeType codeType) {
String fileName = "";
String content = "";
String basePath = getAppPath() + "base/";
switch (codeType){
case BaseView:
if (!new File(basePath + "BaseView.java").exists()){
fileName = "TemplateBaseView.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BaseView.java");
}
break;
case BasePresenter:
if (!new File(basePath + "BasePresenter.java").exists()){
fileName = "TemplateBasePresenter.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BasePresenter.java");
}
break;
case MvpBaseActivity:
if (!new File(basePath + "MvpBaseActivity.java").exists()){
fileName = "TemplateMvpBaseActivity.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "MvpBaseActivity.java");
}
break;
case MvpBaseFragment:
if (!new File(basePath + "MvpBaseFragment.java").exists()){
fileName = "TemplateMvpBaseFragment.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "MvpBaseFragment.java");
}
break;
}
}

/**
* 生成mvp框架代码
* @param codeType
*/
private void createClassFile(CodeType codeType) {
String fileName = "";
String content = "";
String appPath = getAppPath();
switch (codeType){
case Activity:
fileName = "TemplateActivity.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Activity.java");
break;
case Fragment:
fileName = "TemplateFragment.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Fragment.java");
break;
case Contract:
fileName = "TemplateContract.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Contract.java");
break;
case Presenter:
fileName = "TemplatePresenter.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, appPath + mModuleName.toLowerCase(), mModuleName + "Presenter.java");
break;
}
}

/**
* 获取包名文件路径
* @return
*/
private String getAppPath(){
String packagePath = packageName.replace(".", "/");
String appPath = project.getBasePath() + "/App/src/main/java/" + packagePath + "/";
return appPath;
}

/**
* 替换模板中字符
* @param content
* @return
*/
private String dealTemplateContent(String content) {
content = content.replace("$name", mModuleName);
if (content.contains("$packagename")){
content = content.replace("$packagename", packageName + "." + mModuleName.toLowerCase());
}
if (content.contains("$basepackagename")){
content = content.replace("$basepackagename", packageName + ".base");
}
content = content.replace("$author", mAuthor);
content = content.replace("$date", getDate());
return content;
}

/**
* 获取当前时间
* @return
*/
public String getDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
String dateString = formatter.format(currentTime);
return dateString;
}

/**
* 读取模板文件中的字符内容
* @param fileName 模板文件名
* @return
*/
private String ReadTemplateFile(String fileName) {
InputStream in = null;
in = this.getClass().getResourceAsStream("/Template/" + fileName);
String content = "";
try {
content = new String(readStream(in));
} catch (IOException e) {
e.printStackTrace();
}
return content;
}

private byte[] readStream(InputStream inputStream) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
try {
while ((len = inputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
outputStream.close();
inputStream.close();
}

return outputStream.toByteArray();
}

/**
* 生成
* @param content 类中的内容
* @param classPath 类文件路径
* @param className 类文件名称
*/
private void writeToFile(String content, String classPath, String className) {
try {
File floder = new File(classPath);
if (!floder.exists()){
floder.mkdirs();
}

File file = new File(classPath + "/" + className);
if (!file.exists()) {
file.createNewFile();
}

FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}

}

/**
* 从AndroidManifest.xml文件中获取当前app的包名
* @return
*/
private String getPackageName() {
String package_name = "";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(project.getBasePath() + "/App/src/main/AndroidManifest.xml");

NodeList nodeList = doc.getElementsByTagName("manifest");
for (int i = 0; i < nodeList.getLength(); i++){
Node node = nodeList.item(i);
Element element = (Element) node;
package_name = element.getAttribute("package");
}

} catch (Exception e) {
e.printStackTrace();
}
return package_name;
}
}
说明一下,首先会获取包名,然后读取模板文件,替换模板文件中动态字符,在Dialog输入的作者和模块名称也会替换模板中字符,最后通过包名路径生成类文件,这是上面代码的一个简述,具体看代码吧。这样一个mvp框架代码生成插件已经完成,接下来看一下如何部署。

四、部署插件

1.填写插件信息

找到plugin.xml文件,填入一些基本信息,如下图:



填写一些插件的信息,像name、version、description...等等,填写的这些信息会在安装插件时,看到它的简介。

然后点击Build->Prepare Plugin Module ...... 如图:



会在项目的根目录生成一个jar包:



拿到这个jar包就可以安装到Android Studio了。

2.安装插件

打开Android Studio,选择File->Setting->Plugins->Install plugin from disk,然后选择刚刚生成的jar包,如图:



安装好了以后重启Studio即可使用。

重启后可以看到在Code菜单下多了MvpHelper这个选项,点击或者快捷键Alt+T调用。



现在调用这个插件去生成代码吧!

3.上传到Plugins仓库

如果你有个好的插件想要分享给你的小伙伴,但每次都要打成jar包发送给需要的人,然后对方需要Setting->Plugins->Install plugin from disk找到本地jar去安装使用。这样感觉太麻烦了,如果直接通过浏览Plugins仓库安装多好。按照下面步骤就可以实现:

官方Plugins仓库地址:https://plugins.jetbrains.com/

找到plugin.xml文件把下面这段代码打开:

<depends>com.intellij.modules.lang</depends>
打开后代表在所有的插件仓库都可以搜到这个插件,即IntelliJ IDEA和Android Studio的仓库均可搜到,要不然只能在IntelliJ IDEA的仓库搜到。

修改完plugin.xml文件后,重新生成jar,然后在仓库官网注册用户,上传插件,填写插件信息,等待审核通过。这样就可以在插件仓库搜索到自己的插件了,如下图:



五、最后

到此本文结束,编写一个插件可以说就三步:

下载IntelliJ IDEA,创建plugin项目;
代码实现;
生成jar包,部署插件;

步骤很简单,主要的是代码实现上比较麻烦,现在可以动脑去编写一些“偷懒”的插件吧。

插件源码:https://github.com/xunzzz/MvpCreatePlugin
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android studio 插件 mvp
相关文章推荐