Android WebView H5 Hybrid 混和开发

对于故乡,我忽然有了新的理解:人的故乡,并不止于一块特定的土地,而是一种辽阔无比的心情,不受空间和时间的限制;这心情一经唤起,就是你已经回到了故乡。——《记忆与印象》

前言

移动互联网发展至今,Android开发模式在不断更迭, 目前主要有三种开发模式 :原生开发、Hybrid开发以及跨平台开发。

  • 原生开发: 移动终端的开发主要分为两大阵营, Android(Java、Kotlin) 研发与 IOS(Swift)研发。
  • Hybrid开发: 多种技术栈混合开发App, 在Android中主要指Native与前端(JavaScript)技术的混合开发方式。
  • 跨平台研发: 同一个技术栈, 同一套代码可以在不同的终端上运行,极大的缩减了研发成本, 比如当下比较火的Flutter。

首先,我们需要做一些准备工作:为应用添加一个启用了 JavaScript 的 WebView,声明 INTERNET 权限(WebView 需此权限才能加载页面,即使页面内容为本地资源),在 Assets 资源文件夹中放置页面并加载。

Layout

    ...<WebViewandroid:id="@+id/webview"android:layout_width="match_parent"android:layout_height="match_parent"/>...

XML

Manifest

    <manifest ... ><uses-permission android:name="android.permission.INTERNET" />...</manifest>

XML

MainActivity

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.webkit.WebView;...@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to load the files in the assets folder}...

WebView & H5 Hybrid混合开发基础知识

H5 Runtime支撑 - 浏览器内核

对于Java来说, 最大的一个优点是build once run anywhere(一处编译处处运行), 这一优点主要是通过JVM在不同的平台解释执行(在Android端使用的是基于JVM针对低性能小内存的设备优化的dalvik和art虚拟机)。

对于前端技术栈来说, Runtime依赖浏览器的支持, 浏览器主要依赖内核驱动,内核的两个主要功能一个是界面渲染, 一个是JavaScript 引擎(JS语法解析),当前的主流浏览器以及内核:

浏览器渲染内核JS引擎
IE/Edge(微软)Trident; EdgeHtmlJScript; Chakra
Safari(苹果)Webkit/Webkit2JavaScripCore/Nitro(4+)
Chrome(Google)Chromium(Webkit);BlinkV8
FireFoxGeckoSpiderMonkey(❤️.0);TackMonkey(<4.0);JaegerMonkey(4.0+)
OperaPresto;BlinkFuthark(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年间由加州大学伯克利分校开发和发布的。

JSBridge

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

在这里插入图片描述

所谓 双向通信的通道:

  • JS 向 Native 发送消息 : 调用相关功能、通知 Native 当前 JS 的相关状态等。
  • Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。

JavascriptInterface

在 Android 和 Web 混合开发中,免不了 Java 与 JavaScript 代码相互调用,而 WebView 就给我们提供了这样一个接口:JavascriptInterface

public abstract @interface JavascriptInterface implements Annotation

Annotation that allows exposing methods to JavaScript. Starting from API level Build.VERSION_CODES.JELLY_BEAN_MR1 and above, only methods explicitly marked with this annotation are available to the Javascript code.

简单来说,在 Android 4.2 Jelly Bean(API 17)后,应用需要在方法中声明 @JavascriptInterface 注解,并将其所在类添加到 WebView 中,允许应用内启用了 JavaScript 的 WebView 直接调用其类成员方法。

MainActivity

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScriptmWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder}@SuppressWarnings("unused")public static class JavaScriptBridge {@JavascriptInterfacepublic void makeToast(final String message) {Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();}}...

WebPage

...
<script type="text/javascript">"use strict";window.Android.makeToast("Hello world");
</script>
...

HTML

上述示例代码将允许 JavaScript 通过 window.Android 对象,调用 JavaScriptBridge 类中声明了 @JavascriptInterface 注解的 makeToast 方法。运行后显示一个内容为 Hello world 的 Toast。


链接访问拦截

WebViewClient 提供了 shouldOverrideUrlLoading 事件,可以让我们在 URL 加载时做一些事情,比如拦截某个链接。

public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request)

Give the host application a chance to take control when a URL is about to be loaded in the current WebView. If a WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning true causes the current WebView to abort loading the URL, while returning false causes the WebView to continue loading the URL as usual.

MainActivity

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {if (request.getUrl().toString().equalsIgnoreCase("https://www.google.cn/")) {view.loadUrl("https://www.google.com/ncr");return true;} else if (request.getUrl().toString().startsWith("meowcat://open_settings")) {final Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.android.settings");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);mContext.startActivity(intent);return true;}return false;}});mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder}...

上述示例代码将在加载 https://www.google.cn/ 时跳转到 https://www.google.com/ncr*1,或在链接为 meowcat://open_settings 时打开系统设置。

除示例代码外,也可以直接 return true; 来中断页面加载。

注:该方法不适用于 POST 请求,页面在进行表单提交等 POST 请求时不会调用。


在页面内执行外部 JavaScript 代码

出于调试需求,我们可能需要通过 Java 代码在页面内执行一些 JavaScript 代码,使用 loadUrl(String)evaluateJavascript(String, ValueCallback<String>) 方法即可轻松实现该需求。若代码需要在页面加载完毕后执行,WebViewClient 也为我们提供了 onPageFinished 事件。

public void loadUrl (String url)

Loads the given URL.
Also see compatibility note on evaluateJavascript(String, ValueCallback).

public void evaluateJavascript (String script, ValueCallback resultCallback)

Asynchronously evaluates JavaScript in the context of the currently displayed page. If non-null, resultCallback will be invoked with any result returned from that execution. This method must be called on the UI thread and the callback will be made on the UI thread.
Compatibility note. Applications targeting Build.VERSION_CODES.N or later, JavaScript state from an empty WebView is no longer persisted across navigations like loadUrl(java.lang.String). For example, global variables and functions defined before calling loadUrl(java.lang.String) will not exist in the loaded page. Applications should use addJavascriptInterface(Object, String) instead to persist JavaScript objects across navigations.

public void onPageFinished (WebView view, String url)

Notify the host application that a page has finished loading. This method is called only for main frame. Receiving an onPageFinished() callback does not guarantee that the next frame drawn by WebView will reflect the state of the DOM at this point. In order to be notified that the current DOM state is ready to be rendered, request a visual state callback with WebView#postVisualStateCallback and wait for the supplied callback to be triggered.

MainActivity

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.setWebViewClient(new WebViewClient() {@Overridepublic void onPageFinished(WebView view, String url) {if (url.startsWith("https://www.google.")) {view.loadUrl("javascript:(() => {window.location = 'https://www.google.com/ncr';})();");// Equals with// view.evaluateJavascript("window.location = 'https://www.google.com/ncr';", null);}super.onPageFinished(view, url);}});mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder}...

上述示例代码将在页面加载完毕后,打开 https://www.google.cn/,而后被 shouldOverrideUrlLoading 方法跳转到 https://www.google.com/ncr

代码中 loadUrlevaluateJavascript 的示例等价,选用其一即可。

注:若使用 evaluateJavascript 方法的回调功能,则此方法与回调方法都必须在主线程(UI 线程)中执行或声明。


本地资源加载

在上面的示例代码中,我们使用了 file:///android_asset/ 来直接加载 assets 资源文件夹中的资源。但由于一些强制执行的安全策略(Content Security Policy)限制,使得该非同源 URL 无法正常被加载,这时候就可以使用 WebViewClient 提供的 shouldInterceptRequest 事件来辅助加载。

public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)

Notify the host application of a resource request and allow the application to return the data. If the return value is null, the WebView will continue to load the resource as usual. Otherwise, the return response and data will be used.
This callback is invoked for a variety of URL schemes (e.g., http(s):, data:, file:, etc.), not only those schemes which send requests over the network. This is not called for javascript: URLs, blob: URLs, or for assets accessed via file:///android_asset/ or file:///android_res/ URLs.
In the case of redirects, this is only called for the initial resource URL, not any subsequent redirect URLs.

MainActivity

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;import java.io.IOException;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScriptmWebView.setWebViewClient(new WebViewClient() {@Overridepublic void onPageFinished(WebView view, String url) {view.loadUrl("javascript:(() => {const script = document.createElement('script'); script.src = '/MeowCat-Android-Asset/www/js/main.js'; document.body.append(script);})();");super.onPageFinished(view, url);}@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) {String url = webResourceRequest.getUrl().toString();Uri uri = Uri.parse(url);String key = uri.getScheme() + "://" + uri.getHost() + "/MeowCat-Android-Asset/";if (url.contains(key)) {String assetsPath = url.replace(key, "");try {return new WebResourceResponse("text/plain", "UTF-8", getAssets().open(assetsPath));} catch (IOException e) {e.printStackTrace();}}return super.shouldInterceptRequest(view, webResourceRequest);}});mWebView.loadUrl("https://www.google.com/ncr");}@SuppressWarnings("unused")private static class JavaScriptBridge {@JavascriptInterfacepublic void makeToast(final String message) {Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();}}...

Main

"use strict";
window.Android.makeToast("Hello world");

JavaScript

上述示例代码将打开 https://www.google.com/ncr(因 shouldInterceptRequest 方法不会在加载特殊 Schemes 时被调用,故选用 Google 作为示例),页面加载完毕后插入 script 标签,加载并执行位于 file://android_asset/www/js/main.js 中的代码。运行后显示一个内容为 Hello world 的 Toast。

注:在 Android 官方开发文档 中,还有另一种使用 WebViewAssetLoader 的本地资源加载方式,感兴趣的可以自行研究一下,本文不再赘述。


JavaScript 弹窗提示

上面的示例代码已经可以帮助我们完成大多数需求,但在实际应用中发现了另外一个问题,JavaScript 的 alert() comfirm() prompt() 函数全部失效,这不是我们期望的行为。WebChromeClient 为我们提供了 onJsAlert onJsConfirm onJsPrompt 事件,分别对应上述函数,我们需要自行实现上述方法。

MainActivity

import androidx.appcompat.app.AlertDialog;import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setDefaultTextEncodingName("utf-8");mWebView.getSettings().setJavaScriptEnabled(true);mWebView.setWebChromeClient(new WebChromeClient() {@Overridepublic boolean onJsAlert(WebView view, String url, String message, final JsResult result) {onJsDialog(DialogType.ALERT, view, url, message, result, null, null);return true;}@Overridepublic boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {onJsDialog(DialogType.CONFIRM, view, url, message, result, null, null);return true;}@Overridepublic boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {onJsDialog(DialogType.PROMPT, view, url, message, null, defaultValue, result);return true;}});mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder}private enum DialogType {ALERT,CONFIRM,PROMPT}private static void onJsDialog(DialogType type, WebView view, String url, String message, final JsResult result, String defaultValue, final JsPromptResult promptResult) {AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());String[] content = message.split(":", 2);builder.setTitle(content[0]);builder.setMessage(content[1] + "\n" + url);builder.setCancelable(false);switch (type) {case PROMPT:builder.setPositiveButton(android.R.string.ok, (dialog, which) -> promptResult.confirm(defaultValue)); // TODO: Inputbreak;case CONFIRM:builder.setCancelable(true);builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel());case ALERT:default:builder.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm());}builder.create().show();}...

HTML

...
<script type="text/javascript">"use strict";alert("Alert Title:This is an alert");confirm("Confirm Title:This is a confirm") ? alert("Alert Title (Confirm):You confirmed the dialog") : alert("Alert Title (Confirm):You canceled the dialog");alert("Alert Title (Prompt):Prompt content is " + prompt("Prompt Title:This is a prompt", "Hello world"));
</script>
...

HTML

上述示例代码中,onJsDialog 方法统一处理了来自 WebChromeClient 的 onJsAlert onJsConfirm onJsPrompt 事件,添加了标题(JavaScript 函数仅支持信息传参,这里以第一个 : 作为标题和信息的分隔符),弹出对话框并返回;DialogType 用于判断事件类型。

运行后依次弹出对话框,内容分别为:

Alert Title
This is an alert
file:///android_asset/www/index.html[OK]
Confirm Title
This is a confirm
file:///android_asset/www/index.html[CANCEL] [OK]

若点击了 OK

Alert Title (Confirm)
You confirmed the dialog
file:///android_asset/www/index.html[OK]

若点击了 CANCEL

Alert Title (Confirm)
You canceled the dialog
file:///android_asset/www/index.html[OK]
Prompt Title
This is a prompt
file:///android_asset/www/index.html[OK]
Alert Title (Prompt)
Prompt content is Hello world
file:///android_asset/www/index.html[OK]

亦可根据其他需求定制对话框的样式和(或)功能。

注:onJsDialog 方法仅作为示例,并未实现 prompt() 函数的输入功能,以默认值返回。


实战:在页面中插入 vConsole 并在成功插入后弹出提示对话框

vConsole 是腾讯出品的一个轻量、可拓展、针对手机网页的前端开发者调试面板,可以在 Vue、React 或其他任何框架中使用。用于移动设备调试非常好用,下面的实例将使用本文所介绍的所有技巧,在页面底部插入 vConsole。

下载 vconsole.min.js 并保存至 assets 资源文件夹中:https://cdn.jsdelivr.net/npm/vconsole@latest/dist/vconsole.min.js

Java

import androidx.appcompat.app.AlertDialog;import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;import java.io.IOException;...@SuppressLint("StaticFieldLeak")private static Context mContext;@SuppressLint("SetJavaScriptEnabled")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = getApplicationContext();WebView mWebView = findViewById(R.id.webview);mWebView.getSettings().setJavaScriptEnabled(true);mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScriptmWebView.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {if (request.getUrl().toString().equalsIgnoreCase("https://www.google.cn/")) {view.loadUrl("https://www.google.com/ncr");return true;} else if (request.getUrl().toString().startsWith("meowcat://open_settings")) {final Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.android.settings");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);mContext.startActivity(intent);return true;}return false;}@Overridepublic void onPageFinished(WebView view, String url) {view.loadUrl("javascript:(() => {const script = document.createElement('script'); script.src='/MeowCat-Android-Asset/www/js/vconsole.min.js'; document.body.append(script); script.onload = () => {alert('vConsole:Loaded!'); if (typeof VConsole !== 'undefined') {new VConsole({onReady: () => {const vc = document.getElementById('__vconsole'); const vc_switch = vc.querySelector('.vc-switch'); vc.style.position = 'relative'; vc.style.zIndex = 9999999999; vc_switch.style.opacity = 'opacity' in this ? this.opacity : .5;},});}};})();");super.onPageFinished(view, url);}@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) {String url = webResourceRequest.getUrl().toString();Uri uri = Uri.parse(url);String key = uri.getScheme() + "://" + uri.getHost() + "/MeowCat-Android-Asset/";if (url.contains(key)) {String assetsPath = url.replace(key, "");try {return new WebResourceResponse("text/plain", "UTF-8", getAssets().open(assetsPath));} catch (IOException e) {e.printStackTrace();}}return super.shouldInterceptRequest(view, webResourceRequest);}});mWebView.setWebChromeClient(new WebChromeClient() {@Overridepublic boolean onJsAlert(WebView view, String url, String message, final JsResult result) {onJsDialog(DialogType.ALERT, view, url, message, result, null, null);return true;}@Overridepublic boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {onJsDialog(DialogType.CONFIRM, view, url, message, result, null, null);return true;}@Overridepublic boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {onJsDialog(DialogType.PROMPT, view, url, message, null, defaultValue, result);return true;}});mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to load the files in the assets folder}private enum DialogType {ALERT,CONFIRM,PROMPT}private static void onJsDialog(DialogType type, WebView view, String url, String message, final JsResult result, String defaultValue, final JsPromptResult promptResult) {AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());String[] content = message.split(":", 2);builder.setTitle(content[0]);builder.setMessage(content[1] + "\n" + url);builder.setCancelable(false);switch (type) {case PROMPT:builder.setPositiveButton(android.R.string.ok, (dialog, which) -> promptResult.confirm(defaultValue)); // TODO: Inputbreak;case CONFIRM:builder.setCancelable(true);builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel());case ALERT:default:builder.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm());}builder.create().show();}@SuppressWarnings("unused")private static class JavaScriptBridge {@JavascriptInterfacepublic void makeToast(final String message) {Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();}}...

WebPage

...
<script type="text/javascript">"use strict";alert("Alert Title:This is an alert");confirm("Confirm Title:This is a confirm") ? alert("Alert Title (Confirm):You confirmed the dialog") : alert("Alert Title (Confirm):You canceled the dialog");alert("Alert Title (Prompt):Prompt content is " + prompt("Prompt Title:This is a prompt", "Hello world"));window.Android.makeToast("Hello world");window.location = "https://www.google.cn/";
</script>
...

HTML

运行代码,最终您将能够看到如下提示:

vConsole
Loaded!
https://www.google.com/[OK]

然后在页面的右下角,会出现一个绿色按钮,上面写着 vConsole。我们做到了,那正是我们想要的。

常见问题

1. 前端如何调试WebView

  • 首先,要在WebView页面打开可以debug的设置。(不过只支持KITKAT以上版本)
scss 代码解读复制代码if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {mWeb.setWebContentsDebuggingEnabled(true);
}
  • Android端需要开启开发者模式, 然后打开usb调试, 最后插上电脑。
  • 在Chrome地址栏输入:Chrome://inspect。你会看到如下界面。

img

正常的话在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@Overridepublic 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);}}

引申阅读:在 Android 开发者文档 中,还有更多关于 Android WebView 混合开发的内容。


*1: NCR: No Country Redirect,Google 支持禁用地区跳转功能。

参考:Android WebView & H5 Hybrid开发知识点整理
DSBridge for Android
Java & V8 通讯
深入理解JSCore

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143931.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

用Python画一个五星红旗

#codingutf-8 import turtle import mathdef draw_polygon(aTurtle, size50, n3): 绘制正多边形args:aTurtle: turtle对象实例size: int类型&#xff0c;正多边形的边长n: int类型&#xff0c;是几边形 for i in range(n):aTurtle.forward(size)aTurtle.left(360.0/n)de…

esp32-C2 对接火山引擎实现语音转文本(二)

目录 一、 语音转文本初始化 二、 WedStream 事件处理函数 一、 语音转文本初始化 Volcengine_vtt_handle_t Volcengine_Vtt_Init(Volcengine_vtt_config_t *config) {// 管道配置audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();Volcengine_vtt_t *vt…

【从计算机的发展角度理解编程语言】C、CPP、Java、Python,是偶然还是应时代的产物?

参考目录 前言什么是"computer"?计算机的大致发展历程计算机系统结构阶段(1946~1981)计算机网络和视窗阶段(1982~2007)复杂信息系统阶段(2008~today)人工智能阶段 越新的语言是越好的吗、越值得学习吗&#xff1f; 前言 最近读了 《Python语言程序设计基础》 这本书…

数据结构与算法学习day21-回溯法

一、组合 1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2思路 把组合问题抽象成树形结构&#xff08;N叉树&#xff09; 每次从集合中选取元素&#xff0c;可选择的范围随着选择的进行而收缩&#xff0c;调整可选择的范围。 图中可以发现n相当于树的宽度&#xff0c…

[Linux]进程控制详解

1.创建进程 进程调用fork,当控制转移到内核中的fork代码后&#xff0c;内核做&#xff1a; ● 分配新的内存块和内核数据结构给子进程 ● 将父进程部分数据结构内容拷贝至子进程 ● 添加子进程到系统进程列表当中 ● fork返回&#xff0c;开始调度器调度 这个前面提到过&#…

c++基础入门三

文章目录 C基础入门(三)auto关键字auto简介使用细则一、可以和指针联合使用二、在一行定义多个变量 不能使用场景一、不能作为函数的参数二、不能用来声明数组 基于for的循环使用条件 指针空值nullptr C基础入门(三) 回顾上集&#xff0c;我们介绍了C的函数重载&#xff0c;引…

JAVA并发编程系列之Semaphore信号量剖析

腾讯T2面试&#xff0c;现场限时3分钟限最多20行代码&#xff0c;模拟地铁口安检进站。其中安检入口10个&#xff0c;当前排队人数是100个&#xff0c;每个人安检进站耗时5秒。开始吧! 候选人&#xff0c;心中万马奔腾&#xff01;&#xff01;&#xff01;吐了一口82年老血&am…

电池管理仓的拆解

拆解视频里面可以学习到大厂的设计思想和创意&#xff0c;接触到比较行业化的设计方案&#xff0c;从而提升设计电路的水平。 电池仓&#xff1a; 电池管家的芯片用的就是前段时间了解到的STM32G030C8T6&#xff0c;便宜好用的典范&#xff1a; 弧形走线较为推荐&#xff1a; …

C++初阶学习——探索STL奥秘——标准库中的queue与stack

1、适配器模式 STL 中的适配器可以分为三类: 从应用角度出发 容器适配器 container adapters 迭代器适配器 iterator adapters 仿函数适配器 functor adapters 其中&#xff0c;容器适配器可修改底层为指定容器 如由 vector 构成的栈、由 list 构成的队列 迭代器适配器…

sqli-labs靶场搭建

下载了一个phpstudy进行搭靶场搭建 然后打开phpstudy安装好php,mysql等环境 正式sqli-labs靶场搭建 第一步&#xff1a;下载源码&#xff1a;https://codeload.github.com/Audi-1/sqli-labs/zip/master 解压后放进网站根目录&#xff0c;进到 sqli-labs的文件夹下&#xff0…

windows C++ 并行编程-异步代理库概述

异步代理库&#xff08;简称代理库&#xff09;提供了一个编程模型&#xff0c;该模型可提高支持并发的应用程序开发的可靠性。 代理库是一个 C 模板库&#xff0c;为粗粒度数据流和管道任务提升了基于角色的编程模型和进程内消息传递。 代理库构建在并发运行时的计划和资源管理…

Windows系统通过部署wsl + Goland进行跨平台开发

1.背景 近期项目中因为用到了 Golang库中的 "log/syslog" 包,而这个包是禁止在windows平台上编译的. 并且在windows环境上开发也会有诸多不便,如执行makefile文件的make命令,本地开发环境中docker,etcd,redis的搭建等等,而这些通过部署wsl去搭建一个linux环境就很可以…

如何使用下拉字段创建WordPress表单(简单方法)

许多网站所有者在收集用户输入时&#xff0c;都会因为表单过长而让用户感到压迫。 下拉列表字段通过提供一个简洁的选项列表&#xff0c;使表单变得更简单。这意味着它们可以提高表单完成率&#xff0c;并改善用户体验。 在本文中&#xff0c;我们将向您展示如何创建带有下拉…

Kubernetes从零到精通(11-CNI网络插件)

Kubernetes网络模型 Kubernetes的网络模型&#xff08;Kubernetes Networking Model&#xff09;旨在提供跨所有节点、Pod和服务的统一网络连接。它的核心理念是通过统一的网络通信规则&#xff0c;保证集群中的所有组件能够顺畅地相互通信。Kubernetes网络模型主要有以下几个关…

专业学习|随机规划概观(性质、针对问题与分类)

一、随机规划概观 随机规划&#xff08;Stochastic Programming&#xff09;是一种用于处理决策问题中的不确定性的优化方法。它能够在决策过程中考虑到未来的不确定性&#xff0c;从而帮助找到在不同情境下都能较好表现的解决方案。以下是随机规划能解决的一些主要问题以及它的…

阿里巴巴搜索API返回值:电商市场竞争的新武器含

阿里巴巴搜索API返回值在电商市场竞争中扮演着至关重要的角色&#xff0c;它为企业提供了深入了解市场、分析竞争对手的宝贵资源。以下是对阿里巴巴搜索API返回值及其在电商市场竞争中应用的详细解析&#xff0c;并附上示例代码。 一、阿里巴巴搜索API返回值概述 阿里巴巴搜索…

超大酒店司机收布草-酒店分层管理--酒店布草洗涤

一、大酒店布草分层管理 1. 提高效率 - 对布草进行分层&#xff0c;可以更有针对性地安排收集和分发流程&#xff0c;减少混乱和等待时间&#xff0c;提高整体运营效率。 2. 质量控制 - 不同层级的布草可能有不同的质量标准和使用场景。分层管理有助于确保每个层级的…

2024年第五届“华数杯”全国大学生数学建模竞赛 A题详细思路+详细matlab代码

没有更新完之前,专栏价格为59,更新完毕之后恢复到99. 专栏内包含2024年所有数学建模比赛思路和代码,有些重要比赛着重更新(华数杯、国赛、美赛),小比赛可能会有chatgpt4更新,只需订阅一次。有些文章没有完整代码,请到专栏内查找最新代码和思路。如果比赛结束后没有更新…

Web后端开发技术:RESTful 架构详解

RESTful 是一种基于 REST&#xff08;表述性状态转移&#xff0c;Representational State Transfer&#xff09;架构风格的 API 设计方式&#xff0c;通常用于构建分布式系统&#xff0c;特别是在 Web 应用开发中广泛应用。REST 是一种轻量级的架构模式&#xff0c;利用标准的 …

大语言模型超参数调优:开启 AI 潜能的钥匙

前言 在人工智能的广袤领域中&#xff0c;大语言模型&#xff08;LLM&#xff09;凭借其强大的实力&#xff0c;不断重塑着我们对机器理解语言的认知。然而&#xff0c;要使这些模型在特定应用场景中发挥最大效能&#xff0c;关键在于巧妙调整其超参数。本文将引领你深入探究 …