使用 AndroidX 增强 WebView 的能力
作者:Qinglin
本文转载自「简绘Android」。
在应用开发过程中,为了在多个平台上保持一致的用户体验和提高开发效率,许多应用程序选择使用 H5 技术。在 Android 平台上,通常使用 WebView 组件来承载 H5 内容以供展示。
WebView 的实现基于 Chromium 开源项目,而 Android 则基于 AOSP 项目,这两个项目有着不同的发布周期,WebView 往往一个月就可以推出下一个版本,而 Android 则需要一年的时间,对于 WebView 新增的 Feature 我们最迟需要一年才能使用。
“胶水层” 是在某个版本之后才后才支持的,旧版本的 WebView 内核并不支持,这也是为什么在调用之前始终应该检查 isFeatureSupported 的原因。
向下兼容
需要注意的是在调用之前对 WebViewFeature 的检查,对于每个 Feature ,AndroidX Webkit 会取平台和 WebView 所提供 Feature 的并集 ,在调用某个 API 之前必须进行检查,如果平台和 WebView 均不支持该 API 则将抛出 UnsupportedOperationException 异常。
// Old code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
WebView.startSafeBrowsing(appContext, callback);
}
// New code:
if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
WebViewCompat.startSafeBrowsing(appContext, callback);
}
// WebViewCompat#startSafeBrowsing
public static void startSafeBrowsing(@NonNull Context context,
@Nullable ValueCallback<Boolean> callback) {
ApiFeature.O_MR1 feature = WebViewFeatureInternal.START_SAFE_BROWSING;
if (feature.isSupportedByFramework()) {
ApiHelperForOMR1.startSafeBrowsing(context, callback);
} else if (feature.isSupportedByWebView()) {
getFactory().getStatics().initSafeBrowsing(context, callback);
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
}
代理功能支持
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
ProxyConfig proxyConfig = new ProxyConfig.Builder()
.addProxyRule("localhost:7890") //添加要用于所有 URL 的代理
.addProxyRule("localhost:1080") //优先级低于第一个代理,仅在上一个失败时应用
.addDirect() //当前面的代理失败时,不使用代理直连
.addBypassRule("www.baidu.com") //该网址不使用代理,直连服务
.addBypassRule("*.cn") //以.cn结尾的网址不使用代理
.build();
Executor executor = ...
Runnable listener = ...
ProxyController.getInstance().setProxyOverride(proxyConfig, executor, listener);
}
白名单代理
安全的 WebView 和 Native 通信支持
// App (in Java)
WebMessageListener myListener = new WebMessageListener() {
@Override
public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
// do something about view, message, sourceOrigin and isMainFrame.
replyProxy.postMessage("Got it!");
}
};
HashSet<String> allowedOriginRules = new HashSet<>(Arrays.asList("[https://example.com](https://example.com/ "https://example.com")"));
// Add WebMessageListeners.
WebViewCompat.addWebMessageListener(webView, "replyObject", allowedOriginRules,myListener);
// Web page (in JavaScript)
myObject.onmessage = function(event) {
// prints "Got it!" when we receive the app's response.
console.log(event.data);
}
myObject.postMessage("I'm ready!");
文件传递
// App (in Java)
WebMessageListener myListener = new WebMessageListener() {
@Override
public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
// Communication is setup, send file data to web.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
// Suppose readFileData method is to read content from file.
byte[] fileData = readFileData("myFile.dat");
replyProxy.postMessage(fileData);
}
}
}
// Web page (in JavaScript)
myObject.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
const data = event.data; // Received file content from app.
const dataView = new DataView(data);
// Consume file content by using JavaScript DataView to access ArrayBuffer.
}
}
myObject.postMessage("Setup!");
// Web page (in JavaScript)
const response = await fetch('example.jpg');
if (response.ok) {
const imageData = await response.arrayBuffer();
myObject.postMessage(imageData);
}
// App (in Java)
WebMessageListener myListener = new WebMessageListener() {
@Override
public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
if (message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
byte[] imageData = message.getArrayBuffer();
// do something like draw image on ImageView.
}
}
};
深色主题的支持
以 Android 12 或更低版本为目标平台的应用 API 设计过于复杂,以 Android 13 或更高版本为目标平台的应用精简了 API ,具体变更请参考官方文档[3]
JavaScript and WebAssembly 执行引擎支持
if(!JavaScriptSandbox.isSupported()){
return;
}
//连接到引擎
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
//创建上下文 上下文间有简单的数据隔离
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
//执行函数 && 获取结果
final String code = "function sum(a, b) { let r = a + b; return r.toString(); }; sum(3, 4)";
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Futures.addCallback(resultFuture,
new FutureCallback<String>() {
@Override
public void onSuccess(String result) {
text.append(result);
}
@Override
public void onFailure(Throwable t) {
text.append(t.getMessage());
}
},
mainThreadExecutor); //Wasm运行
final byte[] hello_world_wasm = {
0x00 ,0x61 ,0x73 ,0x6d ,0x01 ,0x00 ,0x00 ,0x00 ,0x01 ,0x0a ,0x02 ,0x60 ,0x02 ,0x7f ,0x7f ,0x01,
0x7f ,0x60 ,0x00 ,0x00 ,0x03 ,0x03 ,0x02 ,0x00 ,0x01 ,0x04 ,0x04 ,0x01 ,0x70 ,0x00 ,0x01 ,0x05,
0x03 ,0x01 ,0x00 ,0x00 ,0x06 ,0x06 ,0x01 ,0x7f ,0x00 ,0x41 ,0x08 ,0x0b ,0x07 ,0x18 ,0x03 ,0x06,
0x6d ,0x65 ,0x6d ,0x6f ,0x72 ,0x79 ,0x02 ,0x00 ,0x05 ,0x74 ,0x61 ,0x62 ,0x6c ,0x65 ,0x01 ,0x00,
0x03 ,0x61 ,0x64 ,0x64 ,0x00 ,0x00 ,0x09 ,0x07 ,0x01 ,0x00 ,0x41 ,0x00 ,0x0b ,0x01 ,0x01 ,0x0a,
0x0c ,0x02 ,0x07 ,0x00 ,0x20 ,0x00 ,0x20 ,0x01 ,0x6a ,0x0b ,0x02 ,0x00 ,0x0b,
};
final String jsCode = "android.consumeNamedDataAsArrayBuffer('wasm-1').then(" +
"(value) => { return WebAssembly.compile(value).then(" +
"(module) => { return new WebAssembly.Instance(module).exports.add(20, 22).toString(); }" +
")})";
boolean success = js.provideNamedData("wasm-1", hello_world_wasm);
if (success) {
FluentFuture.from(js.evaluateJavaScriptAsync(jsCode))
.transform(this::println, mainThreadExecutor)
.catching(Throwable.class, e -> println(e.getMessage()), mainThreadExecutor);
} else {
// the data chunk name has been used before, use a different name
}
更多支持
AndroidX Webkit 是一个功能强大的库,由于篇幅原因上文将开发者比较常用的功能进行了列举,AndroidX 还提供对 WebView 更精细化的控制,对 Cookie 的便捷访问、对 Web 资源的便捷访问,对 WebView 性能的收集,还有对大屏幕的支持等等强大的 API,大家可以查看发布页面[5]查看最新的功能。
参考资料
[1]prefers-color-scheme:
https://www.w3.org/TR/mediaqueries-5/#descdef-media-prefers-color-scheme
[2]color-scheme:
https://www.w3.org/TR/css-color-adjust-1/#propdef-color-scheme
[3]官方文档:
https://developer.android.com/develop/ui/views/layout/webapps/dark-theme#12
[4]AndroidX JavascriptEngine:
https://developer.android.com/jetpack/androidx/releases/javascriptengine?hl=zh-cn
[5]发布页面:
https://developer.android.com/jetpack/androidx/releases/webkit?hl=zh-cn#declaring_dependencies
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!