refactor(browser): 重构 CEF 应用管理逻辑

- 新增 CefAppManager 类,实现多线程安全的 CEF应用管理
- 优化 CEF 初始化和资源释放流程,增强异常处理能力
- 修改 BrowserWindow 和 BrowserWindowJDialog,使用新的 CEF 应用管理方式- 调整 Tray线程初始化位置,提高代码可读性

修复严重bug,父类窗口(BrowserWindow.java)出现问题,没有接入CEF应用管理器
This commit is contained in:
tzdwindows 7
2025-05-03 19:02:47 +08:00
parent e3b9555081
commit b3c860677a
6 changed files with 222 additions and 83 deletions

View File

@@ -378,14 +378,6 @@ public class AxisInnovatorsBox {
main.progressBarManager.close(); main.progressBarManager.close();
new Thread(() -> {
try {
Tray.init();
} catch (RegisterTray.TrayException e) {
throw new RuntimeException(e);
}
}, "TrayThread").start();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
try { try {
main.ex = new MainWindow(); main.ex = new MainWindow();
@@ -408,6 +400,14 @@ public class AxisInnovatorsBox {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
new Thread(main.thread.getThreadGroup() ,() -> {
try {
Tray.init();
} catch (RegisterTray.TrayException e) {
throw new RuntimeException(e);
}
}, "TrayThread").start();
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to load plugins", e); logger.error("Failed to load plugins", e);
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

View File

@@ -154,26 +154,12 @@ public class BrowserWindow extends JFrame {
private Component initializeCef(Builder builder) throws MalformedURLException { private Component initializeCef(Builder builder) throws MalformedURLException {
if (!isInitialized && CefApp.getState() != CefApp.CefAppState.INITIALIZED) { if (!isInitialized) {
isInitialized = true;
try { try {
// 1. 初始化CEF设置禁用多窗口 this.cefApp = CefAppManager.getInstance();
CefSettings settings = new CefSettings(); //CefAppManager.incrementBrowserCount();
settings.windowless_rendering_enabled = false;
settings.javascript_flags = "--expose-gc";
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE;
String subprocessPath = FolderCreator.getLibraryFolder() + "/jcef/lib/win64/jcef_helper.exe";
// 验证子进程路径
if (!new File(subprocessPath).exists()) {
throw new IllegalStateException("jcef_helper.exe not found at: " + subprocessPath);
}
settings.browser_subprocess_path = subprocessPath;
System.out.println("Subprocess Path: " + settings.browser_subprocess_path);
// 2. 创建CefApp和Client严格单例
cefApp = CefApp.getInstance(settings);
client = cefApp.createClient(); client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){ client.addDisplayHandler(new CefDisplayHandler (){
@Override @Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
@@ -210,14 +196,14 @@ public class BrowserWindow extends JFrame {
) { ) {
// 格式化输出到 Java 控制台 // 格式化输出到 Java 控制台
//if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) { //if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) {
String log = String.format( String log = String.format(
"[Browser Console] %s %s (Line %d) -> %s", "[Browser Console] %s %s (Line %d) -> %s",
getLogLevelSymbol(level), getLogLevelSymbol(level),
source, source,
line, line,
message message
); );
System.out.println(log); System.out.println(log);
//} //}
return false; return false;
} }
@@ -420,22 +406,19 @@ public class BrowserWindow extends JFrame {
public void windowClosed(WindowEvent e) { public void windowClosed(WindowEvent e) {
browser.close(true); browser.close(true);
client.dispose(); client.dispose();
cefApp.dispose();
isInitialized = false;
} }
}); });
setVisible(true); setVisible(true);
isInitialized = true;
}); });
return browserComponent; return browserComponent;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
isInitialized = false;
JOptionPane.showMessageDialog(null, "初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(null, "初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
System.exit(1);
} }
} else { } else {
isInitialized = false;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
dispose(); dispose();
}); });

View File

@@ -170,9 +170,8 @@ public class BrowserWindowJDialog extends JDialog {
isInitialized = true; isInitialized = true;
try { try {
this.cefApp = CefAppManager.getInstance(); this.cefApp = CefAppManager.getInstance();
CefAppManager.incrementBrowserCount(); //CefAppManager.incrementBrowserCount();
client = cefApp.createClient(); client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){ client.addDisplayHandler(new CefDisplayHandler (){
@Override @Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {

View File

@@ -3,23 +3,58 @@ package com.axis.innovators.box.browser;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefSettings; import org.cef.CefSettings;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 增强版CEF应用管理器
* 特性:
* 1. 多级锁并发控制
* 2. 设置冲突自动恢复
* 3. 状态跟踪和验证
* 4. 增强的异常处理
*
* @author tzdwindows 7
*/
public class CefAppManager { public class CefAppManager {
private static CefApp cefApp; private static final Logger logger = LogManager.getLogger(CefAppManager.class);
private static int browserCount = 0; private static volatile CefApp cefApp;
private static boolean isInitialized = false; private static final CefSettings settings = new CefSettings();
// 状态跟踪
private static final AtomicBoolean isInitialized = new AtomicBoolean(false);
private static final AtomicBoolean settingsApplied = new AtomicBoolean(false);
private static final AtomicBoolean isDisposing = new AtomicBoolean(false);
// 并发控制
private static final Lock initLock = new ReentrantLock();
private static final Lock disposeLock = new ReentrantLock();
private static final AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
static { static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> { initializeDefaultSettings();
disposeCefApp(); registerShutdownHook();
}));
} }
public static synchronized CefApp getInstance() throws Exception {
// 关键修改:仅在第一次初始化时设置参数 private static void registerShutdownHook() {
if (cefApp == null && !isInitialized) { if (shutdownHookRegistered.compareAndSet(false, true)) {
CefSettings settings = new CefSettings(); Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("JVM shutdown hook triggered");
dispose(true);
}));
logger.debug("Shutdown hook registered successfully");
}
}
private static void initializeDefaultSettings() {
initLock.lock();
try {
settings.windowless_rendering_enabled = false; settings.windowless_rendering_enabled = false;
settings.javascript_flags = "--expose-gc"; settings.javascript_flags = "--expose-gc";
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE; settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE;
@@ -28,38 +63,161 @@ public class CefAppManager {
validateSubprocessPath(subprocessPath); validateSubprocessPath(subprocessPath);
settings.browser_subprocess_path = subprocessPath; settings.browser_subprocess_path = subprocessPath;
cefApp = CefApp.getInstance(settings); logger.info("Default CEF settings initialized");
isInitialized = true; } finally {
} else if (cefApp == null) { initLock.unlock();
// 后续调用使用无参数版本 }
cefApp = CefApp.getInstance(); }
public static CefApp getInstance() {
if (cefApp == null) {
if (initLock.tryLock()) {
try {
performSafeInitialization();
} finally {
initLock.unlock();
}
} else {
handleConcurrentInitialization();
}
} }
return cefApp; return cefApp;
} }
private static void performSafeInitialization() {
if (cefApp != null) return;
if (isDisposing.get()) {
throw new IllegalStateException("CEF is during disposal process");
}
try {
// 阶段1启动CEF运行时
if (!CefApp.startup(new String[0])) {
throw new IllegalStateException("CEF native startup failed");
}
// 阶段2应用设置仅首次
if (settingsApplied.compareAndSet(false, true)) {
cefApp = CefApp.getInstance(settings);
logger.info("CEF initialized with custom settings");
} else {
cefApp = CefApp.getInstance();
logger.info("CEF reused existing instance");
}
isInitialized.set(true);
} catch (IllegalStateException ex) {
handleInitializationError(ex);
}
}
private static void handleConcurrentInitialization() {
try {
if (initLock.tryLock(3, TimeUnit.SECONDS)) {
try {
if (cefApp == null) {
performSafeInitialization();
}
} finally {
initLock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("CEF initialization interrupted");
}
}
private static void handleInitializationError(IllegalStateException ex) {
if (ex.getMessage().contains("Settings can only be passed")) {
logger.warn("Settings conflict detected, recovering...");
recoverFromSettingsConflict();
} else if (ex.getMessage().contains("was terminated")) {
handleTerminatedState(ex);
} else {
logger.error("Critical CEF error", ex);
throw new RuntimeException("CEF initialization failed", ex);
}
}
private static void recoverFromSettingsConflict() {
disposeLock.lock();
try {
if (cefApp == null) {
cefApp = CefApp.getInstance();
settingsApplied.set(true);
isInitialized.set(true);
logger.info("Recovered from settings conflict");
}
} finally {
disposeLock.unlock();
}
}
private static void handleTerminatedState(IllegalStateException ex) {
disposeLock.lock();
try {
logger.warn("CEF terminated state detected");
dispose(false);
performEmergencyRecovery();
} finally {
disposeLock.unlock();
}
}
private static void performEmergencyRecovery() {
try {
logger.info("Attempting emergency recovery...");
CefApp.startup(new String[0]);
cefApp = CefApp.getInstance();
isInitialized.set(true);
settingsApplied.set(true);
logger.info("Emergency recovery successful");
} catch (Exception e) {
logger.error("Emergency recovery failed", e);
throw new RuntimeException("Unrecoverable CEF state", e);
}
}
public static synchronized void dispose(boolean isShutdownHook) {
disposeLock.lock();
try {
if (cefApp == null || isDisposing.get()) return;
isDisposing.set(true);
try {
logger.info("Disposing CEF resources...");
cefApp.dispose();
if (!isShutdownHook) {
cefApp = null;
isInitialized.set(false);
settingsApplied.set(false);
}
logger.info("CEF resources released");
} catch (Exception e) {
logger.error("Disposal error", e);
} finally {
isDisposing.set(false);
}
} finally {
disposeLock.unlock();
}
}
private static void validateSubprocessPath(String path) { private static void validateSubprocessPath(String path) {
File exeFile = new File(path); File exeFile = new File(path);
if (!exeFile.exists()) { if (!exeFile.exists()) {
throw new IllegalStateException("jcef_helper.exe not found at: " + path); String errorMsg = "JCEF helper executable missing: " + path;
logger.error(errorMsg);
throw new IllegalStateException(errorMsg);
} }
logger.debug("Validated JCEF helper at: {}", path);
} }
public static synchronized void incrementBrowserCount() { // 状态查询接口
browserCount++; public static String getInitStatus() {
} return String.format("Initialized: %s, SettingsApplied: %s, Disposing: %s",
isInitialized.get(), settingsApplied.get(), isDisposing.get());
public static synchronized void decrementBrowserCount() {
if (--browserCount <= 0) {
disposeCefApp();
}
}
private static void disposeCefApp() {
if (cefApp != null) {
cefApp.clearSchemeHandlerFactories();
cefApp.dispose();
cefApp = null;
isInitialized = false;
}
} }
} }

View File

@@ -42,8 +42,7 @@ public class MainApplication {
* 弹出AI窗口 * 弹出AI窗口
* @param parent 父窗口 * @param parent 父窗口
*/ */
public static void popupAIWindow public static void popupAIWindow(JFrame parent) {
(JFrame parent) {
LM.loadLibrary(LM.CUDA); LM.loadLibrary(LM.CUDA);
modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK); modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK);
ctxHandle = LM.createContext(modelHandle); ctxHandle = LM.createContext(modelHandle);
@@ -108,11 +107,11 @@ public class MainApplication {
Path filePath = Paths.get(path); Path filePath = Paths.get(path);
// 验证文件存在性 //// 验证文件存在性
if (!Files.exists(filePath)) { //if (!Files.exists(filePath)) {
callback.failure(404, "{\"code\":404,\"message\":\"文件未找到\"}"); // callback.failure(404, "{\"code\":404,\"message\":\"文件未找到\"}");
return true; // return true;
} //}
// 读取文件内容 // 读取文件内容
String content = Files.readString(filePath, StandardCharsets.UTF_8); String content = Files.readString(filePath, StandardCharsets.UTF_8);

View File

@@ -43,9 +43,9 @@ public class Tray {
load(new TrayLabels("启动 HTML 查看器", new Runnable() { load(new TrayLabels("启动 HTML 查看器", new Runnable() {
@Override @Override
public void run() { public void run() {
SwingUtilities.invokeLater(() -> { //SwingUtilities.invokeLater(() -> {
MainApplication.popupHTMLWindow(""); MainApplication.popupHTMLWindow("");
}); //});
} }
})); }));