如何与H5前端完美互动:掌握WebView技巧
WebView与H5前端交互
一、混合开发介绍
1-1 混合开发介绍
混合开发定义
一种开发模式,英文名叫Hybrid App
混合使用Native和web技术开发
混合开发有那些优缺点
优点:开发快、易更新,开发周期短
缺点:性能问题、兼容性问题
Android 5.0+ 和IOS9.0+ 上缺点不再明显
混合开发的应用场景
微信公众号,通过JSSDK链接Native端和Web端
微信小程序,通过内置框架链接Native端和Web端
混合开发应用场景-微信公众号

混合开发应用场景-微信小程序

混合开发学习的意义
更好的使用第三方平台
更灵活的技术方案选型
具备搭建平台和输出服务的能力
1-2 混合开发核心技术
混合开发的核心技术
JSBridge
实现Native端和Web端双向通信的一种机
以Javascript引擎或WebView 容器为媒介
通过约定协议进行通信
混合开发的主流技术架构
Web渲染:Cordova(前身是PhoneGap)
原生渲染:React Native、weex
混合渲染:微信小程序
二、混合开发核心技术 JSBridge
2-1 JSBridge 实现原理
JSBridge 实现原理

类比Client/Server模式
将Native 端原生接口分装成JavaScript接口
将Web端JavaScript 接口封装成原生接口
Web端和Native 端之间双向通信
JSBridge 的两种实现方式
方式一:拦截WebView请求的URL Schema
方式二:向WebView注入JS API
2-2 JSBridge 实现方式一:拦截 URL Schema
JSBridge实现方式一:拦截URL Schema
官方
1、URL Schema 是类URL的一种请求格式
2、<protocol>://<domain>/<path>?<query>
3、http://www.google.com/search?keyword=jsbridge
自定义
1、自定义JS bridge通信的URL Schema
2、jsbridge://<method>?<params>
3、jsbridge://showToast?text=hello&a=b

方式一的优缺点
优点:兼容性好
缺点:不直观、URL长度有限制
代码实现
原生调用JS代码,弹出Web弹窗
private fun showWebDialog(text: String) {
val jsCode = String.format("window.showWebDialog('%s')", text)
mWebView.evaluateJavascript(jsCode, null)
}
Web端接收到原生端发出的请求
window.showWebDialog = text => window.alert(text);
Web端输入内容,点击显示原生弹窗,弹出原生对话框流程
document.addEventListener('DOMContentLoaded', e => {
const editText = document.querySelector('#editText');
const showBtn = document.querySelector('#showBtn');
showBtn.addEventListener('click', e => {
const inputValue = editText.value;
showNativeDialog(inputValue)
})
})
Web端通过alert发出一个请求
function showNativeDialog (text) {
window.alert('jsbridge://showNativeDialog?text=' + text);
}
原生端拦截代码
mWebView.setWebChromeClient(object : WebChromeClient() {
override fun onJsAlert(
view: WebView,
url: String,
message: String,
result: JsResult
): Boolean {
if (!message.startsWith("jsbridge://")) {
return super.onJsAlert(view, url, message, result)
}
val text = message.substring(message.indexOf("=") + 1)
self.showNativeDialog(text)
result.confirm()
return true
}
})
这三句话,句句非常重要:
第一句: self.showNativeDialog(text)
表示我们拦截html中alert函数之后,我们自己的操作,这里是弹出showNativeDialog
第二句:result.confirm();
这句话非常重要,它表示向WebView通知操作结果,JsResult有两个函数:JsResult.confirm()和JsResult.cancel(),JsResult.confirm()表示点击了弹出框的确定按钮,JsResult.cancel()则表示点击了弹出框的取消按钮。
如果没有使用JsResult来告诉WebView处理结果,则WebView就会认为这个弹出框还一直弹在那里,你再点击alert按钮,将会无效;
第三句:return true;
表示告诉WebView我们已经拦截了alert()函数,不需要再弹出网页中的alert弹出框了,如果我们return false,那么WebView就会认为我们没有拦截alert()函数,会继续弹出alert对话框。
2-3JSBridge 实现方式二:注入 JS API
JSBridge 实现方式二:注入 JS API

优缺点:
优点:简单直观
缺点:有兼容性问题(Android 4.2+)
实践:基于注入 JS API方式实现JSBridge
Demo实例,原生端调用Webview端代码不变,通过Webview中执行一段JS脚本来实现,Web端调用原生端的代码需要做一些改变。
需要在原生端Webview中暴露一个全局的JS对象,然后调用全局JS对象的方法
第一步:方法注入addJavascriptInterface(),暴露全局的JS对象,需要传入Java对象和JS对象的方法名
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
NativeBridge类
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
第二步:把NativeBridge 对象暴露到 WebView中的名叫NativeBridge JS对象上,然后我们在Web端就可以拿到这个方法去调用。
function showNativeDialog (text) {
// window.alert('jsbridge://showNativeDialog?text=' + text);
window.NativeBridge.showNativeDialog(text);
}
2-4 带回调的 JSBridge
前面介绍了实现JSBridge的两种方式,一种是基于拦截 URL Schema来实现JSBridge。另一种注入 JS API实现JSBridge,这两种都实现了JSBridge的一个双向通信,但这里的双向通行是结合两端来看的,如果单纯的站在某一端来看的话还是实现的单向通信,没有调用另一端的时候,没有把数据返回,这样就会产生个问题,在实际应用中我们经常需要实现调用对方直接要求对方把结果返回。
场景:Web端获取Nativity端的输入框的值
这个场景下我们需要Web端调用Nativity的方法,去把这个输入框的值获取到传给原生端,同样Nativity端需要把输入的值传回给Web端
什么是带回调的JSBridge?
在对端执行操作并返回结果
有输入有输出才是完整的调用
如何实现带回调的JSBridge?

2-5 使用 JSBridge 的开源实现
学习JSBridge的目的
掌握原理
具备造轮子的能力
避免重复造轮子
JSBridge的开源实现
JSBridge:拦截URL Schema
DSBridge:注入JS API
那么社区有那些JSBridge的开源实现呢,比较流行的就是JSBridgehe、DSBridge。
JSBridge是基于拦截URL Schema这种方式来实现的。
DSBridge是基于注入JS API实现的,所以掌握这两种开源库基本就可以满足大部分场景,可以根据需要去选择
DSBridge实现双向通信
腾讯X5:腾讯浏览服务
添加依赖
maven { url 'https://jitpack.io' }
implementation 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'
implementation 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
布局文件使用 替换原生的 WebView
<wendu.dsbridge.DWebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
></wendu.dsbridge.DWebView>
实现功能:web 输入内容,原生点击获取WEB输入按钮,获取H5的内容展示原生弹窗,
原生输入内容Web点击获取Native按钮,获取原生输入内容展示Web弹窗
js代码:点击获取Native输入
dsBridge.call('getNativeEditTextValue', '', value => {
window.alert('Native 输入值:' + value)
})
Webview中 dsBridge提供了一个注册原生接口的方法,这里提供一个JsApi的类就可以了。这样可以统一管理提供给WebView的方法。
webView.addJavascriptObject(new JSApi(this), null);
给Web提供调用的方法,
/**
* @param msg Web调用原生端传过来的参数
* @param handler 回调方法 获取到参数之后 通过 handler传递给web端
*/
@JavascriptInterface
public void getNativeEditTextValue(Object msg, CompletionHandler<String> handler) {
String value = ((MainActivity) ctx).editText.getText().();
handler.complete(value);
}
三、 开发一个简单的混合 App
基于JSBridge开源库实现双端通信,开发一个为以后原生支持H5功能可以扩展的混合App。
3-1 Web 端发送原生 HTTP 请求
Web端HTTP请求存在的问题
Web发送请求接口XMLHttpRepuest 和fetch
浏览器同源政策CORS安全限制
不够安全、无法优化网络
web端跨域请求的例子

Web端发送原生的Http请求优点
没有浏览器跨域限制
安全加密,签名校验
弱网优化,流量优化
实践:实现Web端发送原生Http请求
通过JSBridge 实现
流程:
将 com.github.lzyzsd.jsbridge.BridgeWebView 添加到您的布局中,它是从 WebView 继承的。
<com.github.lzyzsd.jsbridge.BridgeWebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.github.lzyzsd.jsbridge.BridgeWebView>
注册一个 Java处理函数,以便 JavaScript 可以调用
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
function.onCallBack("submitFromWeb exe, response data 中文 from Java");
}
});
Node.js 可以通过以下方式调用这个 Java 处理程序方法“submitFromWeb”:
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': '需要的参数'}
, function(responseData) {
document.getElementById("show").innerHTML = "原生请求回来的数据 = " + responseData
}
);
你可以在Java中设置一个默认的handler,这样js就可以在不分配handlerName的情况下向Java发送消息
Java默认注册
webView.setDefaultHandler(new DefaultHandler());
Node.js 通过doSend发送消息
window.WebViewJavascriptBridge.send(
data
, function(responseData) {
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
注册一个 JavaScript 处理函数,以便 Java 可以调用
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
Java可以通过以下方式调用这个js处理函数“functionInJs”:
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
你也可以定义一个默认的 handler 使用 init 方法,这样 Java 就可以在不分配 handlerName 的情况下向 js 发送消息
window.WebViewJavascriptBridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': 'Wee!'
};
console.log('JS responding with', data);
responseCallback(data);
});
将在 webview 控制台中打印 'JS got a message hello' 和 'JS respond with'。
注意
这个库将向window对象注入一个 WebViewJavascriptBridge 对象。您可以监听WebViewJavascriptBridgeReady事件以确保window.WebViewJavascriptBridge存在。
if (window.WebViewJavascriptBridge) {
//do your work here
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
//do your work here
},
false
);
}
或者如果未定义,则将所有 JsBridge 函数调用放入window.WVJBCallbacks数组中,当事件触发window.WebViewJavascriptBridge时,此任务队列将被刷新。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback);
}
window.WVJBCallbacks = [callback];
}
调用setupWebViewJavascriptBridge然后使用bridge来注册处理程序或调用 Java 处理程序:
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data);
responseCallback(data);
});
bridge.callHandler('ObjC Echo', {'key':'value'}, function(responseData) {
console.log("JS received response:", responseData);
});
});
3-2 Native 端实现沉侵式换肤功能
Native 端沉侵式换肤功能
Native 端包括标题栏、状态栏、导航栏
Web端包含页面背景
3-3 扫码测试网页功能
操作流程
手动输入测试地址,点击打开按钮,进行页面加载
通过扫码获取在线地址(草料文本二维码生成器)
WebSettings说明(史上最全的WebSettings说明_juruiyuan111的博客-CSDN博客)
3-4 测试原生API
首页注入Cookie下,测试原生API 方法入口
开放api JsCallNativeApi
四、经验分享
Android如何区分app原生和webview实现
一、抓包
这是比较原始,也是比较容易想到的,打开相应界面,抓取数据包看看,如果有url是返回比较完整的html代码,那基本就是webview来实现的了。
二、利用系统开发人员工具
打开“开发人员工具”功能,点击“设置”->“辅助功能”->“开发人员工具”,在绘图栏中找到“显示布局边界”并打开。这样所有应用的控件布局都一目了然了,webview作为一个控件,只有一个边界框,所以通过这一点,就比较容易区分出一个界面是webview实现的还是native布局控件实现的,当然也不排除用一堆webview来拼成一个界面的实现方法。
混合开发返回键处理
在混合开发的时候,android返回键默认每次按返回都是返回上一页,而有时候我们的需求并不是这样的,如果返回的界面是有弹窗的,那么我们点击返回键需求就是关闭弹窗,而不是返回上一页,那么这时候我们就需要提供接口来进行判断,是否是按需求来返回,还是默认返回,那具体是怎么实现的呢?
主要是重写onKeyDown方法 ,在h5端提供_Native_backListener()方法,返回String或是boolean,若是返回ture,则由h5端处理,若是返回false,则默认返回。
cookie注入
在开发过程中,我们有时会需要让Android原生 登录完成之后记录登录状态,然后在内嵌的 H5 页面也使用当前的登录账户,这个时候,我们可以采用 token 的方式,后台根据 token 方式,去加载对应页面数据。
浏览器调试webview功能
在Android 4.4(KitKat)或更高版本中,使用DevTools可以在原生Android应用中调试WebView内容。
1、在您的原生 Android 应用中启用 WebView 调试;在 Chrome DevTools 中调试 WebView。
2、通过 chrome://inspect 或者 edge://inspect 访问已启用调试的 WebView 列表。
JSSDK开发者文档:
更多推荐



所有评论(0)