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();
new Thread(() -> {
try {
Tray.init();
} catch (RegisterTray.TrayException e) {
throw new RuntimeException(e);
}
}, "TrayThread").start();
SwingUtilities.invokeLater(() -> {
try {
main.ex = new MainWindow();
@@ -408,6 +400,14 @@ public class AxisInnovatorsBox {
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) {
logger.error("Failed to load plugins", e);
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 {
if (!isInitialized && CefApp.getState() != CefApp.CefAppState.INITIALIZED) {
if (!isInitialized) {
isInitialized = true;
try {
// 1. 初始化CEF设置禁用多窗口
CefSettings settings = new CefSettings();
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);
this.cefApp = CefAppManager.getInstance();
//CefAppManager.incrementBrowserCount();
client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){
@Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
@@ -210,14 +196,14 @@ public class BrowserWindow extends JFrame {
) {
// 格式化输出到 Java 控制台
//if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) {
String log = String.format(
"[Browser Console] %s %s (Line %d) -> %s",
getLogLevelSymbol(level),
source,
line,
message
);
System.out.println(log);
String log = String.format(
"[Browser Console] %s %s (Line %d) -> %s",
getLogLevelSymbol(level),
source,
line,
message
);
System.out.println(log);
//}
return false;
}
@@ -420,22 +406,19 @@ public class BrowserWindow extends JFrame {
public void windowClosed(WindowEvent e) {
browser.close(true);
client.dispose();
cefApp.dispose();
isInitialized = false;
}
});
setVisible(true);
isInitialized = true;
});
return browserComponent;
} catch (Exception e) {
e.printStackTrace();
isInitialized = false;
JOptionPane.showMessageDialog(null, "初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
} else {
isInitialized = false;
SwingUtilities.invokeLater(() -> {
dispose();
});

View File

@@ -170,9 +170,8 @@ public class BrowserWindowJDialog extends JDialog {
isInitialized = true;
try {
this.cefApp = CefAppManager.getInstance();
CefAppManager.incrementBrowserCount();
//CefAppManager.incrementBrowserCount();
client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){
@Override
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 org.cef.CefApp;
import org.cef.CefSettings;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
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 {
private static CefApp cefApp;
private static int browserCount = 0;
private static boolean isInitialized = false;
private static final Logger logger = LogManager.getLogger(CefAppManager.class);
private static volatile CefApp cefApp;
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 {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
disposeCefApp();
}));
initializeDefaultSettings();
registerShutdownHook();
}
public static synchronized CefApp getInstance() throws Exception {
// 关键修改:仅在第一次初始化时设置参数
if (cefApp == null && !isInitialized) {
CefSettings settings = new CefSettings();
private static void registerShutdownHook() {
if (shutdownHookRegistered.compareAndSet(false, true)) {
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.javascript_flags = "--expose-gc";
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE;
@@ -28,38 +63,161 @@ public class CefAppManager {
validateSubprocessPath(subprocessPath);
settings.browser_subprocess_path = subprocessPath;
cefApp = CefApp.getInstance(settings);
isInitialized = true;
} else if (cefApp == null) {
// 后续调用使用无参数版本
cefApp = CefApp.getInstance();
logger.info("Default CEF settings initialized");
} finally {
initLock.unlock();
}
}
public static CefApp getInstance() {
if (cefApp == null) {
if (initLock.tryLock()) {
try {
performSafeInitialization();
} finally {
initLock.unlock();
}
} else {
handleConcurrentInitialization();
}
}
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) {
File exeFile = new File(path);
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 synchronized void decrementBrowserCount() {
if (--browserCount <= 0) {
disposeCefApp();
}
}
private static void disposeCefApp() {
if (cefApp != null) {
cefApp.clearSchemeHandlerFactories();
cefApp.dispose();
cefApp = null;
isInitialized = false;
}
// 状态查询接口
public static String getInitStatus() {
return String.format("Initialized: %s, SettingsApplied: %s, Disposing: %s",
isInitialized.get(), settingsApplied.get(), isDisposing.get());
}
}

View File

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

View File

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