Android WebView & H5 Hybrid开发简单讲两句

一、前言

移动互联网发展至今,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、webGL、webGPU关系:

    • three.js: 是基于目前浏览器的3D图形渲染标准--webGL 或者 webGPU进行封装的3D库。

    • WebGL: 是一套与openGL ES 绑定的 API,而openGL ESopenGL 三维图形 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拦截框架

github.com/acsbendi/An...

原理

通过注入的方式重写了原始前端的 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);
        }
    }
相关推荐
帅次12 天前
Flutter Expanded 与 Flexible 详解
android·flutter·ios·小程序·webview
帅次13 天前
Flutter ListView 详解
android·flutter·ios·iphone·webview
奇舞精选17 天前
web api 新特性--画中画
web3·webview
高林雨露1 个月前
WebView加载URL时添加时间戳可以防止缓存问题方案
webview
帅次1 个月前
Flutter DropdownButton 详解
android·flutter·ios·kotlin·gradle·webview
帅次2 个月前
Flutter 基础组件 Scaffold 详解
android·flutter·ios·kotlin·objective-c·webview·android-studio
帅次2 个月前
Flutter:StatelessWidget vs StatefulWidget 深度解析
android·flutter·ios·小程序·swift·webview·android-studio
AmazonUnicon2 个月前
WebView中操作视频播放,暂停
webview
仙魁XAN2 个月前
Flutter 学习之旅 之 flutter 使用 webview_flutter 进行网页加载显示
flutter·web·webview·网页显示·webview_flutter
御承扬2 个月前
从零开始开发纯血鸿蒙应用之网页浏览
华为·harmonyos·webview