JS調用JCEF方法


坐下寫這篇文章的時候,內心還是有一點點小激動的,折騰了一個多星期,踩了一個又一個的坑,終於找到一條可以走通的路,內心的喜悅相信經歷過的人都會明白~~~~~今兒個老百姓啊,真呀個真高興啊,哈哈,好了,廢話不多說,上正文

 

首先是問題的需求:

我用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);
}
View Code

大家看到什么沒有?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);
    }
}
View Code

並在主窗口中加入該實現:

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

然后在界面調用window.cefQuery,代碼如下:

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

一切准備就緒,重啟程序,然后興奮的等着結果出現,但是天不遂人願啊,等了半天,界面一片空白,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);
}
View Code

我們別的都不看,就先看最開始的類注釋,寫得清清楚楚,小弟我就不逐字翻譯了,大概意思就是這個就是我找了好幾天了的東西,然后我開始看里面的方法以及實現,多的也不說了,我們就看重點吧:

/**
     * 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,不要怕麻煩,也不要覺得頭暈,你想到的問題很多都已經是別人想到過的,如果真沒有,那你就當一回第一個吃螃蟹的人,挺好

 

本文為原創文章,轉載請注明出處,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM