一、前言
移动互联网发展至今,Android开发模式在不断更迭, 目前主要有三种开发模式 :原生开发、Hybrid开发以及跨平台开发。
- 原生开发: 移动终端的开发主要分为两大阵营, Android(Java、Kotlin) 研发与 IOS(Swift)研发。
- Hybrid开发: 多种技术栈混合开发App, 在Android中主要指Native与前端(JavaScript)技术的混合开发方式。
- 跨平台研发: 同一个技术栈, 同一套代码可以在不同的终端上运行,极大的缩减了研发成本, 比如当下比较火的Flutter。
二、WebView & H5 Hybrid混合开发基础知识
1. H5 Runtime支撑 - 浏览器内核
对于Java来说, 最大的一个优点是build once run anywhere(一处编译处处运行), 这一优点主要是通过JVM在不同的平台解释执行(在Android端使用的是基于JVM针对低性能小内存的设备优化的dalvik和art虚拟机)。
对于前端技术栈来说, Runtime依赖浏览器的支持, 浏览器主要依赖内核驱动,内核的两个主要功能一个是界面渲染 , 一个是JavaScript 引擎(JS语法解析),当前的主流浏览器以及内核:
浏览器 | 渲染内核 | JS引擎 |
---|---|---|
IE/Edge(微软) | Trident; EdgeHtml | JScript; Chakra |
Safari(苹果) | Webkit/Webkit2 | JavaScripCore/Nitro(4+) |
Chrome(Google) | Chromium(Webkit);Blink | V8 |
FireFox | Gecko | SpiderMonkey(<3.0);TackMonkey(<4.0);JaegerMonkey(4.0+) |
Opera | Presto;Blink | Futhark(9.5-10.2);CaraKan(10.5) |
Chromium 是 Google 公司一个开源浏览器项目,使用 Blink 渲染引擎,V8 是 Blink 内置的JavaScript 引擎, Android端的WebView是基于Chromium的移动端浏览器组件。当前Android和IOS移动端的浏览器内核说到底都是基于Webkit。

WebKit主要分为四个部分:
- 最上层 WebKit Embedding API 是 Browser UI 进行交互的 API 接口
- 最下层 Platform API 提供与底层驱动的交互,如网络,字体渲染,影音文件解码,渲染引擎等
- WebCore 它实现了对文档的模型化,包括了 CSS, DOM, Render 等的实现
- JSCore 是专门处理 JS 脚本的引擎, 以及Hybrid通信支持
WebKit 所包含的绘制引擎 和 JS引擎,均是从KDE的KHTML及KJS引擎衍生而来。它们都是自由软件,在GPL条约下授权,同时支持BSD系统的开发。所以Webkit也是自由软件,同时开放源代码。
KDE: K桌面环境(K Desktop Environment)的缩写。一种著名的运行于 Linux、Unix 以及FreeBSD 等操作系统上的自由图形桌面环境
GNU: 通用公共许可协议(英语:GNU General Public License,缩写GNU GPL 或 GPL),是被广泛使用的自由软件许可证,给予了终端用户运行、学习、共享和修改软件的自由。
BSD: Berkeley Software Distribution,伯克利软件套件,是Unix的衍生系统,在1977至1995年间由加州大学伯克利分校开发和发布的。
2. H5 与 Native 研发对比
对比项 | H5 | Native | 备注 |
---|---|---|---|
加载速度 | 差 | 优 | H5依赖网络进行资源加载。Native本地静态加载。 |
迭代更新 | 优 | 差 | H5支持动态更新, 每次启动可从Server端获取最新资源。Native需要安装包安装更新,当然现在也有热加载的方案。 |
开发成本 | 优 | 差 | H5可运行在有浏览器环境的任何设备上。Native项目只可运行在所属的系统环境下(Android、IOS)。 |
原生支持 | 差 | 优 | H5在使用系统API时需要依赖原生支持或框架, 并且不是所有功能都能完美支持。Native使用原生的SDK即可对于系统API方便调用。 |
3. Android Native & H5 Hybrid方案
(主要目标是为H5提供Runtime环境, 并能与之进行双向通信。)
- Android WebView
WebView是Android提供的原生浏览器组件, 优点不需要额外引入库, 缺点是不同设备上会存在兼容问题, 市面上的Andorid设备浏览器内核版本会有较大跨度。
- 在Android4.4以前,WebView是Android Webkit浏览器内核,很多HTML5标准语法不支持,比如indexeddb、webgl等,canvas性能也非常差。
- Android4.4起,基于chromium采用blink内核,性能和现代语法支持大幅提升。
- Android5开始,WebView脱离ROM可单独更新,伴随着chrome的发版,Google会在Google play store上同步更新Android System WebView。
- X5 WebView
官网:x5.tencent.com/docs/index....
腾讯浏览器服务, 提供了一套兼容性解决方案, 腾讯生态下的浏览器、微信等产品都在用。需要学习API,引入会增大包体积,以及需要关注版本对于W3C标准的支持程度。
- CrossWalk(停止维护)
Github:github.com/crosswalk-p...
官方说明:Crosswalk is an app runtime based on Chromium/Blink. 可理解为在项目中固定了内核版本。
4. 引申
-
J2V8 : github.com/eclipsesour... (Java & V8 通讯)
-
深入理解JavascripCore: tech.meituan.com/2018/08/23/...
-
前端图形渲染
-
three.js: 是基于目前浏览器的3D图形渲染标准--webGL 或者 webGPU进行封装的3D库。
-
WebGL: 是一套与openGL ES 绑定的 API,而openGL ES 是 openGL 三维图形 API 的子集,是针对手机、PDA和游戏主机等嵌入式设备而设计的API,openGL 存在于Windows,部分UNIX平台和Mac OS。
-
WebGPU: 是一套基于浏览器的图形API,浏览器封装了现代图形API(Dx12、Vulkan、Metal),提供给Web 3D程序员,为 Web释放了更多的GPU 硬件的功能。引擎较新,设计上更好的反映了GPU硬件技术这些年新的发展,能提供更好的性能,支持多线程,采用了偏面向对象的编程风格。
-
二、Android WebView & H5 Hybrid开发
1. WebView使用
WebView的使用主要包括:WebView类 及其 工具类(WebSettings类、WebViewClient类、WebChromeClient类)。

2.JSBridge
JSBridge 是一座 Native 与 JavaScript 进行通讯的桥梁,它的核心是 构建 Native 和 JavaScript 双向通信的通道。

所谓 双向通信的通道:
- JS 向 Native 发送消息 : 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
3.Android调用JS
主要是以下两种方法:
-
loadUrl()
兼容所有版本, 效率低,传输大小有2M限制。 -
evaluateJavascript()
4.4之后建议使用,效率高, 可直接获取返回值,避免传输大数据会OOM。
具体使用方法如下:
JS端
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>test</title>
</head>
<body>
<script>
function helloWolrd(){
console.log("Hello world!")
return "hello world"
}
</script>
</body>
</html>
Native端
java
//开放浏览器JS代码执行权限
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
//调用JS方法
mWebView.loadUrl("javascript:helloWolrd()");
mWebView.evaluateJavascript("javascript:helloWolrd()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {//此处为 js 返回的结果}
));
4.JS调用Android
原生支持主要有三种方法
- 请求拦截方式:通过 WebViewClient 的 shouldInterceptRequest() 请求拦截。
- 弹窗拦截方式:通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt()方法拦截JS对话框alert()、confirm()、prompt()的消息。
- JS上下文注入方式:通过WebView的addJavascriptInterface()方法注入。
请求拦截方式
Web端发送网络请求, 会被Native端拦截
-
JS端通过 XMLHttpRequest 与 Fetch API 发送请求
-
Native端拦截代码:
java
webView.setWebViewClient(new WebViewClient() {
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
//通过 request可获取web发起的请求url 和 header信息
}
});
弹窗拦截
前端可以发起很多种弹窗包含:
- alert() 弹出个提示框,只能点确认无回调
- confirm() 弹出个确认框(确认,取消),可以回调
- prompt() 弹出个输入框,让用户输入东西,可以回调
JS端代码
js
var data = {
action:'xxxx',
params:'xxxx',
callback:'xxxx',
};
var jsonData = JSON.stringify([data]);
//发起弹框
prompt(jsonData);
Native代码
java
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
//1 根据传来的字符串反解出数据,判断是否是所需要的拦截而非常规H5弹框
if (是){
//2 取出指令参数,确认要发起的native调用的指令是什么
//3 取出数据参数,拿到JS传过来的数据
//4 根据指令调用对应的native方法,传递数据
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
JS上下文注入方式(常用)
注入 API 方式的主要原理是,通过 WebView 提供的接口,向JavaScript 的 Context(window)中注入对象,让 JavaScript 可以调用 Native 端的代码逻辑。
Native端注入代码
java
class NativeInterface {
...
// 开启js脚本执行权限
webSittings.setJavaScriptEnabled(true);
//在window上绑定一个名为xxx对象用来访问native提供的方法
webView.addJavascriptInterface(this, "xxx");
...
@JavascriptInterface
public void(String\int\Boolean) methodName(String url, String method){
}
}
JS 调用代码
js
window.xxx.methodName();
5.框架
Hack拦截框架
原理
通过注入的方式重写了原始前端的 fetch 和 xmlrequest的api,这种方式不推荐。
DSBridge 框架
Github地址:github.com/wendux/DSBr...
DSBridge是什么
DSBridge是一个三端(Android、IOS、Javascript)易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数。
DSBridge基础使用
- Android端与Web端引入 DSBridge 库
- JS调用Android端
java
// 在Android API中定义方法
public class JsEchoApi{
//同步API
@JavascriptInterface
public Object syn(Object args) throws JSONException {
return args;
}
//异步API
@JavascriptInterface
public void asyn(Object args,CompletionHandler handler){
handler.complete(args);
}
}
// 添加API到命名空间echo中
dwebView.addJavascriptObject(new JsEchoApi(),"echo");
// JS 调用 Navtive
// call echo.syn 同步方法
var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1})
alert(JSON.stringify(ret))
// call echo.asyn 异步方法
dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},
function (ret) {
alert(JSON.stringify(ret));
})
// 通过命名空间名称移除相应的Java API object 。
dwebview.removeJavascriptObject(String namespace)
- Android端调用JS
javascript
// Js中注册方法
// 同步方法
dsBridge.register('addValue',function(l,r){
return l+r;
})
// 异步方法
dsBridge.registerAsyn('append',function(arg1,arg2,arg3,responseCallback){
responseCallback(arg1+" "+arg2+" "+arg3);
})
// Java中调用JS方法
// 调用同步方法
webView.callHandler("addValue",new Object[]{1,6},new OnReturnValue<String>(){
@Overridepublic void onValue(String retValue) {
Log.d("jsbridge","call succeed,return value is: "+retValue);
}
});
// 调用异步方法
webView.callHandler("append",new Object[]{"I","love","you"},new OnReturnValue<String>(){
@Overridepublic void onValue(String retValue) {
Log.d("jsbridge","call succeed, append string is: " + retValue);
}
});
- DSBridge 的优点
- 简单易用:主要就两个类,在Android使用DWebView, 在JS中使用DsBridge。
- 高效稳定:底层使用原生JS注入方式实现,保证了数据传输的安全和稳定性。
- 开源免费:可以基于业务需求进行一些定制化操作。
三、常见问题
1. 前端如何调试WebView
- 首先,要在WebView页面打开可以debug的设置。(不过只支持KITKAT以上版本)
scss
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWeb.setWebContentsDebuggingEnabled(true);
}
- Android端需要开启开发者模式, 然后打开usb调试, 最后插上电脑。
- 在Chrome地址栏输入:Chrome://inspect。你会看到如下界面。

正常的话在App中打开WebView时,chrome中会监听到并显示页面。
- 点击页面下的inspect,就可以实时看到手机上WebView页面的显示状态了。

2.JS 如何传递 Uint8Array到 Android端:
- 方法1: 注入参数为
String data
的方法。通过Base64作为传输载体, 前端将Uint8Array数据转Base64, Native侧将Base64解析为byte[]。 - 方法2: 注入参数为
byte[] bytes
的方法。
直接传递字符串, 无论字符串多长,传递时间都在 10ms内, 推断字符串传递可能采用内存映射, 直接传递内存地址.
传递uint8array, 数据越长时间越长, 推断可能底层涉及某些转换操作, 从 js uint8 到 java byte。
3.Android端 如何加载本地前端资源
- 资源文件放置Assert文件夹中
标签加载
ini
<script type="module"
crossorigin src="/android_asset/parkingtest/dist/assets/index.34d4f8c4.js"/>
<link rel="stylesheet"
href="/android_asset/parkingtest/dist/assets/index.cf521aaf.css">
代码加载URL
arduino
"file:///android_asset/xxx/xxx/src.js"
- 资源文件放在本地SD存储
通过请求拦截方式, 拦截前端资源请求, 获取需要加载的文件名称,通过JAVA IO 加载 File 返回给前端。
加载代码(伪代码)
scala
public class MyWebViewClient extends WebViewClient {
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
String fileName = Fileurl.getFileName();
ByteArrayInputStream fileStream = JavaIO.loadFile(filePath + fileName);
return new WebResourceResponse(
mimeType,
encoding,
statusCode,
reasonPhrase,
responseHeaders,
byteArrayInputStream);
}
}