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();
|
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);
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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("");
|
||||||
});
|
//});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user