Cordova|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 = https://www.it610.com/article/%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|Cordova android平台开发四(支持在新webview中打开网页,并支持cordova其他插件)】技术重在分享,不敢闭门造车.文章如有不当,欢迎指正~ >-< ~
(写作不易,加个关注呗~)

    推荐阅读