为什么要使用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

chrom支持java java chromium embedded framework_chrom支持java

在启动程序StartupApp设置JVM启动参数,加入-Djava.library.path=E:\learn\jcef\JcefTest\binary_win64(这个是binary_win64.zip解压后的目录,这个zip包含在gitee下载源码里面,也可以下载最新的zip包https://github.com/jcefbuild/jcefbuild/releases

chrom支持java java chromium embedded framework_JCEF_02

修改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交互的代码,也可以参考。更加深的结合,暂不提供,也可以自己百度下。

chrom支持java java chromium embedded framework_chrom支持java_03