Cordova android平台开发四(支持在新webview中打开网页,并支持cordova其他插件)

前言:

前段时间项目出需求,想要在android应用中打开一个远程并且可以调用cordova插件功能的网页.网上基本没有这个方面的资料,故写此文章记录一下~

首先,这个需求有两个问题要解决:

  • 在已是cordova项目网页的基础上,重新创建一个webview实例来加载远程网页;
  • 这个远程网页具有调用其他插件的能力.
    下面我们来分别实现这两个需求......


    干了这一碗.png

一.创建webview,加载远程网页

这个需求很简单,引入cordova-plugin-inappbrowser插件
InAppBrowser可以使用新的窗口实例打开连接,提供了地址栏的显示隐藏,一些窗口操作。
不能设置地址栏内容、按钮、样式等,如果想更好的操作需要使用cordova-plugin-themeablebrowser插件
官网api:http://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/

  • 命令:
 cordova plugin add cordova-plugin-inappbrowser  
  • 重写配置
 document.addEventListener("deviceready", onDeviceReady, false);  
   function onDeviceReady() {  
     window.open = cordova.InAppBrowser.open;  
 }
  • 使用
$scope.openUrl=function(){
if (!cordova.InAppBrowser) {
    return;
}
// toolbar=yes 仅iOS有效,提供关闭、返回、前进三个按钮
// toolbarposition=top/bottom 仅iOS有效,决定toolbar的位置
// closebuttoncaption=关闭 仅iOS有效
window.open('http://www.baidu.com', '_blank', 
    'location=no,toolbar=yes,toolbarposition=top,closebuttoncaption=关闭');
}
  • openUrl是我写的一个方法,在html页面中在相应位置用ng-click去调用这个方法,此时就会触发浏览器跳转的事件,
  • 根据open()中的设置,URL参数是百度的网址;
  • target参数为"_blank",也就是在App中打开网址的页面;
    target的参数有三种:
    _self:如果URL地址在WhiteList中,则用Cordova的WhiteList将其打开;
    _blank:直接在App中将其地址打开,原理: 创建新的webview在一个全屏的dialog上;
    _system:则是用手机默认浏览器将新页面打开
  • options参数为iOS系统下会显示toolbar,toolbar的位置在顶部,closebuttoncaption隐藏Done按钮。

至此,第一个需求完成.............(欢呼一下,so little case~)

二.在远程网页上调用插件功能

这个需求不容易搞定,因为inappbrowser的实现默认是不允许使用cordova的任何资源,只是提供用新webview实例来加载一个纯的网页,所以需要对inAPPbrowser进行改造:

  • 在网页中加载cordova.js
    要想在新的网页中调用插件功能,必须在网页中注入cordova.js;我们通过在webview中拦截自定义URL来加载本地cordova.js
    改造InAppBrowser$InAppBrowserClient.class类:
 public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

         ....
            // 在网页加载完毕,根据需要注入 nativeJs
            // 自定义URL:private static final String NATIVE_JS_PREFIX = "https://native-js/";
            if (nativeJs) {
                String jsWrapper = "(function(d) { var c = d.createElement('script'); 
                                     c.src = %s; d.body.appendChild(c); })(document)";
                //在InAppBrowser WebView中注入一个对象(脚本或样式)。
                injectDeferredObject(NATIVE_JS_PREFIX + "cordova.js", jsWrapper);
            }
        }

重写shouldInterceptRequest方法,对自定义URL拦截处理

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            // SDK API < 21 时走这个方法
            return processInterceptRequest(view, url);
        }
        @SuppressLint("NewApi")
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            // SDK API >= 21 时走这个方法
            Uri uri = request.getUrl();
            String url = uri.toString();
            return processInterceptRequest(view, url);
        }
        public WebResourceResponse processInterceptRequest(WebView view, String url) {
            // 对于注入的 nativeJs,从本地读取
            if (url.startsWith(NATIVE_JS_PREFIX) && url.endsWith(".js")) {
                String path = url.substring(NATIVE_JS_PREFIX.length());
                String assetPath = "www/" + path;
                try {
                    //打开并返回本地js文件资源
                    InputStream inputStream = webView.getContext().getAssets().open(assetPath);
                    return new WebResourceResponse("application/javascript", "UTF-8", inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
  • 支持inappbrowser调用cordova插件功能
    inappbrowser默认只能触发"gap-iab"协议的回调,其他回调不能被触发,为了支持其他协议,我们要对InAppChromeClient.class修改:
 /**
     * Tell the client to display a prompt dialog to the user.
     * If the client returns true, WebView will assume that the client will
     * handle the prompt dialog and call the appropriate JsPromptResult method.      
     * (返回true,表示客户端处理提示行为,调用适当的JsPromptResult方法)
     *
     * The prompt bridge provided for the InAppBrowser is capable of executing any
     * oustanding callback belonging to the InAppBrowser plugin. Care has been
     * taken that other callbacks cannot be triggered, and that no other code
     * execution is possible.
     * (为InAppBrowser提供的提示桥能够执行任何属于InAppBrowser插件的回调。注意,        
        其他回调不能被触发,并且不可能有其他的代码执行。)
     *
     * To trigger the bridge, the prompt default value should be of the form:
     *
     * gap-iab://
     *
     * where  is the string id of the callback to trigger (something
     * like "InAppBrowser0123456789")
     *
     * If present, the prompt message is expected to be a JSON-encoded value to
     * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid.
     *
     * @param view
     * @param url
     * @param message
     * @param defaultValue
     * @param result
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute.
        if (defaultValue != null && defaultValue.startsWith("gap")) {
            if(defaultValue.startsWith("gap-iab://")) {
              //处理inappbrowser插件的回调
               ....
            }
            else
            {
                // 处理 cordova API回调
                CordovaWebViewEngine engine = webView.getEngine();
                if (engine != null) {
                    CordovaBridge cordovaBridge = null;
                    try {
                        //反射得到CordovaBridge 的对象实例
                        Field bridge = engine.getClass().getDeclaredField("bridge");
                        bridge.setAccessible(true);
                        cordovaBridge = (CordovaBridge) bridge.get(engine);
                        bridge.setAccessible(false);
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    if (cordovaBridge != null) {
                        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
                        String handledRet = cordovaBridge.promptOnJsPrompt(url, message, defaultValue);
                        if (handledRet != null) {
                            result.confirm(handledRet);
                        } else {
                            final JsPromptResult final_result = result;
                            CordovaDialogsHelper dialogsHelper = new CordovaDialogsHelper(webView.getContext());
                            dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
                                @Override
                                public void gotResult(boolean success, String value) {
                                    if (success) {
                                        final_result.confirm(value);
                                    } else {
                                        final_result.cancel();
                                    }
                                }
                            });
                        }
                    }else {
                        // Anything else with a gap: prefix should get this message
                        LOG.w(LOG_TAG, "InAppBrowser does not support Cordova API calls: " + url + " " + defaultValue);
                        result.cancel();
                    }
                }
                return true;
            }
        }
        return false;
    }

现在,你可以试试在新的webview中调用其他插件的API了,是不是很爽(~ >-< ~)

写在最后:

经测试有一个小bug,若通过inappbrowser跳转新的webview后可以正常调用cordova API,但是点击返回如果跳转前的网页没用刷新(即:不重新加载),那么此时的页面将不可以调用cordova API.
这是因为cordova初始时在原生会随机生成一个整数传给网页,来作为原生与H5之间交互的secret.此值由UI线程编写,由JS线程读取。

这里CordovaBridge.class:

   /** Called by cordova.js to initialize the bridge. */
    int generateBridgeSecret() {
        // 如果已经产生过 secret,则直接返回原值,不再重新生成。
        // 目的是让一个 CordovaBridge 实例能够服务于多个 webview 实例(比如用 InAppBrowser 新打开的网页),避免串扰。
        if (expectedBridgeSecret >= 0)
            return expectedBridgeSecret;

        SecureRandom randGen = new SecureRandom();
        expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
        return expectedBridgeSecret;
    }

技术重在分享,不敢闭门造车.文章如有不当,欢迎指正~ >-< ~
(写作不易,加个关注呗~)

你可能感兴趣的:(Cordova android平台开发四(支持在新webview中打开网页,并支持cordova其他插件))