HarmonyOS开发:插件体系——DevEco插件开发
HarmonyOS开发:插件体系——DevEco插件开发
📌 核心要点:DevEco Studio的插件体系让你能扩展IDE能力,从代码模板到调试工具,写一个插件比你想的简单,但要做好得花心思。
背景与动机
你用DevEco Studio开发HarmonyOS应用,每天重复着同样的操作:创建新页面、写样板代码、配置路由、添加权限声明……一天下来,真正写业务逻辑的时间可能不到一半。
你有没有想过,这些重复操作能不能一键搞定?
能。DevEco Studio基于IntelliJ平台,支持插件扩展。你可以写一个插件,把所有重复操作自动化——一键生成页面模板、自动配置路由、批量添加权限……你自己用着爽,发布到插件市场,别人也能用。
但一提到"插件开发",很多人就犯怵:要学IntelliJ SDK?要写Java/Kotlin?API那么复杂,从哪下手?
别怕。DevEco插件开发的核心套路就那几个:注册扩展点、实现Action、写Panel。这篇文章,我把这些套路拆开揉碎了讲给你。
核心原理
DevEco插件架构
DevEco Studio的插件体系继承自IntelliJ平台,但增加了一些HarmonyOS特有的扩展点。
graph TB
A[DevEco Studio IDE] --> B[插件管理器]
B --> C[你的插件]
C --> D[Action 动作]
C --> E[Tool Window 工具窗口]
C --> F[Code Template 代码模板]
C --> G[Inspection 代码检查]
C --> H[Run Configuration 运行配置]
D --> D1[菜单项]
D --> D2[快捷键]
D --> D3[右键菜单]
E --> E1[侧边栏面板]
E --> E2[底部面板]
F --> F1[文件模板]
F --> F2[Live Template]
G --> G1[语法检查]
G --> G2[性能提示]
classDef ide fill:#E91E63,stroke:#880E4F,color:#fff
classDef plugin fill:#2196F3,stroke:#1565C0,color:#fff
classDef ext fill:#4CAF50,stroke:#2E7D32,color:#fff
classDef detail fill:#FF9800,stroke:#E65100,color:#fff
class A,B ide
class C plugin
class D,E,F,G,H ext
class D1,D2,D3,E1,E2,F1,F2,G1,G2 detail
插件核心概念
| 概念 | 作用 | 你什么时候需要 |
|---|---|---|
| Action | 用户触发的操作(菜单、快捷键) | 添加自定义操作 |
| Tool Window | IDE侧边/底部的面板 | 显示自定义信息面板 |
| Extension Point | IDE预留的扩展接口 | 扩展IDE的特定功能 |
| Service | 后台服务 | 持续运行的后台逻辑 |
| Notification | 通知提示 | 向用户显示信息 |
| Configurable | 设置页面 | 让用户配置插件参数 |
插件项目结构
一个DevEco插件项目的标准结构:
my-devEco-plugin/
├── src/
│ └── main/
│ ├── java/ # Java/Kotlin源码
│ │ └── com/mypackage/
│ │ ├── actions/ # Action实现
│ │ ├── services/ # Service实现
│ │ ├── windows/ # Tool Window实现
│ │ └── utils/ # 工具类
│ └── resources/
│ ├── META-INF/
│ │ └── plugin.xml # 插件描述文件(核心!)
│ └── templates/ # 代码模板
├── build.gradle.kts # 构建配置
└── README.md
plugin.xml——插件的灵魂
plugin.xml是插件的声明文件,IDE通过它知道你的插件提供了哪些扩展:
<idea-plugin>
<!-- 插件基本信息 -->
<id>com.mypackage.harmonyos-helper</id>
<name>HarmonyOS Helper</name>
<version>1.0.0</version>
<vendor>yourname</vendor>
<description>HarmonyOS开发辅助工具</description>
<!-- 兼容的IDE版本 -->
<idea-version since-build="231" until-build="241.*"/>
<!-- 依赖 -->
<depends>com.intellij.modules.platform</depends>
<depends>com.huawei.deveco</depends>
<!-- 扩展点注册 -->
<extensions defaultExtensionNs="com.intellij">
<toolWindow factoryClass="com.mypackage.windows.ApiDocWindow"
id="HarmonyOS API" anchor="right" icon="/icons/api.png"/>
<applicationService serviceImplementation="com.mypackage.services.TemplateService"/>
</extensions>
<!-- Action注册 -->
<actions>
<group id="HarmonyOSHelper.Menu" text="HarmonyOS" description="HarmonyOS辅助工具">
<add-to-group group-id="MainMenu" anchor="last"/>
<action id="CreatePage" class="com.mypackage.actions.CreatePageAction"
text="创建HarmonyOS页面" description="一键生成页面模板">
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift P"/>
</action>
</group>
</actions>
</idea-plugin>
代码实战
基础用法:创建一个代码模板Action
最常见的插件功能——一键生成代码模板。比如你经常要创建ArkUI页面,每次都要写@Entry、@Component、build()方法,烦不烦?让插件帮你搞定。
// CreatePageAction.java - 一键创建ArkUI页面的Action
package com.mypackage.actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.huawei.deveco.ets.lang.EtsFileType;
public class CreatePageAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getProject();
if (project == null) return;
// 弹出对话框,让用户输入页面名称
String pageName = Messages.showInputDialog(
project,
"请输入页面名称(如:UserProfile):",
"创建HarmonyOS页面",
Messages.getQuestionIcon()
);
if (pageName == null || pageName.trim().isEmpty()) return;
// 生成页面代码
String code = generatePageCode(pageName);
// 获取当前选中的目录
PsiDirectory dir = getSelectedDirectory(e);
if (dir == null) {
Messages.showErrorDialog(project, "请先选中一个目录", "错误");
return;
}
// 创建文件
String fileName = pageName + ".ets";
PsiFileFactory factory = PsiFileFactory.getInstance(project);
PsiFile file = factory.createFileFromText(fileName, EtsFileType.INSTANCE, code);
// 检查文件是否已存在
if (dir.findFile(fileName) != null) {
Messages.showErrorDialog(project, "文件已存在: " + fileName, "错误");
return;
}
dir.add(file);
Messages.showInfoMessage(project, "页面创建成功: " + fileName, "提示");
}
// 生成ArkUI页面模板代码
private String generatePageCode(String pageName) {
// 将大驼峰转为下划线命名(用于路由路径)
String routePath = pageName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
return "// " + pageName + " 页面\n" +
"import { router } from '@kit.ArkUI';\n\n" +
"@Entry\n" +
"@Component\n" +
"struct " + pageName + " {\n" +
" // 状态变量\n" +
" @State message: string = '" + pageName + "';\n\n" +
" build() {\n" +
" Column() {\n" +
" // 页面内容\n" +
" Text(this.message)\n" +
" .fontSize(24)\n" +
" .fontWeight(FontWeight.Bold)\n" +
" }\n" +
" .width('100%')\n" +
" .height('100%')\n" +
" }\n" +
"}\n";
}
@Override
public void update(AnActionEvent e) {
// 只在项目打开时启用
e.getPresentation().setEnabledAndVisible(e.getProject() != null);
}
}
进阶用法:Tool Window + Service实现API文档面板
光有Action不够,你还需要一个常驻的面板来显示信息。比如做一个HarmonyOS API速查面板,选中代码中的API名称,面板自动显示文档。
// ApiDocWindow.java - API文档工具窗口
package com.mypackage.windows;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.SelectionListener;
import com.intellij.openapi.editor.event.SelectionEvent;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Map;
public class ApiDocWindow implements ToolWindowFactory {
// API文档数据(实际项目中从文件或网络加载)
private static final Map<String, String> API_DOCS = Map.of(
"Text", "Text组件:显示文本内容。支持fontSize、fontColor、fontWeight等属性。",
"Column", "Column组件:垂直方向布局容器。子组件从上到下排列。",
"Row", "Row组件:水平方向布局容器。子组件从左到右排列。",
"List", "List组件:列表容器。支持懒加载、下拉刷新、上拉加载。",
"Tabs", "Tabs组件:标签页容器。支持顶部/底部标签栏。",
"Navigation", "Navigation组件:导航容器。支持路由栈管理。",
"router", "router模块:页面路由。pushUrl()跳转,back()返回。",
"promptAction", "promptAction模块:弹窗提示。showToast()显示提示。"
);
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
// 创建面板
JBPanel<JBPanel<?>> panel = new JBPanel<>(new BorderLayout());
// 文档显示区域
JBTextArea docArea = new JBTextArea();
docArea.setEditable(false);
docArea.setLineWrap(true);
docArea.setWrapStyleWord(true);
docArea.setText("选中代码中的API名称,此处自动显示文档。");
docArea.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14));
JBScrollPane scrollPane = new JBScrollPane(docArea);
panel.add(scrollPane, BorderLayout.CENTER);
// 搜索框
JTextField searchField = new JTextField();
searchField.setToolTipText("输入API名称搜索文档");
searchField.addActionListener(e -> {
String query = searchField.getText().trim();
String doc = API_DOCS.get(query);
if (doc != null) {
docArea.setText("【" + query + "】\n\n" + doc);
} else {
docArea.setText("未找到API: " + query + "\n\n可查询的API: " +
String.join(", ", API_DOCS.keySet()));
}
});
panel.add(searchField, BorderLayout.NORTH);
// 注册编辑器选中事件监听
project.getMessageBus().connect().subscribe(
com.intellij.openapi.editor.event.SelectionListener.TOPIC,
new SelectionListener() {
@Override
public void selectionChanged(@NotNull SelectionEvent e) {
Editor editor = e.getEditor();
String selectedText = editor.getSelectionModel().getSelectedText();
if (selectedText != null && API_DOCS.containsKey(selectedText)) {
docArea.setText("【" + selectedText + "】\n\n" +
API_DOCS.get(selectedText));
}
}
}
);
// 添加到Tool Window
toolWindow.getComponent().add(panel);
}
}
// TemplateService.java - 模板管理服务
package com.mypackage.services;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
// 持久化服务:保存用户自定义模板
@State(name = "HarmonyOSTemplateSettings",
storages = @Storage("harmonyos-templates.xml"))
public class TemplateService implements PersistentStateComponent<TemplateService.State> {
private State myState = new State();
public static TemplateService getInstance() {
return ApplicationManager.getApplication()
.getService(TemplateService.class);
}
@Nullable
@Override
public State getState() {
return myState;
}
@Override
public void loadState(@NotNull State state) {
myState = state;
}
// 获取所有自定义模板
public List<TemplateItem> getTemplates() {
return myState.templates;
}
// 添加模板
public void addTemplate(TemplateItem item) {
myState.templates.add(item);
}
// 删除模板
public void removeTemplate(int index) {
if (index >= 0 && index < myState.templates.size()) {
myState.templates.remove(index);
}
}
// 状态类
public static class State {
public List<TemplateItem> templates = new ArrayList<>();
}
// 模板项
public static class TemplateItem {
public String name; // 模板名称
public String code; // 模板代码
public String category; // 分类
public TemplateItem() {}
public TemplateItem(String name, String code, String category) {
this.name = name;
this.code = code;
this.category = category;
}
}
}
完整示例:代码检查插件
写一个Inspection插件,检查ArkTS代码中的常见问题。比如:@State变量命名不规范、build()方法里缺少根容器、ForEach没有设置key。
// ArkTSInspection.java - ArkTS代码检查
package com.mypackage.inspections;
import com.intellij.codeInspection.AbstractBaseJavaInspection;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifierList;
import org.jetbrains.annotations.NotNull;
public class ArkTSInspection extends AbstractBaseJavaInspection {
@Override
@NotNull
public String getDisplayName() {
return "ArkTS代码规范检查";
}
@Override
@NotNull
public String getShortName() {
return "ArkTSConvention";
}
@Override
public void buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
// 检查1:@State变量命名规范
// ArkTS规范:@State变量应使用小驼峰命名
// 错误示例:@State Message: string = ''
// 正确示例:@State message: string = ''
// 检查2:build()方法必须有根容器
// 错误示例:build() { Text("hello") } // 缺少Column/Row等容器
// 正确示例:build() { Column() { Text("hello") } }
// 检查3:ForEach必须设置key生成器
// 错误示例:ForEach(this.list, item => Text(item.name))
// 正确示例:ForEach(this.list, item => Text(item.name), item => item.id.toString())
}
}
<!-- plugin.xml中注册Inspection -->
<extensions defaultExtensionNs="com.intellij">
<localInspection
language="ETS"
implementationClass="com.mypackage.inspections.ArkTSInspection"
displayName="ArkTS代码规范检查"
groupName="HarmonyOS"
enabledByDefault="true"
level="WARNING"/>
</extensions>
// PluginConfigurable.java - 插件设置页面
package com.mypackage.settings;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class PluginConfigurable implements Configurable {
private JPanel mainPanel;
private JCheckBox checkStateNaming;
private JCheckBox checkBuildRoot;
private JCheckBox checkForEachKey;
private JTextField templatePathField;
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "HarmonyOS Helper";
}
@Nullable
@Override
public JComponent createComponent() {
mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.anchor = GridBagConstraints.WEST;
// 检查选项
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
JLabel checkLabel = new JLabel("代码检查选项:");
mainPanel.add(checkLabel, gbc);
gbc.gridy = 1; gbc.gridwidth = 1;
checkStateNaming = new JCheckBox("检查@State变量命名规范", true);
mainPanel.add(checkStateNaming, gbc);
gbc.gridy = 2;
checkBuildRoot = new JCheckBox("检查build()方法根容器", true);
mainPanel.add(checkBuildRoot, gbc);
gbc.gridy = 3;
checkForEachKey = new JCheckBox("检查ForEach的key生成器", true);
mainPanel.add(checkForEachKey, gbc);
// 模板路径
gbc.gridy = 4; gbc.gridwidth = 2;
JLabel pathLabel = new JLabel("自定义模板路径:");
mainPanel.add(pathLabel, gbc);
gbc.gridy = 5;
templatePathField = new JTextField(30);
mainPanel.add(templatePathField, gbc);
return mainPanel;
}
@Override
public boolean isModified() {
return true;
}
@Override
public void apply() throws ConfigurationException {
// 保存设置
}
@Override
public void reset() {
// 重置设置
}
}
踩坑与注意事项
坑1:插件兼容性
你用最新版IntelliJ SDK开发的插件,在旧版DevEco Studio上跑不了。DevEco Studio的版本通常比IntelliJ IDEA落后一两个大版本。
解法:在build.gradle.kts里设置合理的兼容范围:
intellij {
version.set("2023.1")
// 不要设太高,DevEco Studio基于特定版本
}
坑2:ETS语言支持有限
DevEco Studio对ETS(ArkTS)的PSI支持不如Java完善。你想用PSI解析ETS文件,发现很多API不支持。
解法:对于复杂的代码分析,用正则表达式或文本匹配代替PSI解析。虽然不够优雅,但管用:
// 用正则匹配代替PSI解析
String content = file.getText();
Pattern pattern = Pattern.compile("@State\\s+(\\w+)\\s*:");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
String varName = matcher.group(1);
// 检查命名规范
}
坑3:插件调试困难
插件运行在IDE进程里,调试不像普通应用那样方便。
解法:用IntelliJ的"Plugin DevKit"插件,它提供了runIde任务,可以启动一个带调试端口的DevEco实例:
# 启动沙盒IDE实例
./gradlew runIde
# 远程调试
./gradlew runIde --debug-jvm
坑4:Action不显示
你注册了一个Action,但菜单里找不到。原因可能是add-to-group的group-id不对,或者update()方法返回了不可见。
解法:先检查plugin.xml里的add-to-group,确保group-id正确。常用的group-id:
MainMenu- 主菜单EditorPopupMenu- 编辑器右键菜单ProjectViewPopupMenu- 项目视图右键菜单NavbarToolBar- 导航栏工具栏
坑5:插件体积过大
你把一堆第三方依赖打进插件包,结果几十MB。IDE加载你的插件时卡半天。
解法:只保留必要的依赖。在build.gradle.kts里配置:
tasks {
buildSearchableOptions {
enabled = false // 如果不需要全文搜索,关掉
}
}
HarmonyOS 6适配说明
HarmonyOS 6对DevEco插件开发的影响:
-
DevEco Studio 5.x升级:基于IntelliJ 2024.1,插件需要适配新的API。部分废弃的API被移除,需要迁移。
-
ArkTS语法增强:新增的类型特性(如类型推断增强)可能影响代码检查插件的规则。需要更新Inspection逻辑。
-
新增扩展点:DevEco Studio 5.x新增了
hvigorTaskProvider扩展点,允许插件注册自定义的hvigor构建任务。 -
性能监控面板:IDE内置了新的性能分析面板,插件可以通过
profilerProvider扩展点贡献自定义的性能指标。
总结
DevEco插件开发的核心就是:找到扩展点、实现接口、注册声明。技术门槛不高,但要做好用户体验——你的插件是给别的开发者用的,他们可没耐心研究你那个反人类的UI。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ IntelliJ SDK学习曲线较陡 |
| 使用频率 | ⭐⭐⭐ 团队规模大时很有价值 |
| 重要程度 | ⭐⭐⭐ 提效工具,非必需但很有用 |
几个关键建议:
- 从Action开始:别一上来就搞Tool Window和Service,先写一个简单的Action,跑通整个流程。
- 参考官方插件:DevEco Studio自带了一些插件,源码在
plugins/目录下,是最好的学习材料。 - 先解决自己的痛点:你每天最烦的重复操作是什么?把它自动化。这样的插件你最有动力维护。
- 发布前测兼容性:至少在两个版本的DevEco Studio上测试,确保不会因为版本差异导致崩溃。
- 点赞
- 收藏
- 关注作者
评论(0)