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

首先是问题的需求:

我用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. 函数式中的 currying

    currying 是函数式语言中经常遇到的一个概念,翻译成 柯里化,不是库里化. currying 指的是将接收多个参数的函数变换成接收一个单一参数,并且返回接收余下的参数而且返回结果的新函数的技术. ...

  2. js获取上传文件个数 以及名称

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. iscroll 加载不全解决方案

    例如上图中,get_kaijiang 中如果执行一段ajax跨域传输的话 function get_kaijiang(){ ajax------- $('#div').append(html); -- ...

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

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

  5. 大气散射的demo

  6. 使用NUGet自动下载(还原)项目中使用的包

    签出完整项目后,在解决方案名称上点右键,选择"启用NuGet程序包还原",如下图: 出现询问,当然要点是:是 当完成后,会发现在解决方案中,多出".nuget" ...

  7. C语言的概述--学习c的第二天

    以下是整理的知识点: #include <stdio.h>/* 引入stdio.h文件c的标准函数库 */ int main(void)/* 定义一个函数main(),int定义函数返回的 ...

  8. ASPX开发基础

    ASP.NET:.net开发网站应用程序的技术总称,分为WebForm和MVC 表单元素: 文本类:(1)<input type=“text”/> 文本框 (2)<input typ ...

  9. Markdown 是什么

    tags: Markdown tags && syngx ###Markdown 是什么Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber).它允许人 ...

  10. cmd光标移动

    ESC:清除当前命令行.F1: 单字符输出上次输入的命令 相当于方向键上的 → 的作用.F2: 可复制字符数量 , 输入上次命令中含有的字符,系统自动删除此字符后的内容.F3: 重新输入前一次输入的命 ...