为什么要使用JCEF了?因为我们发现有些基于Web的应用不是很稳定,在业务连续性方面还是有些缺陷,如果不想耗费精力和时间以及成本。使用基于Java应用的嵌入浏览器的方式反而是一种比较好的解决方案,可以通过集成Java应用以及websocket跟Web页面交互。
这篇文章主要是给大家开个头,个人精力有限,也借助了一些牛人开源的项目进行演练,本人主要还是起到抛砖引玉的作用,想要这篇文章达到技术水平至高的作业,请不要再继续浏览。
1.JCEF介绍(翻译来自xuanyimao)
Java Chromium嵌入式框架(JCEF)。 一个简单的框架,用于使用Java编程语言在其他应用程序中嵌入基于Chromium的浏览器
使用JCEF是为了用它开发自己的桌面应用程序。相对于vc,vb,swing这些,使用浏览器外壳,利用网络上众多流行的Web UI开源框架(如Easyui)做界面无疑是最快的,这可以让我们有更多的时间去实现业务逻辑,而不用被那些该死的UI控件折磨。想一想,原先做个表格,一整套界面做完大半天过去了,现在,引入JS、CSS,一瞬间做出一个高大上的界面,效率不言而喻。
初次接触JCEF,光是编译就花了一天时间。接着发现在网上找不到什么中文资料,利用空闲时间靠不断的网络搜索加翻译英文帮助文档了解它。断断续续到现在,终于做出了自己想要的软件。
https://github.com/chromiumembedded/java-cef
帮助文档
http://www.xuanyimao.com/jcef/index.html#pol
2.JCEF实战
2.1导入xuanyimao作者的源码到idea
https://gitee.com/edadmin/PowerOfLongedJcef
导入完后,设置JVM
在启动程序StartupApp设置JVM启动参数,加入-Djava.library.path=E:\learn\jcef\JcefTest\binary_win64(这个是binary_win64.zip解压后的目录,这个zip包含在gitee下载源码里面,也可以下载最新的zip包https://github.com/jcefbuild/jcefbuild/releases)
修改https://gitee.com/edadmin/PowerOfLongedJcef 这个项目的源码,主要修改下面两个:
MainFrame.java
package com.xuanyimao.polj.index;
import java.awt.*;
import java.io.File;
import javax.swing.*;
import org.cef.OS;
import com.xuanyimao.polj.index.anno.JsClass;
import com.xuanyimao.polj.index.bean.DevRepertory;
/**
* @Description: 启动窗口
* @author liuming
*/
@JsClass
public class MainFrame extends JFrame {
/**
*
*/
private static final long serialVersionUID = -5550525843316817638L;
private JButton[] jb = new JButton[9];
private JPanel jp1, jp2;
public MainFrame() {
jp1 = new JPanel();
jp2 = new JPanel();
jp1.setLayout(new FlowLayout());
Font font=new Font("宋体",Font.BOLD,60);
jp1.setFont(font);
jp2.setLayout(new BorderLayout());
jp2.setFont(font);
jb[0] = new JButton("诸葛亮");
jb[0].setFont(font);
jb[1] = new JButton("刘备");
jb[1].setFont(font);
jb[2] = new JButton("关羽");
jb[2].setFont(font);
jb[3] = new JButton("张飞");
jb[3].setFont(font);
jb[4] = new JButton("赵云");
jb[4].setFont(font);
jb[5] = new JButton("黄忠");
jb[5].setFont(font);
jb[6] = new JButton("马超");
jb[6].setFont(font);
jb[7] = new JButton("典韦");
jb[7].setFont(font);
jb[8] = new JButton("许褚");
jb[8].setFont(font);
jp1.add(jb[0]);
jp1.add(jb[1]);
jp1.add(jb[2]);
jp1.add(jb[3]);
jp1.add(jb[4]);
jp2.add(jb[5], BorderLayout.CENTER);
jp2.add(jb[6], BorderLayout.NORTH);
jp2.add(jb[7], BorderLayout.WEST);
jp2.add(jb[8], BorderLayout.EAST);
this.setLayout(new BorderLayout());
this.add(jp1, BorderLayout.EAST);
this.add(jp2, BorderLayout.SOUTH);
//this.setTitle("你好啊!");
this.setSize(300, 600);
this.setLocation(300, 600);
//this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);
CefManager cmg=CefManager.getInstance(OS.isLinux(), false, this);
cmg.createBrowser("file:///"+DevRepertory.getInstance().getAppPath()+"/index.html");
this.setExtendedState(JFrame.MAXIMIZED_BOTH);
this.setTitle("PowerOfLongedJcef-玄翼猫-V1.0");
ImageIcon icon=new ImageIcon(DevRepertory.getInstance().getAppPath()+File.separator+"logo.png");
this.setIconImage(icon.getImage());
}
}
CelManager.java
/**
* http://www.xuanyimao.com
* @author:liuming
* @date: 2019年9月4日
* @version V1.0
*/
package com.xuanyimao.polj.index;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.Vector;
import javax.swing.*;
import org.apache.commons.lang.StringUtils;
import org.cef.CefApp;
import org.cef.CefApp.CefAppState;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.browser.CefMessageRouter.CefMessageRouterConfig;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefMessageRouterHandler;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.xuanyimao.polj.index.bean.HandlerObject;
import com.xuanyimao.polj.index.bean.TabBrowser;
import com.xuanyimao.polj.index.handler.ContextMenuHandler;
import com.xuanyimao.polj.index.handler.DisplayHandler;
import com.xuanyimao.polj.index.handler.FileDownloadHandler;
import com.xuanyimao.polj.index.handler.LifeSpanHandler;
import com.xuanyimao.polj.index.handler.LoadHandler;
import com.xuanyimao.polj.index.listener.TabCloseListener;
import com.xuanyimao.polj.index.scanner.AnnotationScanner;
/**
* @Description: cef对象管理类
* “做人一定要脚踏实地,不要老想着和别人攀比,比来比去又得到了什么,让自己白白受累。百年后不过是一坯黄土,自己踏踏实实过好自己就行了。”
* “还是你心态好啊~”
* “也不是,主要是比不过。”
* @author liuming
*/
public class CefManager{
private static CefManager mg;
private CefManager(boolean useOSR,boolean isTransparent,JFrame frame) {
String[] args=new String[] {
"--enable-system-flash=true"//启用flash
,"--disable-web-security"//开启跨域
};
CefApp.addAppHandler(new CefAppHandlerAdapter(args) {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
if (state == CefAppState.TERMINATED) System.exit(0);
}
});
this.useOSR=useOSR;
this.isTransparent=isTransparent;
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = useOSR;
//模拟手机
// settings.user_agent="Mozilla/5.0 & #40;Linux; U; Android 8.0.0; zh-CN; MI 6 Build/OPR1.170623.027& #41; AppleWebKit/537.36 & #40;KHTML, like Gecko& #41; Version/4.0 Chrome/69.0.3497.100 UWS/3.20.0.31 Mobile Safari/537.36 UCBS/3.20.0.31_190923233723 NebulaSDK/1.8.100112 Nebula AlipayDefined& #40;nt:WIFI,ws:360|0|3.0& #41; AliApp& #40;AP/10.1.75.7000& #41;";
cefApp= CefApp.getInstance(args,settings);
client = cefApp.createClient();
//注册一些handler
client.addLoadHandler(new LoadHandler());
client.addLifeSpanHandler(new LifeSpanHandler());
client.addDisplayHandler(new DisplayHandler());
client.addContextMenuHandler(new ContextMenuHandler());
client.addDownloadHandler(new FileDownloadHandler());
//添加js交互
jsActive();
tabbedPane=new JTabbedPane(JTabbedPane.TOP,JTabbedPane.SCROLL_TAB_LAYOUT);
this.frame=frame;
this.frame.getContentPane().add(tabbedPane, BorderLayout.CENTER);
this.frame.pack();
this.frame.setSize(800, 600);
this.frame.setVisible(true);
this.frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
JLabel label = new JLabel("确定退出系统?");
label.setFont(new Font("宋体", Font.BOLD, 40));
// 确定按钮
JButton btnYes = new JButton("可以了哦");
btnYes.setFont(new Font("宋体", Font.BOLD, 40));
btnYes.setForeground(Color.MAGENTA);
// 否定按钮
JButton btnNo = new JButton("不行不行");
btnNo.setFont(new Font("宋体", Font.ITALIC, 40));
btnNo.setForeground(Color.PINK);
// 按钮选项加入数组
Object[] options = { btnYes, btnNo };
//JOptionPane
int option= JOptionPane.showConfirmDialog(
frame, label, "提示 ",JOptionPane.YES_NO_OPTION);
/* int option= JOptionPane.showOptionDialog(frame, label, "提示 ",JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
null, options, options[0]);
System.out.println("当前options是"+option);*/
if(option==JOptionPane.YES_OPTION) {
closeAllBrowser();
CefApp.getInstance().dispose();
frame.dispose();
}else {
return;
}
}
});
}
private CefManager() {}
/**
* 获取CefManager对象实例
* @author:liuming
* @param useOSR
* @param isTransparent
* @param frame
* @return
*/
public static CefManager getInstance(boolean useOSR,boolean isTransparent,JFrame frame) {
if(mg==null) {
mg=new CefManager( useOSR, isTransparent,frame);
}
return mg;
}
/**
* 获取CefManager对象实例,在此方法之前必须在任意位置调用过getInstance(boolean useOSR,boolean isTransparent,JFrame frame)
* @author:liuming
* @return
*/
public static CefManager getInstance() {
return mg;
}
private static CefApp cefApp;
private static CefClient client;
private JFrame frame;
private boolean useOSR;
private boolean isTransparent;
private static JTabbedPane tabbedPane;
private List<TabBrowser> tbList=new Vector<TabBrowser>();
private int tbIndex=0;
private final static String TITLE_INFO="正在载入...";
/**
* 获取CefClient对象
* @return client
*/
public static CefClient getClient() {
return client;
}
/**
* 获取Frame窗口对象
* @return frame
*/
public JFrame getFrame() {
return frame;
}
/**
* 获取TabBrowser对象集合
* @return tbList
*/
public List<TabBrowser> getTbList() {
return tbList;
}
/**
* 关闭所有浏览器窗口
* @author:liuming
*/
public void closeAllBrowser() {
for(int i=tbList.size()-1;i>=0;i--) {
TabBrowser tb=tbList.get(i);
tb.getBrowser().close(true);
tabbedPane.removeTabAt(i);
System.out.println("移除索引为"+i+"的tab...");
}
}
/**
* 根据url创建一个新的tab页
* @author:liuming
* @param url
* @return 最后一个tab的索引
*/
public int createBrowser(String url) {
CefBrowser browser = client.createBrowser(url, useOSR, isTransparent);
tabbedPane.addTab(".", browser.getUIComponent());
int lastIndex=tabbedPane.getTabCount()-1;
tbIndex++;
//创建自定义tab栏
JPanel jp=new JPanel();
//设置panel为卡片布局
// jp.setLayout(new GridLayout(1, 1, 10, 0));
Font font=new Font("宋体",Font.BOLD,60);
JLabel ltitle=new JLabel(TITLE_INFO);
ltitle.setFont(font);
JLabel lclose=new JLabel("X");
lclose.setFont(font);
jp.setOpaque(false);
ltitle.setHorizontalAlignment(JLabel.LEFT);
lclose.setHorizontalAlignment(JLabel.RIGHT);
jp.add(ltitle);
jp.add(lclose);
lclose.addMouseListener(new TabCloseListener(tbIndex));
tabbedPane.setTabComponentAt(lastIndex, jp);
TabBrowser tb=new TabBrowser(tbIndex, browser, ltitle);
tbList.add(tb);
tabbedPane.setSelectedIndex(lastIndex);
return lastIndex;
}
/**
* 修改标题
* @author:liuming
* @param browser
* @param title
*/
public void updateTabTitle(CefBrowser browser,String title) {
if(StringUtils.isNotBlank(title)) {
if(title.length()>12) title=title.substring(0, 12)+"...";
for(TabBrowser tb:tbList) {
if(tb.getBrowser()==browser) {
tb.getTitle().setText(title);
break;
}
}
}
}
/**
* 移除tab
* @author:liuming
* @param browser
* @param index
*/
public void removeTab(CefBrowser browser,int index) {
if(browser!=null) {
for(int i=0;i<tbList.size();i++) {
TabBrowser tb=tbList.get(i);
if(tb.getBrowser()==browser) {
tb.getBrowser().close(true);
tabbedPane.removeTabAt(i);
tbList.remove(i);
// System.out.println("移除索引为"+i+"的tab");
break;
}
}
}else {
for(int i=0;i<tbList.size();i++) {
TabBrowser tb=tbList.get(i);
if(tb.getIndex()==index) {
tb.getBrowser().close(true);
tabbedPane.removeTabAt(i);
tbList.remove(i);
// System.out.println("移除索引为"+i+"的tab");
break;
}
}
}
}
/**
* 添加js交互
* @author:liuming
*/
public void jsActive() {
//配置一个查询路由
CefMessageRouterConfig cmrc=new CefMessageRouterConfig("java","javaCancel");
//创建查询路由
CefMessageRouter cmr=CefMessageRouter.create(cmrc);
cmr.addHandler(new CefMessageRouterHandler() {
@Override
public void setNativeRef(String str, long val) {
System.out.println(str+" "+val);
}
@Override
public long getNativeRef(String str) {
System.out.println(str);
return 0;
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) {
System.out.println("取消查询:"+query_id);
}
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent,
CefQueryCallback callback) {
int i1=request.indexOf(":");
String action=request;
String param="";
if(i1!=-1) {
action=request.substring(0,i1);
param=request.substring(i1+1);
}
System.out.println("action:"+action+" param"+param);
//创建js交互数据重新封装的对象
HandlerObject ho=new HandlerObject(browser, frame, query_id, request, persistent, callback);
JsonObject jobj=null;
if(StringUtils.isNotBlank(param)) {//如果存在参数
JsonParser parser=new JsonParser();
jobj=parser.parse(param).getAsJsonObject();
}
AnnotationScanner.execMain(action, jobj, ho);
return true;
}
}, true);
client.addMessageRouter(cmr);
}
}
最后界面展示如下,通过下面的界面,大家就可以在上面将Java的应用程序跟浏览器结合,通过websocket机制,基本可以跟web界面作比较好的交互,xuanyimao这个作者也做了一些基本的跟JS交互的代码,也可以参考。更加深的结合,暂不提供,也可以自己百度下。