refactor(browser): 重构 CEF 应用管理逻辑
- 新增 CefAppManager 类,实现多线程安全的 CEF应用管理 - 优化 CEF 初始化和资源释放流程,增强异常处理能力 - 修改 BrowserWindow 和 BrowserWindowJDialog,使用新的 CEF 应用管理方式- 调整 Tray线程初始化位置,提高代码可读性 修复严重bug,父类窗口(BrowserWindow.java)出现问题,没有接入CEF应用管理器
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -43,9 +43,9 @@ public class Tray {
|
||||
load(new TrayLabels("启动 HTML 查看器", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//SwingUtilities.invokeLater(() -> {
|
||||
MainApplication.popupHTMLWindow("");
|
||||
});
|
||||
//});
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user