HarmonyOS开发:插件体系——DevEco插件开发

举报
Jack20 发表于 2026/06/28 21:17:40 2026/06/28
【摘要】 HarmonyOS开发:插件体系——DevEco插件开发📌 核心要点:DevEco Studio的插件体系让你能扩展IDE能力,从代码模板到调试工具,写一个插件比你想的简单,但要做好得花心思。 背景与动机你用DevEco Studio开发HarmonyOS应用,每天重复着同样的操作:创建新页面、写样板代码、配置路由、添加权限声明……一天下来,真正写业务逻辑的时间可能不到一半。你有没有想过...

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@Componentbuild()方法,烦不烦?让插件帮你搞定。

// 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插件开发的影响:

  1. DevEco Studio 5.x升级:基于IntelliJ 2024.1,插件需要适配新的API。部分废弃的API被移除,需要迁移。

  2. ArkTS语法增强:新增的类型特性(如类型推断增强)可能影响代码检查插件的规则。需要更新Inspection逻辑。

  3. 新增扩展点:DevEco Studio 5.x新增了hvigorTaskProvider扩展点,允许插件注册自定义的hvigor构建任务。

  4. 性能监控面板:IDE内置了新的性能分析面板,插件可以通过profilerProvider扩展点贡献自定义的性能指标。

总结

DevEco插件开发的核心就是:找到扩展点、实现接口、注册声明。技术门槛不高,但要做好用户体验——你的插件是给别的开发者用的,他们可没耐心研究你那个反人类的UI。

维度 评价
学习难度 ⭐⭐⭐⭐ IntelliJ SDK学习曲线较陡
使用频率 ⭐⭐⭐ 团队规模大时很有价值
重要程度 ⭐⭐⭐ 提效工具,非必需但很有用

几个关键建议:

  • 从Action开始:别一上来就搞Tool Window和Service,先写一个简单的Action,跑通整个流程。
  • 参考官方插件:DevEco Studio自带了一些插件,源码在plugins/目录下,是最好的学习材料。
  • 先解决自己的痛点:你每天最烦的重复操作是什么?把它自动化。这样的插件你最有动力维护。
  • 发布前测兼容性:至少在两个版本的DevEco Studio上测试,确保不会因为版本差异导致崩溃。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。