江苏城工建设科技有限公司网站,广州个人网页制作,上海企业网站模板建站费用,花生壳怎么建设购物网站项目背景
当前的开发环境存在多种不同语言的 IDE#xff0c;如 JetBrains 全家桶、Eclipse、Android Studio 和 VS Code 等等。由于每个 IDE 各有其特定的语言和平台要求#xff0c;因此开发 IDE 插件时#xff0c;需要投入大量资源才能尽可能覆盖大部分工具。同时#xf…项目背景
当前的开发环境存在多种不同语言的 IDE如 JetBrains 全家桶、Eclipse、Android Studio 和 VS Code 等等。由于每个 IDE 各有其特定的语言和平台要求因此开发 IDE 插件时需要投入大量资源才能尽可能覆盖大部分工具。同时代码难复用、用户体验难统一等问题又会进一步加重资源负担。
在调研过程中我们发现如今的大多数开发工具都支持集成 CEF而 CEF 提供的跨平台解决方案正可以有效解决上述问题。
关于 CEF 和 JCEF
CEFChromium Embedded Framework是一个开源项目它基于 Google Chromium 浏览器引擎允许开发人员将完整的浏览器功能嵌入到自己的应用程序中。
通过 CEF开发者可以利用现代 Web 技术来创建强大的桌面应用程序并实现与 Web 内容的无缝集成。如此一来开发者便可以利用 CEF 的功能和灵活性为各种开发工具提供统一的、高质量的插件体验。
JCEFJava Chromium Embedded Framework是基于 CEF 的一个特定版本专门为 Java 应用程序而生。本文内容也主要围绕 JCEF 展开。
JCEF 和其他产品的对比
JCEF vs JxBrowser
JxBrowser 和 JCEF 都允许将 Chromium 浏览器功能嵌入到 Java 应用程序中。其中JxBrowser 是商业产品而 JCEF 是开源框架且商业授权非常友好。
此外JxBrowser 在独立的本地进程中启动 Chromium而 JCEF 则是在 Java 进程内启动。JCEF 会快速初始化 Chromium同时消耗 Java 进程的内存和 CPU创建多个 Chromium 实例也会占用更多资源。
JCEF vs JavaFX
JavaFX 使用的内置浏览器组件是 WebView其在不同平台上的实现有所不同。例如在 macOS 上使用 WebKit在 Windows 上默认为 Internet Explorer而新版本的 JavaFX 则默认使用 JCEF。
这种不一致性会增加插件适配的难度降低整体开发效率。
Java 进程与 JCEF 交互 如何在 IDE 插件中接入 JCEF
下面以 LigaAI Jetbrains 插件为例介绍集成 JCEF 的过程。
在 Java 代码里创建相应的 JcefBrowser。
static JBCefBrowser createBrowser(Project project) {JBCefClient client JBCefApp.getInstance().createClient();//CefMessageRouter 用于处理来自 Chromium 浏览器的消息和事件//前端代码可以通过innerCefQuery和innerCefQueryCancel发起消息给插件进行处理CefMessageRouter.CefMessageRouterConfig routerConfig new CefMessageRouter.CefMessageRouterConfig(innerCefQuery, innerCefQueryCancel);CefMessageRouter messageRouter CefMessageRouter.create(routerConfig, new MessageRouterHandler());client.getCefClient().addMessageRouter(messageRouter);//用于处理以http://inner/开头的请求。 用于拦截特定请求转发请求到本地以获取本地资源CefApp.getInstance().registerSchemeHandlerFactory(http, inner, new DataSchemeHandlerFactory());return new JBCefBrowser(client, );
}加载对应的 URL渲染页面。
public static void loadURL(JBCefBrowser browser, String url) {//如果不需要设置和浏览器显示相关的可忽略browser.getJBCefClient().addDisplayHandler(settingsDisplayHandler, browser.getCefBrowser());browser.loadURL(url);
}Java 进程拦截前端发起的获取静态资源的请求。如果直接访问外部资源则不需要做拦截这一步可忽略。
import com.intellij.liga.web.WebviewClosedConnection;
import com.intellij.liga.web.WebviewOpenedConnection;
import com.intellij.liga.web.WebviewResourceState;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.commons.lang.StringUtils;
import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandler;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;import java.io.File;
import java.net.URL;//继承 CefResourceHandler 接口自定义处理 Chromium 浏览器加载的资源如网页、图像、样式表等。
//通过实现该接口可以覆盖默认的资源加载行为并提供自定义的资源加载逻辑。
public class DataResourceHandler implements CefResourceHandler {private WebviewResourceState state;/*** 用于处理资源请求你可以通过该方法获取请求的 URL、请求头部信息并返回相应的响应结果。*/public boolean processRequest(CefRequest cefRequest, CefCallback cefCallback) {String url cefRequest.getURL();//判断请求是否是用于获取内部静态资源的如果是则拦截请求并从项目里对应配置获取对应文件返回//如果是请求外部资源则跳过if (StringUtils.isNotBlank(url) url.startsWith(http://inner)) {String pathToResource url.replace(http://inner, /front/inner);pathToResource pathToResource.split(\\?)[0];URL resourceUrl getClass().getResource(pathToResource);VirtualFile f VfsUtil.findFileByURL(resourceUrl);resourceUrl VfsUtil.convertToURL(f.getUrl());try {this.state (WebviewResourceState) new WebviewOpenedConnection(resourceUrl.openConnection());} catch (Exception exception) {//log output}cefCallback.Continue();return true;}return false;}/*** 用于设置资源响应的头部信息例如 Content-Type、Cache-Control 等。*/public void getResponseHeaders(CefResponse cefResponse, IntRef responseLength, StringRef redirectUrl) {this.state.getResponseHeaders(cefResponse, responseLength, redirectUrl);}/*** 用于读取资源的内容可以从这个方法中读取资源的数据并将其传递给浏览器*/public boolean readResponse(byte[] dataOut, int designedBytesToRead, IntRef bytesRead, CefCallback callback) {return this.state.readResponse(dataOut, designedBytesToRead, bytesRead, callback);}/*** 请求取消*/public void cancel() {this.state.close();this.state (WebviewResourceState) new WebviewClosedConnection();}}//定义处理 Chromium Embedded Framework (CEF) 中的 Scheme协议请求
public class DataSchemeHandlerFactory implements CefSchemeHandlerFactory {public CefResourceHandler create(CefBrowser cefBrowser, CefFrame cefFrame, String s, CefRequest cefRequest) {return new DataResourceHandler();}
}import org.cef.callback.CefCallback;
import org.cef.handler.CefLoadHandler;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefResponse;import java.io.InputStream;
import java.net.URLConnection;public class WebviewOpenedConnection implements WebviewResourceState {private URLConnection connection;private InputStream inputStream;public WebviewOpenedConnection(URLConnection connection) {this.connection connection;try {this.inputStream connection.getInputStream();} catch (Exception exception) {System.out.println(exception);}}public void getResponseHeaders(CefResponse cefResponse, IntRef responseLength, StringRef redirectUrl) {try {String url this.connection.getURL().toString();cefResponse.setMimeType(this.connection.getContentType());try {responseLength.set(this.inputStream.available());cefResponse.setStatus(200);} catch (Exception e) {cefResponse.setError(CefLoadHandler.ErrorCode.ERR_FILE_NOT_FOUND);cefResponse.setStatusText(e.getLocalizedMessage());cefResponse.setStatus(404);}} catch (Exception e) {cefResponse.setError(CefLoadHandler.ErrorCode.ERR_FILE_NOT_FOUND);cefResponse.setStatusText(e.getLocalizedMessage());cefResponse.setStatus(404);}}public boolean readResponse(byte[] dataOut, int designedBytesToRead, IntRef bytesRead, CefCallback callback) {try {int availableSize this.inputStream.available();if (availableSize 0) {int maxBytesToRead Math.min(availableSize, designedBytesToRead);int realNumberOfReadBytes this.inputStream.read(dataOut, 0, maxBytesToRead);bytesRead.set(realNumberOfReadBytes);return true;}} catch (Exception exception) {//log output} finally {this.close();}return false;}public void close() {try {if (this.inputStream ! null)this.inputStream.close();} catch (Exception exception) {//log output}}
}前端发送请求调用插件Java 进程接收并处理。
//前端示例代码
button onclickcallBrowser()调用浏览器代码/buttonscript
function callBrowser() {var parameter example parameter;window.location.href innerCefQuery:// parameter;
}
/script//插件示例代码
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandlerAdapter;public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {Overridepublic boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request,boolean persistent, CefQueryCallback callback) {try {System.out.println(request);callback.success();return true;} catch (Exception e) {//log output}return false;}
}插件前端代码。
// java进程调用前端代码
String script window.postMessage( JSONObject.toJSONString(scriptObj) );;
browser.executeJavaScript(script, , 0);// 前端代码
function postMessage(data) {// 处理从后端传递过来的数据console.log(Received message from backend:, data);// 在这里进行你希望执行的其他操作
}实现效果
通过使用 LigaAI IDE 插件开发者们无需跳转或登录外部系统在 IDE 内就能查看任务详情、完成工作、更新和同步任务状态、记录并提报完成信息在享受沉浸式工作的同时零负担地实现个人目标管理。 此外JCEF 为插件开发者提供了一个强大的工具可以利用 Chromium 浏览器的各种功能和扩展性以更丰富、更高级的方式提供信息和功能使编码过程变得容易。
因此利用 LigaAI IDE 插件提供的可视化图表研发团队还可以了解整体编码情况、不同任务类型的耗时分布等更有针对性地制定优化方案或调整规划排期。 常见问题及避坑指南
1集成 JCEF如何使 Web 样式与 IDE 插件整体样式保持统一
通过下述方法获取 IDE 的主题模式
public static String getGlobalStyle() {if (EditorColorsManager.getInstance().isDarkEditor())return dark;return light;
}获取 IDE 内的样式。
//主要可以查看com.intellij.util.ui.UIUtil和com.intellij.ui.JBColor这两个类
//获取字体大小
Font font UIUtil.getLabelFont();
//获取背景颜色
Color bg JBColor.background();
//获取字体颜色
Color labelFontColor UIUtil.getLabelFontColor(UIUtil.FontColor.NORMAL);
//获取按钮的背景颜色
JBColor buttonBg JBColor.namedColor(Button.default.startBackground,JBUI.CurrentTheme.Focus.defaultButtonColor());
//获取边框的颜色
Color border JBColor.border();2Java 和浏览器之间的交互路由名称不能设置为 cefQuery 和 cefQueryCancel。
这两个为 JCEF 的内置路由同名会干扰甚至覆盖 JCEF 的内部处理逻辑有一定概率会导致系统白屏等意外行为和异常情况。
CefMessageRouter.CefMessageRouterConfig routerConfig new CefMessageRouter.CefMessageRouterConfig(innerCefQuery, innerCefQueryCancel);3于 JetBrains 插件而言如果浏览器加载的静态页面数据是打包在插件包内的本地数据加载过程中获取目标 URL 需要先把目标文件转化为 JetBrains 的虚拟文件再获取虚拟文件的 URL 作为结果不然会加载不到目标文件。
public boolean processRequest(CefRequest cefRequest, CefCallback cefCallback) {String url cefRequest.getURL();if (StringUtils.isNotBlank(url) url.startsWith(http://inner)) {String pathToResource url.replace(http://inner, /front/inner);pathToResource pathToResource.split(\\?)[0];// 这里先获取目标文件转成虚拟文件再获取对应URLURL resourceUrl getClass().getResource(pathToResource);VirtualFile f VfsUtil.findFileByURL(resourceUrl);resourceUrl VfsUtil.convertToURL(f.getUrl());//try {this.state (WebviewResourceState) new WebviewOpenedConnection(resourceUrl.openConnection());} catch (Exception exception) {}cefCallback.Continue();return true;}return false;
}4插件初始化时如果浏览器请求 java 的接口较多或接口速度较慢时可能会出现白屏。这是因为 onQuery 里复杂的逻辑需要异步处理不然多个请求会阻塞导致浏览器白屏。
public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {Overridepublic boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request,boolean persistent, CefQueryCallback callback) {try {ApplicationManager.getApplication().invokeLater(() - {//进行复杂的逻辑});callback.success();return true;} catch (Exception e) {//log output}return false;}
}参考资料
[1] CEF 相关文档https://github.com/chromiumembedded/cef
[2] JCEF 源码位置 https://github.com/chromiumembedded/java-cef
[3] Jetbrains 插件开发文档https://plugins.jetbrains.com/docs/intellij/welcome.html
[4] JxBrowser 和 JCEF 的对比https://dzone.com/articles/jxbrowser-and-jcef 了解更多技术干货、研发管理实践等分享请关注 LigaAI。 欢迎试用 LigaAI-智能研发协作平台体验智能研发协作一起变大变强