美文网首页androidCordova
Cordova android平台开发四(支持在新webview

Cordova android平台开发四(支持在新webview

作者: 西瓜皮TWO | 来源:发表于2018-01-22 11:21 被阅读315次

    前言:

    前段时间项目出需求,想要在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://<callbackId>
         *
         * where <callbackId> 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

      本文链接:https://www.haomeiwen.com/subject/hacaoxtx.html