技術分享 | 在 IDE 插件開發中接入 JCEF 框架

項目背景

當前的開發環境存在多種不同語言的 IDE,如 JetBrains 全家桶、Eclipse、Android Studio 和 VS Code 等等。由于每個 IDE 各有其特定的語言和平臺要求,因此開發 IDE 插件時,需要投入大量資源才能盡可能覆蓋大部分工具。同時,代碼難復用、用戶體驗難統一等問題又會進一步加重資源負擔。

在調研過程中,我們發現如今的大多數開發工具都支持集成 CEF,而 CEF 提供的跨平臺解決方案正可以有效解決上述問題。

關于 CEF 和 JCEF

CEF(Chromium Embedded Framework)是一個開源項目,它基于 Google Chromium 瀏覽器引擎,允許開發人員將完整的瀏覽器功能嵌入到自己的應用程序中。

通過 CEF,開發者可以利用現代 Web 技術來創建強大的桌面應用程序,并實現與 Web 內容的無縫集成。如此一來,開發者便可以利用 CEF 的功能和靈活性,為各種開發工具提供統一的、高質量的插件體驗。

JCEF(Java 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 的過程。

  1. 在 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, "");
}
  1. 加載對應的 URL,渲染頁面。
public static void loadURL(JBCefBrowser browser, String url) {//如果不需要設置和瀏覽器顯示相關的,可忽略browser.getJBCefClient().addDisplayHandler(settingsDisplayHandler, browser.getCefBrowser());browser.loadURL(url);                 
}
  1. 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}}
}
  1. 前端發送請求調用插件,Java 進程接收并處理。
//前端示例代碼
<button onclick="callBrowser()">調用瀏覽器代碼</button><script>
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;}
}
  1. 插件前端代碼。
// 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();

2:Java 和瀏覽器之間的交互路由名稱不能設置為 cefQuerycefQueryCancel

這兩個為 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-智能研發協作平臺,體驗智能研發協作,一起變大變強!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166387.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166387.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166387.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構算法-貪心算法

引言 貪心&#xff1a;人只要有 “需求“ &#xff0c;都會有有點“貪“&#xff0c; 這種“貪“是一種選擇&#xff0c;或者“”取舍“ RTS&#xff08;即時戰略&#xff09;游戲&#xff1a; 帝國時代里 首先確保擁有足夠的人口 足夠的糧食&#xff0c;足夠的戰略資源 足夠的…

干貨科普 | 不同類型的機器人及其在工作中的應用

原創 | 文 BFT機器人 制造商在其操作中使用各種類型的機器人&#xff0c;每種機器人都具有特定的能力和功能。我們將討論制造業中使用的一些最常見類型的機器人&#xff0c;以及哪種機器人可能最適合您的應用。 01 關節機器人 關節式機器人是一種工業機器人&#xff0c;具有一…

npm,yarn,pnpm 清理緩存

目錄 1&#xff0c;為什么要清理緩存1&#xff0c;緩存文件太多&#xff0c;影響系統運行2&#xff0c;不同源會有區別 2&#xff0c;命令2.1&#xff0c;npm2.2&#xff0c;yarn2.3&#xff0c;pnpm 1&#xff0c;為什么要清理緩存 1&#xff0c;緩存文件太多&#xff0c;影響…

關于easy-es的聚合問題

es實體類&#xff1a; public class ChemicalES {IndexId(type IdType.CUSTOMIZE)private Long id;HighLightIndexField(fieldType FieldType.TEXT, analyzer "ik_max_word")private String name;IndexField(fieldType FieldType.KEYWORD)private List<Stri…

三數之和 Java版

題目描述&#xff1a;給你一個包含 n 個整數的數組 nums&#xff0c;判斷 nums 中是否存在三個元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 請你找出所有和為 0 且不重復的三元組。 注意&#xff1a;答案中不可以包含重復的三元組。 輸入&#xff1a;nums …

“土味出海”,屢試不爽!短劇出海引來新一輪爆發?

土味和“錢途”并存的短劇不僅在國內迅猛爆發&#xff0c;今年下半年以來海外市場多部爆火短劇出現&#xff0c;“短劇出海”的話題熱度不斷攀升&#xff0c;絲毫不差2021年網文出海的盛況。 “霸總的愛&#xff0c;日入千萬刀”&#xff0c;是真實存在的&#xff01; 據統計…

tp8 使用rabbitMQ(1)簡單隊列

php8.0 使用 rabbitmq 要使用 3.6版本以上的&#xff0c; 并且還要開啟 php.ini中的 socket 擴展 php think make:command SimpleMQProduce //創建一個生產者命令行 php think make:command SimpleMQConsumer //創建一個消費者命令行 代碼中的消息持久化的說明 RabbitMQ 消息持…

#Js篇:var、let和 const

var 聲明的變量具有函數作用域或者全局作用域&#xff1b;存在變量提升&#xff0c;即在執行上下文中&#xff0c;變量會被提升到函數或全局作用域的頂部&#xff0c;但初始化的賦值不會提升&#xff1b;可以重復聲明同一個變量不會報錯&#xff1b;可以被重新賦值&#xff1b…

vue3 + Ant Design Vue國際化,組件默認顯示中文

官網 寫入App.vue <template><ConfigProvider :locale"zhCN"><router-view :key"$route.fullPath"></router-view></ConfigProvider> </template><script setup> import { ConfigProvider } from "ant-de…

某上市證券公司:管控文件交換行為 保護核心數據資產

客戶簡介 某上市證券公司成立于2001年&#xff0c;經營范圍包括&#xff1a;證券經紀、證券投資咨詢、證券承銷與保薦、證券自營等。經過多年發展&#xff0c;在北京、上海、深圳、重慶、杭州、廈門等國內主要中心城市及甘肅省內各地市設立了15家分公司和80余家證券營業部。20…

軟文推廣中如何提煉好產品賣點,媒介盒子分享

內容同質化的時代下&#xff0c;企業應該如何讓用戶留下印象&#xff0c;并且成功將產品賣出去&#xff0c;核心思維就在于提煉產品賣點&#xff0c;產品賣點是銷量提升的關鍵&#xff0c;相信企業在推廣產品時都會有點困惑&#xff0c;感覺自家產品和競品比起來只是logo、外觀…

壓縮與解壓縮

核心接口 Compressor package com.xxx.arch.mw.nbp.common.csp;import com.xxx.arch.mw.nbp.common.constant.CommonConstants; import com.xxx.arch.mw.nbp.common.exception.NbpException;import static com.xxx.arch.mw.nbp.common.csp.CompressorEnum.ZSTD;/*** */ publi…

【mybatis】使用<foreach>報錯parameter ‘id‘ not found

程序其他sql都執行正常&#xff0c;也寫了param注解&#xff0c;但是還是一直報parameter ‘id’ not found。最后發現是調用sql的實現類里ids的入參對象名稱不叫ids&#xff0c;叫idList 代碼如下&#xff1a; List<Map<String,String>> resultmapper.sql(date,…

【攻防世界-misc】pure_color

1.方法一&#xff1a;用畫圖工具打開圖片&#xff0c;將圖片拷貝至虛擬機win7桌面&#xff0c; 點“屬性”&#xff0c;顏色設置為“黑白”&#xff0c; 出現flag值。 2.方法二&#xff1a;使用Stegsilve打開&#xff0c;分析圖片 將圖片打開&#xff0c;按左右鍵查找&#xff…

c#數據庫:vs2022 加入mysql數據源

網上有VS2019連接MySQL數據庫的&#xff0c;那么VS2022&#xff0c;VS2023如果和連接到mysql數據庫呢&#xff0c;這里總結一下我的經歷&#xff1a; 1、首先下載ODBC驅動安裝包 當前下載地址&#xff1a;https://dev.mysql.com/downloads/connector/odbc/ 2、ODBC安裝 下載完…

openGauss學習筆記-131 openGauss 數據庫運維-啟停openGauss

文章目錄 openGauss學習筆記-131 openGauss 數據庫運維-啟停openGauss131.1 啟動openGauss131.2 停止openGauss131.3 示例131.3.1 啟動openGauss131.3.2 停止openGauss 131.4 錯誤排查 openGauss學習筆記-131 openGauss 數據庫運維-啟停openGauss 131.1 啟動openGauss 以操作系…

【ChatGLM3-6B】Docker下快速部署

【ChatGLM2-6B】小白入門及Docker下部署 前提下載安裝包網盤地址 開始安裝加載鏡像啟動鏡像進入容器啟動模型交互頁面訪問頁面地址 前提 安裝好了docker安裝好了NVIDIA顯卡16G 下載安裝包 網盤地址 ? 這里因為網盤上傳文件有大小限制&#xff0c;所以使用了分卷壓縮的方式…

【深度學習】CNN中pooling層的作用

1、pooling是在卷積網絡&#xff08;CNN&#xff09;中一般在卷積層&#xff08;conv&#xff09;之后使用的特征提取層&#xff0c;使用pooling技術將卷積層后得到的小鄰域內的特征點整合得到新的特征。一方面防止無用參數增加時間復雜度&#xff0c;一方面增加了特征的整合度…

MySql數據庫常用指令(一)

MySql數據庫常用指令&#xff08;一&#xff09; 一、MySQL 創建數據庫二、MySQL 刪除數據庫三、選擇數據庫四、選擇數據庫五、創建數據表六、刪除數據表七、插入數據八、查詢數據 注&#xff1a;文中TEST為測試所用數據庫&#xff0c;根據實際應用修改 一、MySQL 創建數據庫 …

JVM中如何實現垃圾收集

Java虛擬機&#xff08;JVM&#xff09;使用垃圾收集器&#xff08;Garbage Collector&#xff09;來管理內存&#xff0c;清理不再使用的對象以釋放內存空間。垃圾收集的主要目標是自動化內存管理&#xff0c;使開發人員無需顯式地釋放不再使用的內存&#xff0c;從而降低了內…