写点什么

Android Studio IDE 插件开发

发布于: 刚刚
Android Studio IDE 插件开发


作者:字节跳动终端技术——周宸韬

概述

这篇文章旨在向读者介绍 IntelliJ IDE 插件的开发流程以及常用的一些通用功能,任何基于 IntelliJ 开发的 IDE 都可以通过该方式制作插件,例如 Android Studio(AS),本篇也将基于 Android Studio 进行展开介绍,读者将从 0 到 1 学习到 Android Studio 插件开发。

背景介绍

什么是 IDE 插件、IDE 插件能做什么?

IDE 插件是将一些功能集成到了 IDE 界面当中,当我们使用 IDE 进行开发工作时能很方便的通过 UI 界面使用这些功能,例如大家熟悉的 project 工程目录,Gradle 工具栏,IDE 底部的 Run、Terminal、Build 界面等,都是通过 IDE 插件来实现的,可以说大部分需要通过命令行执行、或用户手动的一些操作都可以通过插件实现,并以 UI 的形式呈现。


如下图:左图为 Android Studio IDE 界面右侧 Gradle 工具栏,包含了很多 Gradle 任务,点击 UI 的效果等同于用户在命令行中输入 Gradle 命令。右图为 IDE 顶部菜单栏版本控制部分,其中对于版本的提交、拉取等按钮等价于命令行输入对应指令。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin1.png" width="300"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin2.png" width="300"/> </center>

为什么要做 Android Studio IDE 插件?

笔者作为中台部门开发者,经常涉及到一些通用的功能的开发,并以工具或组件等形式交由外部使用。所以如何降低用户学习成本、提高工作效率是我的目标,而这些优化方向都离不开巧妙的使用工具。例如本次要介绍的 IDE 插件的开发背景就是以此为目标:将原本需要使用命令行完成的工作、或者学习成本较高的操作通过 UI 进行包装,并且依附在原生的 AS 界面中,通过 UI 的交互大幅降低用户学习成本,同时提升使用体验。


举例对比一下,下面两幅图片是某个工程自动搭建功能的截图,左右两图分别为使用命令行和使用 AS 插件的体验对比,可以看到左侧在使用 CLI 命令行进行工程搭建时界面信息不够简洁明了,且用户交互体验较差,用户必须在使用前阅读文档,并且没有容错机制,输错就得从头开始。而相同的功能使用右侧 AS 插件的体验则好很多,不仅各条信息清楚明了,还能拓展更多细节功能,如动态检验用户输入,输入无误才可进行下一步等等,用户完全可以在零知识背景的情况下使用该插件并轻松完成所有功能操作,而且接近原生的界面更美观。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin3.png" width="400"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin4.png" width="400"/> </center>

如何开发一个 IDE 插件?

准备工作

在开发第一个插件前,我们要下载正确的开发工具,在 JetBrain 官网中下载IntelliJ IDEA下载链接


这里使用的开发工具是 IntelliJ IDEA 而不是 Android Studio,因为 AS 是基于 IntelliJ 为模版开发的,IDE 插件必须通过 IntelliJ 开发、发布,再安装到 Android Studio 中才能使用。


我们需要确认我们使用的 Android Studio 是基于哪个 IntelliJ 版本。这很重要,和你当前使用的 Android Studio 版本相同能让你在调试时很方便,而新版的 IntelliJ 包含的 features 在你使用的 AS 上可能并没有,导致插件无法安装,或提示兼容性报错。(图中的报错也会出现在未开启向高版本兼容时发生,开发时按需开启) 。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin5.png" width="400"/></center>


下载时请跟随这个步骤:


  1. 打开你的 Android Studio,查看版本号(The Build Number),这就是我们需要的 IntelliJ 版本号。

  2. 下载页面,点击 Other versions,找到对应的 IntelliJ 版本,下载安装即可。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin6.png" width="300"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin7.png" width="500"/> </center>

创建新的工程 + 配置

这部分也可参照官网:https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html


  1. 创建新工程包含两个向导页面,【选择工程模版框架 + 填写插件工程信息】,按图中配置即可。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin8.png" width="350"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin9.png" width="350"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin10.png" width="100" hight="300"/> </center>


  1. 完成两步向导程序后自动创建工程,我们需要先了解两个核心文件:【build.gradle + plugin.xml】,并做一些前置的配置工作。

build.gradle

因为 build.gradle 和 Android 工程中的构建文件非常类似,这里只解释 Android 中没有的配置。


  • version:intellij 闭包创建时只带一个属性 version,该属性代表用来构建这个插件的 IntelliJ 平台 IDE 的版本,如果我们在开发时调用【runIde】这个 Gradle task,一个基于这个版本的 IntelliJ IDE 实例就会被创建。

  • localPath:因为我们希望在 AS 的环境下测试我们的插件,所以我们需要将 AS 作为我们插件的一个依赖,增加一个属性叫 localPath 指定本机 Android Studio 应用程序 Contents 的安装目录,一个基于这个版本的 Android Studio 实例就会被创建(注意 localPath 不能和 version 属性同时使用,因为我们本地的 AS 路径中已经有了版本信息)。

  • plugins:添加开发需要的依赖插件。可以在这里添加很多我们想用的插件,比如我们想在插件中执行 git 命令,我们可以添加 ’git4idea‘ plugin。


intellij {    version '2020.1.4'    localPath '/Applications/Android Studio.app/Contents'    plugins = ['Kotlin','android','git4idea']}
复制代码

plugin.xml

在 resource 文件夹下可以找到 plugin.xml 文件,这个文件中可以配置我们插件的各项属性,核心功能是注册我们插件包含的 components 和 service(功能类实现后还需要在这里进行注册才能使用,类似在 AndroidManifest.xml 中声明 Activity 和 Service)。


  • 声明我们的插件需要并且和 AS 相兼容:增加 android 和 android studio modules 作为依赖。


<idea-plugin>    ...    <depends>com.intellij.modules.platform</depends>    <depends>org.jetbrains.android</depends>    <depends>com.intellij.modules.androidstudio</depends>
<extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions>
<actions> <!-- Add your actions here --> </actions>
</idea-plugin>
复制代码

运行插件

配置完成后我们可以尝试运行插件工程,具体位置在 Gradle 工具栏 项目名称/Tasks/intelliJ/runIde路径。运行runIde任务,因为我们配置了 Android Studio 为启动路径,所以一个 Android Studio 模拟 IDE 会打开,所有内容都和我们本地的 Android Studio 没有差别。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin11.png" width="270"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin12.png" width="670"/> </center>

IDE 插件常用功能介绍

创建一个 Action

什么是 Action?

Actions 官方介绍: The system of actions allows plugins to add their own items to IDEA menus and toolbars. An action is a class, derived from the AnAction.

Actions 是用户调用插件功能最常见的方式,如下图的工具目录是开发者经常用到的,里面所有的可选项都是一个 Action,可以进一步展开的则是 Action Group。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin13.png" width="500"/> </center>

如何创建一个 Action?

两个步骤:


  • 【code implementation - 实现 Action 的具体代码逻辑】:决定了这个 action 在哪个 context 下有效,并且在 UI 中被选择后的功能(继承父类 AnAction 并重写 actionPerformed()方法,用于 Action 被执行后的回调)。

  • 【registered - 在配置文件中注册】:决定了这个 action 在 IDE 界面的哪个位置出现(创建新的 group 或存放进现有的 ActionGroup,以及在 group 中的位置)。


两个条件达成,action 就可以从 IntelliJ Platform 中获得用户执行动作后的回调,例子: ‘HelloWorld’。

Code implementation

class HelloWorldAction : AnAction() {
override fun actionPerformed(event: AnActionEvent) { //这里创建了一个消息提示弹窗,在IDE中展示“Hello World” val notificationGroup = NotificationGroup( displayId = "myActionId", displayType = NotificationDisplayType.BALLOON )
val notification = notificationGroup.createNotification( title = "chentao Demo", content = "Hello World", type = NotificationType.INFORMATION ).notify(event.project) //从方法的Event对象中获取到当前IDE正在展示的project,在该project中展示弹窗 }}
复制代码

Registering a Custom Action

<actions>    <!-- Add your actions here -->    <!-- 创建了一个ActionGroup -->    <group id = "ChentaoDemo.TopMenu"           text="ChentaoDemo Plugin"           description="Demo Plugin in top menu">           <!-- 注册HelloWorld Action -->        <action class="com.chentao.demo.actions.HelloWorldAction"                id="DemoAction"                text="Hello World Action"                description="This is a test action">                <!-- 设置 HelloWorld Action 的键盘快捷键-->            <keyboard-shortcut first-keystroke="control alt p" keymap="$default"/>            <!-- 将HelloWorld Action添加到剪切拷贝组中 -->            <add-to-group group-id="CutCopyPasteGroup" anchor="last"/>                  </action>        <!-- 将这个Group添加到主菜单 -->        <add-to-group group-id="MainMenu" anchor="last"/>    </group></actions>
复制代码

运行插件 - 结果展示

实现了以上两步后,运行runIde Task,顶部的主菜单栏末尾出现了我们添加的 ActionGroup,展开可看见 HelloWorldAction,点击 Action,右下角弹出“Hello World”提示信息。我们不仅可以创建 Group 来放置 Action,还可以将 Action 添加进 IDE 已有的 Group 当中,如下左图中,我们将 HelloWorld Action 添加进了 IDE 的 CutCopyPasteGroup,和复制粘贴等 Action 放在了一起。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin14.png" width="800"/> </center>


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin15.png" width="400"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin16.png" width="400"/> </center>


plugin.xml 文件中 actions 的 group 可以更为复杂,group 可以相互包含,并形成工具栏或菜单(如下图),有兴趣的同学可以拉取 Demo(文章末尾)体验一下。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin17.png" width="800"/> </center>

向导程序 Wizard

Wizard 意为向导程序,就是指引使用者完成某个功能的程序,通常为单个或多个指引界面组成。例如下面两幅图为 Android Studio 中经典的创建新工程窗口,就包含两个页面的向导程序。下面将介绍如何制作出和图中主题完全相同的向导程序。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin18.png" width="480"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin19.png" width="480"/> </center>


向导程序的基础类属于 android.jar,由以下几个核心类构成:


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin20.png" width="300"/> </center>

1. ModelWizard

向导程序的“主类”,一个 ModelWizard 包含了一个 ModelWizardStep 的队列(Step 队列是一个有序的队列,并且每个 Step 都包含了它的下一个 Step 的引用),当一个 ModelWizard 结束时,它会遍历所有 steps,访问 step 对应的 WizardModel,并调用 WizardModel#handleFinished()方法。

2. ModelWizardStep

一个 Step 就是 Wizard 向导程序中的一个单独页面,它负责创建一个 UI 界面呈现给用户,确定页面上的信息是否有效,并且将用户数据保存在对应的 WizardModel 对象中。

3. SkippableWizardStep

可以设置可见性的 Step,可以通过前一个 Step 来控制跟在其后的 Step 可见性,例如一个 Step 提供了一些选项给用户,并根据用户的选择来决定之后哪些 Steps 可以被展示。

4. WizardModel

Model 就是数据的集合,这些数据由 wizard 中的每个 step 进行填充。多个 step 可以共享同一个 model,核心的方法是 handleFinished(),当用户在向导程序中点击了“Finish”按钮时,wizard 结束,这个方法将会被调用进行最终的逻辑处理。

Wizard 向导程序工作流程图

<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin21.jpeg" width="900"/> </center>

创建一个 Android Studio 样式的向导程序

同样在 android.jar 库中,和 wizard 同级的名叫 ui 的包中提供了一个很方便的类,帮助使用者创建 AS 样式的 ModelWizard,只需将 ModelWizard 对象作为参数放入 StudioWizardDialogBuilder 的构造器中即可。使用 AS 样式包装我们的插件 UI 能让用户使用时更有原生的感觉,体验更好。


class CreateNewProjectAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) { StudioWizardDialogBuilder( ModelWizard.Builder().addStep(NewProjectStep()).build(), "Create New MARS Project" ).build().show() }}
class ProjectWizardModel : WizardModel() { //记录一些希望保存的字段 //... override fun handleFinished() { //处理最后的逻辑 }}
class NewProjectStep : ModelWizardStep<ProjectWizardModel?>(ProjectWizardModel(), "Create MARS Project") {
init { //创建Step页面的UI } //链接下一个Step override fun createDependentSteps(): MutableCollection<out ModelWizardStep<*>> { return arrayListOf(SelectBaselineStep(model)) }}
复制代码


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin22.png" width="480"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin23.png" width="480"/> </center>

Tool Windows

Tool Windows 是 IDE 的子窗口。这些窗口通常都在 IDE 主窗口的“边框”上拥有属于自己的一个 tool window button,点击后将在 IDE 主窗口的左、右、下侧激活 panel 来展示信息。如下图左一 Gradle 工具栏。创建 Tool Window 需要提供一个 JPanel,并通过 ToolWindowFactory 来实现。

ToolWindowFactory

Performs lazy initialization of a tool window registered in {@code plugin.xml}.


使用者必须创建 ToolWindowFactory 的实现类,并实现 createToolWindowContent()方法,在该方法中初始化 tool Window 的 UI,并添加到 Android Studio 中。ToolWindowFactory 提供了懒加载机制,这样实现的好处是未使用的工具窗口不会增加启动时间或导致内存使用方面的任何开销:如果用户没有与该工具窗口交互,相关代码就不会被加载和执行。


public class MyToolWindowFactory implements ToolWindowFactory {
@Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { // 初始化自定义组件对象 MyToolWindow myToolWindow = new MyToolWindow(project, toolWindow);
// 组件添加到AS中 ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); Content content = contentFactory.createContent(myToolWindow.getContent(), "", false); toolWindow.getContentManager().addContent(content); }}
复制代码


在插件中使用 Tool Windows 有两种形式:


  • declarative setup:可以理解为静态,在plugin.xml文件中注册,始终可见用户随时都可以使用。

  • Programmatic Setup:通过 API 接口动态注入,可以在一些操作前后出现和隐藏。

Declarative Setup

<extensions defaultExtensionNs="com.intellij">    <!-- Add your extensions here -->    <toolWindow id="MyToolWindow" secondary="true" anchor="right" factoryClass="com.volcengine.plugin.toolwindow.MyToolWindowFactory"/></extensions>
复制代码

Programmatic Setup

updateBaselineBtn.addActionListener(e -> {    BaselineWindow baselineWindow = new BaselineWindow(versionsJson, project, toolWindow);    ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();    Content content = contentFactory.createContent(baselineWindow.getContent(), "", false);    toolWindow.getContentManager().addContent(content);    toolWindow.getContentManager().setSelectedContent(content);});
复制代码


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin24.png" width="320"/>


<img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin25.png" width="320"/>


<img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin26.png" width="320"/> </center>

UI 创建工具

Wizard 向导程序和 Tool Window 工具栏都需要 UI 作为面板内容的填充,根本上来说,需要的只是一个内容丰富的 JPanel 作为 Content。AS 插件中的 UI 大量的使用了 Java Swing 组件,所以对 Swing 比较熟悉的同学上手会很快,这里介绍几种在 AS 插件中生成 UI 的技巧。

GUI Form

New --> Swing UI Designer --> GUI Form 填写信息后就会生成对可视化的.form 文件以及绑定的 java 类,在对应的 java 文件中增加一个 getRootPanel 方法获取 root panel 就可以将构建好的 Panel 给到向导程序或工具栏中使用。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin27.png" /> </center>

Eclipse - WindowBuilder

上面提到的 GUI Form 有一个缺点,只能使用 Java,并且.fome 文件和.java 文件强绑定,我们也无法单独使用这个生成的 java 文件,并且当我们想编写纯 Kotlin 代码时,GUI Form 就显得不好用了。


Eclipse 是多数同学刚接触 Java 时使用的经典 IDE,其中有一个 WindowBuilder 插件同样可以可视化创建 GUI 界面,但是相比于 GUI Form,WindowBuilder 生成的是单独的.java 文件,用户在 GUI 可视化界面操作的每个步骤都会生成对应的源码,我们可以直接 copy 这些代码到 AS 插件当中,并使用“convert java code to Kotlin”功能,将这些代码一键转为 Kotlin 代码,非常方便(更重要的是,WindowBuilder 的使用体验个人觉得更好)。


<center class="half"><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin28.png"/><img src="http://lf3-client-infra.bytetos.com/obj/client-infra-images/ASPlugin29.png"/></center>

Kotlin UI DSL

IntelliJ 插件官方提供的一些基于 Kotlin 的领域特定语言,可以在 Kotlin 代码中写 UI,优点是代码优美,缺点是累,具体可参考官网的指引https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl.html

数据持久化

有时我们希望能保存用户在插件中的操作或一些配置,避免重复的工作以及必要数据的读取,或避免用户重复多次输入。IntelliJ Platform 提供了一些方便的 API 来做数据持久化。

Plugin Service

这是 IntelliJ 插件开发中的基础能力,分为三种不同的类型,当我们想要在 IDE 插件的不同生命周期进行一些状态和逻辑上的处理,就可以使用这三种服务,例如:持久化状态、订阅事件、Application 启动/关闭时、Project 被打开/关闭时。



这块代码是模拟在 IDE 启动时,自动检验当前是否存在新版本的功能,若有新版本则进行更新操作,就是使用了持久化存储来实现的。


@State(name = "DemoConfiguration", storages = [    Storage(value = "demoConfiguration.xml")])
class DemoComoponent:ApplicationComponent, PersistentStateComponent<DemoComoponent>, Serializable { var version = 1 var localVersion = 0;
private fun isANerVersion() = localVersion < version
private fun updateVersion(){ localVersion = version }
override fun initComponent() { if(isANerVersion()){ updateVersion() } }
override fun getState(): DemoComoponent? = this override fun loadState(state: DemoComoponent) { XmlSerializerUtil.copyBean(state, this) }}
复制代码

持久化存储的两种方式

1. PropertiesComponent

这是一个简单的 Key-Value 数据结构,可以当作 Map 使用,用于保存 application 和 project 级别的数据。


//获取 application 级别的 PropertiesComponentPropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
//获取 project 级别的 PropertiesComponent,指定相应的 projectPropertiesComponent propertiesComponent = PropertiesComponent.getInstance(Project);
// set & getpropertiesComponent.setValue(name, value)propertiesComponent.getValue(name)
复制代码

2. PersistentStateComponent

复杂类型的数据结构使用 PersistentStateComponent,可以指定持久化的存储位置。


public interface PersistentStateComponent<T> {  @Nullable  T getState();  void loadState(T state);}
复制代码


  1. 创建一个 PersistentStateComponent 的实现类,T 表示需要持久化的数据结构类型,可以是任意类,甚至是实现类本身,然后重写 getState 和 loadState 方法。

  2. 若要指定存储的位置,需要在显现类上增加*@State*注解。

  3. 若不希望其中的某个字段被持久化,可以在该字段上增加*@Transient* 注解。


@State(    name = "ChentaoPlugin" ,    storages = [Storage("chentao-plugin.xml")])
class AarCheckBoxSettings :PersistentStateComponent<HashMap<String, AarCheckBoxState>> { var checkBoxStateList = HashMap<String, AarCheckBoxState>()
override fun getState(): HashMap<String, AarCheckBoxState>? { return checkBoxStateList } override fun loadState(stateList: HashMap<String, AarCheckBoxState>) { checkBoxStateList = stateList }
//将持久化组件声明为Serveice的获取方式是通过ServiceManager companion object{ @JvmStatic fun getInstance(): PersistentStateComponent<HashMap<String, AarCheckBoxState>>{ return ServiceManager.getService(AarCheckBoxSettings::class.java) } }}data class AarCheckBoxState(val componentId:String, val isSelected:Boolean)
复制代码
注册持久化组件

PersistentStateComponent 的实现类需要在 plugin.xml 中注册为 Service 后使用。


<extensions defaultExtensionNs="com.intellij">    <applicationService serviceImplementation="com.volcengine.plugin.actions.AarCheckBoxSettings"/></extensions>
复制代码

插件打包与安装

  • 打包:在 Gradle 工具栏中运行 assemble 任务,即可在/build/distribution/{插件名称}-{插件版本}.zip 路径下找到打包好的插件 zip 包。

  • 本地安装:还没将插件发布到插件市场前我们可以选择安装本地插件,打开 AS 菜单栏/Android Studio/Preference/Plugins/Install Plugin from Disk... 安装后即可使用。

  • 发布插件市场:

  • 访问 https://hub.jetbrains.com/,创建账号。

  • 使用账号登陆 jetbrains marketplace https://plugins.jetbrains.com/,发布插件(需官方审核 2 个工作日)。

  • 插件的第一个版本都需要在网站手动上传,之后的版本可以使用 hub 账号中的 token 自动更新。

总结

回顾开发过程:IDE 插件的核心步骤:安装正确版本 IntelliJ --> 配置工程 --> 创建 Action --> 将复杂流程注入 Wizard 向导程序或 ToolWindow 工具栏(同时创建 UI) --> 使用数据持久化保存必要数据 --> 打包 &安装 &发布。


笔者觉得 IDE 插件开发的难点主要是摸索的过程,IDE 插件较冷门,网上介绍的文章很少,官网介绍了一些功能和组件后也没有详细的 API 指引,令人有点无从下手。最终,通过反编译查看一些官方插件(Firebase、Flutter 等)的源码,以及收集 Google、Youtube 和各大博客的信息,终于将 AS 插件的一期雏形打造完毕,也将学到的一些常用的通用能力在本文中进行整理,希望能帮到之后想要接触 AS 插件开发的同学。

Demo

https://github.com/ChentaoZhou/ChentaoDemo

关于字节终端技术团队

字节跳动终端技术团队(Client Infrastructure)是大前端基础技术的全球化研发团队(分别在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率;支持的产品包括但不限于抖音、今日头条、西瓜视频、飞书、懂车帝等,在移动端、Web、Desktop 等各终端都有深入研究。


就是现在!客户端/前端/服务端/端智能算法/测试开发 面向全球范围招聘!一起来用技术改变世界,感兴趣请联系 [chenxuwei.cxw@bytedance.com](mailto:chenxuwei.cxw@bytedance.com),邮件主题 简历-姓名-求职意向-期望城市-电话


字节跳动应用开发套件MARS是字节跳动终端技术团队过去九年在抖音、今日头条、西瓜视频、飞书、懂车帝等 App 的研发实践成果,面向移动研发、前端开发、QA、 运维、产品经理、项目经理以及运营角色,提供一站式整体研发解决方案,助力企业研发模式升级,降低企业研发综合成本。

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2021.05.17 加入

字节跳动终端技术团队是大前端基础技术行业领军者,负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率,在移动端、Web、Desktop等各终端都有深入研究。

评论

发布
暂无评论
Android Studio IDE 插件开发