坐下写这篇文章的时候,内心还是有一点点小激动的,折腾了一个多星期,踩了一个又一个的坑,终于找到一条可以走通的路,内心的喜悦相信经历过的人都会明白~~~~~今儿个老百姓啊,真呀个真高兴啊,哈哈,好了,废话不多说,上正文

首先是问题的需求:

我用JCEF实现了java程序中嵌入浏览器,不了解JCEF的同学请戳这里,那么问题就来了,如果我想要打开一个原生的文件选择对话框怎么办?因为在页面上,我们用的最多的就是js,而在示例demo里面全是后台的java代码,以及JNI的调用,而我想要的效果是如何用js去打开文件选择对话框,当时我的第一反应就是ajax请求,但是这样的话,我每次发送请求的时候都会需要带上我外部框架的对象CefBrowser,CefFrame等,并且我要一直在我的客户端持有这些对象,这个方法理论上是走得通的,但是如果能有更好的办法,何乐而不为呢?

于是英语水平稀烂的我开始研究网上的各种英文资料,也有幸找到了几篇中文的资料,下面就简单谈谈我踩坑的经历吧

我一个想到的就是,jcef打开文件选择对话框的本质还是通过JNI调用c++的接口来实现的,于是我想到了能不能通过js调用c++的方法来实现,正好官网上也有wiki文档:JavaScriptIntegration,参照官网的介绍,我开始一步一步按部就班的敲代码,敲好以后编译,重启,然后满怀期待的看着界面,然后就没然后了,不满足于失败的我开始寻找原因,由于水平有限,我折腾了三四天,始终没有解决这个问题,哎,我这暴脾气,就跟他杠上了,结果,还是以我的失败告终,悲哀~~如果有幸有大神看到这里,麻烦告知原因,小弟感激不尽。

既然直接用js调用c++的方式没走通,咱也不能真的在一棵树上吊死不是,于是我想到了能不能用js直接调用java代码,于是DWR出现在了我的视野里,这里就不再详细描述了,我只知道以我的水平,短时间内又败给了DWR,这就让人头痛了啊,搞不定,任务就没办法推进,总不能一直这么晾着哈。

于是我开始用我很中文的英文去网上提问,如果大家搜索时搜到了类似:How to open a filedialog using javascript或者How to bind javascript with java?这样的问题,很有可能就是我问的,然后问题放在好几天了,也没人回答,宝宝心里急啊,可是一点办法也没有,最后实在是不知道怎么办的时候,我开始静下心来看jecf的源码,试图从源码中找到点蛛丝马迹,还真别说,真就找到了。

当我看到cef/handler/CefMessageRouterHandler的时候,详细代码如下:

public interface CefMessageRouterHandler extends CefNative {

    /**
* Called when the browser receives a JavaScript query.
* @param browser The browser generating the event.
* @param query_id The unique ID for the query.
* @param persistent True if the query is persistent.
* @param callback Object used to continue or cancel the query asynchronously.
*
* @return true to handle the query or false to propagate the query to other
* registered handlers, if any. If no handlers return true from this method
* then the query will be automatically canceled with an error code of -1
* delivered to the JavaScript onFailure callback.
*/
public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent,
CefQueryCallback callback); /**
* Called when a pending JavaScript query is canceled.
* @param browser The browser generating the event.
* @param query_id The unique ID for the query.
*/
public void onQueryCanceled(CefBrowser browser, long query_id);
}

大家看到什么没有?Javascript有木有?抱着怀疑的态度,我看了该接口的实现类,啥都没有啊,但是好不容易找到一点蛛丝马迹,怎么可能轻易放弃呢,于是我又开始Google,果然,

Correct way to use window.cefQuery 和 Asynchronous JavaScript Bindings

终于觉得自己找到点有用的了,于是我开始研究这个接口的实现,写了一个简单的实现类如下:

public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {

    @Override
public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent,
final CefQueryCallback callback) {
if (StringUtils.isNotEmpty(request) && StringUtils.indexOf(request, "cmd:") != -1) {
String cmd = StringUtils.substring(request, 4);
if (StringUtils.equals(cmd, "open")) {
this.openAppFile(browser, callback);
}
}
return true;
} /**
* 打开文件
*/
private void openAppFile(CefBrowser browser, final CefQueryCallback callback) {
CefRunFileDialogCallback dialogCallBack = new CefRunFileDialogCallback() {
@Override
public void onFileDialogDismissed(int selectedAcceptFilter, Vector<String> filePaths) {
if (filePaths.size() == 0) {
return;
}
File selectedFile = new File(filePaths.get(0));
if (selectedFile != null) {
String selectedPath = selectedFile.getAbsolutePath();
callback.success(selectedPath);
}
}
};
browser.runFileDialog(FileDialogMode.FILE_DIALOG_OPEN, "test", null, null, 0, dialogCallBack);
}
}

并在主窗口中加入该实现:

//绑定js和jcef后台代码,window.cefQuery();
CefMessageRouter msgRouter = CefMessageRouter.create();
msgRouter.addHandler(new MessageRouterHandler(), true);
client.addMessageRouter(msgRouter);

然后在界面调用window.cefQuery,代码如下:

window.cefQuery({
request : "cmd:open",
onSuccess : function(response) {
alert(response);
},
onFailure : function(response) {
alert(response);
}
});

一切准备就绪,重启程序,然后兴奋的等着结果出现,但是天不遂人愿啊,等了半天,界面一片空白,FUCK,根据不算丰富的前端经验,这种情况多半是js异常,于是打开dev窗口,果然:

Uncaught TypeError: window.cefQuery is not a function

一下子又懵逼了,于是我开始在源码里面找CefMessageRouterHandler相关的代码,然后让我找到了CefMessageRouter类:

/**
* The below classes implement support for routing aynchronous messages between
* JavaScript running in the renderer process and C++ running in the browser
* process. An application interacts with the router by passing it data from
* standard CEF C++ callbacks (OnBeforeBrowse, OnProcessMessageRecieved,
* OnContextCreated, etc). The renderer-side router supports generic JavaScript
* callback registration and execution while the browser-side router supports
* application-specific logic via one or more application-provided Handler
* instances.
*
* The renderer-side router implementation exposes a query function and a cancel
* function via the JavaScript 'window' object:
*
* // Create and send a new query.
* var request_id = window.cefQuery({
* request: 'my_request',
* persistent: false,
* onSuccess: function(response) {},
* onFailure: function(error_code, error_message) {}
* });
*
* // Optionally cancel the query.
* window.cefQueryCancel(request_id);
*
* When |window.cefQuery| is executed the request is sent asynchronously to one
* or more C++ Handler objects registered in the browser process. Each C++
* Handler can choose to either handle or ignore the query in the
* Handler::OnQuery callback. If a Handler chooses to handle the query then it
* should execute Callback::Success when a response is available or
* Callback::Failure if an error occurs. This will result in asynchronous
* execution of the associated JavaScript callback in the renderer process. Any
* queries unhandled by C++ code in the browser process will be automatically
* canceled and the associated JavaScript onFailure callback will be executed
* with an error code of -1.
*
* Queries can be either persistent or non-persistent. If the query is
* persistent than the callbacks will remain registered until one of the
* following conditions are met:
*
* A. The query is canceled in JavaScript using the |window.cefQueryCancel|
* function.
* B. The query is canceled in C++ code using the Callback::Failure function.
* C. The context associated with the query is released due to browser
* destruction, navigation or renderer process termination.
*
* If the query is non-persistent then the registration will be removed after
* the JavaScript callback is executed a single time. If a query is canceled for
* a reason other than Callback::Failure being executed then the associated
* Handler's OnQueryCanceled method will be called.
*
* Some possible usage patterns include:
*
* One-time Request. Use a non-persistent query to send a JavaScript request.
* The Handler evaluates the request and returns the response. The query is
* then discarded.
*
* Broadcast. Use a persistent query to register as a JavaScript broadcast
* receiver. The Handler keeps track of all registered Callbacks and executes
* them sequentially to deliver the broadcast message.
*
* Subscription. Use a persistent query to register as a JavaScript subscription
* receiver. The Handler initiates the subscription feed on the first request
* and delivers responses to all registered subscribers as they become
* available. The Handler cancels the subscription feed when there are no
* longer any registered JavaScript receivers.
*
* Message routing occurs on a per-browser and per-context basis. Consequently,
* additional application logic can be applied by restricting which browser or
* context instances are passed into the router. If you choose to use this
* approach do so cautiously. In order for the router to function correctly any
* browser or context instance passed into a single router callback must then
* be passed into all router callbacks.
*
* There is generally no need to have multiple renderer-side routers unless you
* wish to have multiple bindings with different JavaScript function names. It
* can be useful to have multiple browser-side routers with different client-
* provided Handler instances when implementing different behaviors on a per-
* browser basis.
*
* This implementation places no formatting restrictions on payload content.
* An application may choose to exchange anything from simple formatted
* strings to serialized XML or JSON data.
*
*
* EXAMPLE USAGE
*
* 1. Define the router configuration. You can optionally specify settings
* like the JavaScript function names. The configuration must be the same in
* both the browser and renderer processes. If using multiple routers in the
* same application make sure to specify unique function names for each
* router configuration.
*
* // Example config object showing the default values.
* CefMessageRouterConfig config = new CefMessageRouterConfig();
* config.jsQueryFunction = "cefQuery";
* config.jsCancelFunction = "cefQueryCancel";
*
* 2. Create an instance of CefMessageRouter in the browser process.
*
* messageRouter_ = CefMessageRouter.create(config);
*
* 3. Register one or more Handlers. The Handler instances must either outlive
* the router or be removed from the router before they're deleted.
*
* messageRouter_.addHandler(myHandler);
*
* 4. Add your message router to all CefClient instances you want to get your
* JavaScript code be handled.
*
* myClient.addMessageRouter(messageRouter_);
*
* 4. Execute the query function from JavaScript code.
*
* window.cefQuery({request: 'my_request',
* persistent: false,
* onSuccess: function(response) { print(response); },
* onFailure: function(error_code, error_message) {} });
*
* 5. Handle the query in your CefMessageRouterHandler.onQuery implementation
* and execute the appropriate callback either immediately or asynchronously.
*
* public boolean onQuery(CefBrowser browser,
* long query_id,
* String request,
* boolean persistent,
* CefQueryCallback callback) {
* if (request.indexOf("my_request") == 0) {
* callback.success("my_response");
* return true;
* }
* return false; // Not handled.
* }
*
* 6. Notice that the success callback is executed in JavaScript.
*/
public abstract class CefMessageRouter { private final CefMessageRouterConfig routerConfig_; /**
* Used to configure the query router. If using multiple router pairs make
* sure to choose values that do not conflict.
*/
public static class CefMessageRouterConfig {
/**
* Name of the JavaScript function that will be added to the 'window' object
* for sending a query. The default value is "cefQuery".
*/
public String jsQueryFunction; /**
* Name of the JavaScript function that will be added to the 'window' object
* for canceling a pending query. The default value is "cefQueryCancel".
*/
public String jsCancelFunction; public CefMessageRouterConfig() {
this("cefQuery", "cefQueryCancel");
} public CefMessageRouterConfig(String queryFunction, String cancelFunction) {
jsQueryFunction = queryFunction;
jsCancelFunction = cancelFunction;
}
} // This CTOR can't be called directly. Call method create() instead.
CefMessageRouter(CefMessageRouterConfig routerConfig) {
routerConfig_ = routerConfig;
} /**
* Create a new router with the specified configuration.
*
* @param config router configuration
* @return
*/
public static final CefMessageRouter create() {
return CefMessageRouter.create(null, null);
} public static final CefMessageRouter create(CefMessageRouterConfig config) {
return CefMessageRouter.create(config, null);
} public static final CefMessageRouter create(CefMessageRouterHandler handler) {
return CefMessageRouter.create(null, handler);
} public static final CefMessageRouter create(CefMessageRouterConfig config, CefMessageRouterHandler handler) {
CefMessageRouter router = CefMessageRouter_N.createNative(config);
if (router != null && handler != null)
router.addHandler(handler, true);
return router;
} /**
* Must be called if the CefMessageRouter instance isn't used any more
*/
public abstract void dispose(); public final CefMessageRouterConfig getMessageRouterConfig() {
return routerConfig_;
} /**
* Add a new query handler. If |first| is true it will be added as the first
* handler, otherwise it will be added as the last handler. Returns true if
* the handler is added successfully or false if the handler has already been
* added. Must be called on the browser process UI thread. The Handler object
* must either outlive the router or be removed before deletion.
*
* @param handler the according handler to be added
* @param first if If set to true it will be added as the first handler
* @return true if the handler is added successfully
*/
public abstract boolean addHandler(CefMessageRouterHandler handler, boolean first); /**
* Remove an existing query handler. Any pending queries associated with the
* handler will be canceled. Handler.OnQueryCanceled will be called and the
* associated JavaScript onFailure callback will be executed with an error
* code of -1. Returns true if the handler is removed successfully or false
* if the handler is not found. Must be called on the browser process UI
* thread.
*
* @param handler the according handler to be removed
* @return true if the handler is removed successfully
*/
public abstract boolean removeHandler(CefMessageRouterHandler handler); /**
* Cancel all pending queries associated with either |browser| or |handler|.
* If both |browser| and |handler| are NULL all pending queries will be
* canceled. Handler::OnQueryCanceled will be called and the associated
* JavaScript onFailure callback will be executed in all cases with an error
* code of -1.
*
* @param browser may be empty
* @param handler may be empty
*/
public abstract void cancelPending(CefBrowser browser, CefMessageRouterHandler handler); /**
* Returns the number of queries currently pending for the specified |browser|
* and/or |handler|. Either or both values may be empty. Must be called on the
* browser process UI thread.
*
* @param browser may be empty
* @param handler may be empty
* @return the number of queries currently pending
*/
public abstract int getPendingCount(CefBrowser browser, CefMessageRouterHandler handler);
}

我们别的都不看,就先看最开始的类注释,写得清清楚楚,小弟我就不逐字翻译了,大概意思就是这个就是我找了好几天了的东西,然后我开始看里面的方法以及实现,多的也不说了,我们就看重点吧:

/**
* Used to configure the query router. If using multiple router pairs make
* sure to choose values that do not conflict.
*/
public static class CefMessageRouterConfig {
/**
* Name of the JavaScript function that will be added to the 'window' object
* for sending a query. The default value is "cefQuery".
*/
public String jsQueryFunction; /**
* Name of the JavaScript function that will be added to the 'window' object
* for canceling a pending query. The default value is "cefQueryCancel".
*/
public String jsCancelFunction; public CefMessageRouterConfig() {
this("cefQuery", "cefQueryCancel");
} public CefMessageRouterConfig(String queryFunction, String cancelFunction) {
jsQueryFunction = queryFunction;
jsCancelFunction = cancelFunction;
}
}

这里就是初始化js的window对象下面cefQuery,cefQueryCancel方法的地方,而你在设置CefMessageRouter的时候需要带上参数CefMessageRouterConfig,于是我将上面主窗口中的实现修改如下:

//绑定js和jcef后台代码,window.cefQuery();
CefMessageRouter msgRouter = CefMessageRouter.create(new CefMessageRouterConfig());
msgRouter.addHandler(new DIDesktopDocmd(), true);
client.addMessageRouter(msgRouter);

这一次总不会错了,重启,然后开始等待,终于,原生的文件选择框出现在了我的眼前,NICE,折腾了这么久,终于有一点点的进步了,上图,让各位看官都瞧一瞧:

至此,用js打开文件选择对话框告一段落,得出的结论如下:

1,一定要看源码,尤其是源码的类注释很重要

2,不要怕麻烦,也不要觉得头晕,你想到的问题很多都已经是别人想到过的,如果真没有,那你就当一回第一个吃螃蟹的人,挺好

本文为原创文章,转载请注明出处,谢谢!

JS调用JCEF方法的更多相关文章

  1. JS调用OC方法并传值,OC调用JS方法并传值////////////////////////zz

     iOS开发-基于原生JS与OC方法互相调用并传值(附HTML代码)     最近项目里面有有个商品活动界面,要与web端传值,将用户在网页点击的商品id 传给客户端,也就是js交互,其实再说明白一点 ...

  2. [iOS Hybrid实践:UIWebView中Html中用JS调用OC方法,OC执行JS代码]

    原理: 1.JS调用OC 每次webview执行跳转时都会被iOS给拦截,执行下面函数获得系统允许. 因此可以根据跳转信息转给系统,执行相应功能,比如打开相册等. // 网页中的每一个请求都会被触发 ...

  3. UIWebView中Html中用JS调用OC方法及OC执行JS代码

    HTML代码: <html> <head> <title>HTML中用JS调用OC方法</title> <meta http-equiv=&quo ...

  4. JS调用Silverlight方法拾遗

    在最近做的物联网项目中,需要利用封装过的Silverlight刻度控件显示温度,湿度,二氧化碳浓度等值.由于最新的数据是通过js ajax获取的,所以需要把这些数据传递给silverlight显示,这 ...

  5. js调用后台方法(如果你能容忍执行的后台方法变成一个常量)

    最近一直在做一个电话拨号的系统,系统不大,但是做的时间有点长了.其中用到了一个技术:js调用后台方法.解决这个问题花了不少时间,现如今仍然还有些不明白的地方,今天跟大家分享一下.真正明白的同学欢迎指正 ...

  6. c# js调用AjaxPro方法出错解析

    公司的项目的框架中有一部分用到了AjaxPro这个方法,看到这个方法的我一脸懵逼,老老实实去百度了一下. AjaxPro是.NET平台下的一个回调式AJAX框架,使用简单,功能强大.顾名思义ajax, ...

  7. Xilium.CefGlue利用XHR实现Js调用c#方法

    防外链 博客园原文地址在这里http://www.cnblogs.com/shen6041/p/3442499.html 引 Xilium CefGlue是个不错的cef扩展工程,托管地址在这里 ht ...

  8. 在WebBrowser控件使用js调用C#方法

    有时我们需要在WebBrowser控件中嵌入了网页,然后通过html页面调用后台方法,如何实现呢?其实很简单,主要有三步: 在被调用方法所属的类上加上[ComVisible(true)]标签,意思就是 ...

  9. JS调用App方法及App调用JS方法

    做App内嵌H5项目时,经常会遇到js与App的交互,最普遍的就是方法的互相调用,那么如何实现方法的互相调用呢? 写在前面: 如果只是小项目且后期扩大的可能性不大的时候,可以忽略,可如果是长期项目的话 ...

随机推荐

  1. SQL INSERT INTO 语句

    SQL Order By SQL update INSERT INTO 语句 INSERT INTO 语句用于向表格中插入新的行. 语法 INSERT INTO 表名称 VALUES (值1, 值2, ...

  2. UML之类图

    类(Class)封装了数据和行为,是具有相同属性.操作.关系的对象集合的总称. 类图(Class Dialog)使用系统中不同类来描述系统的静态结构,类图用来描述不同类和它们之间的关系. 类图由三部分 ...

  3. [功能改进]Live Writer发博支持“建分类、加标签、写摘要”

    以前您在园子里用Windows Live Wirter发布博文是不是有以下三个不爽: 不爽1:如果想在发布随笔时新建分类并将随笔添加至该分类,需要先在博客后台添加分类,然后在Live Writer中刷 ...

  4. 微信小程序开发POST请求

    onLoad: function() { that = this; wx.request( { url: "http://op.juhe.cn/onebox/weather/query&qu ...

  5. 就publish/subscribe功能看redis集群模式下的队列技术(一)

    Redis 简介 Redis 是完全开源免费的,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化,可以将内存中 ...

  6. SpringMVC原理解析-Servlet容器启动时初始化SpringMVC应用的原理

  7. SpringMVC常用配置-Controller返回格式化数据如JSON、XML的配置方式和机制

  8. NuGet控制台有几个常用命令

    NuGet控制台有几个常用命令 Get-Package 获取当前项目已经安装的类库 Install-Package 安装指定类库,命令格式如下:Install-Package 类库ID,如Instal ...

  9. 当利用pip安装模块出现错误时咋办

    >在DOS窗口中到Python安装路径的scripts中执行  pip install pyperclip 出现错误 >>错误提示:Fatal error in launcher: ...

  10. 前端安全配置之Content-Security-Policy(csp)

    什么是CSP CSP全称Content Security Policy ,可以直接翻译为内容安全策略,说白了,就是为了页面内容安全而制定的一系列防护策略. 通过CSP所约束的的规责指定可信的内容来源( ...