Utilisez AndroidX pour améliorer la capacité de WebView

Dans le processus de développement d'applications, afin de maintenir une expérience utilisateur cohérente et d'améliorer l'efficacité du développement sur plusieurs plates-formes, de nombreuses applications choisissent d'utiliser la technologie H5. Sur la plate-forme Android, le composant WebView est généralement utilisé pour héberger du contenu H5 à afficher.

Problèmes avec les vues Web

Depuis Android Lollipop, la mise à jour du composant WebView est indépendante de la plateforme Android. Cependant, l'API (android.webkit) qui contrôle le WebView est toujours pertinente pour les mises à niveau de la plate-forme. Cela signifie que les développeurs d'applications ne peuvent utiliser que les interfaces définies par la plate-forme actuelle, mais ne peuvent pas utiliser pleinement toutes les fonctionnalités de WebView. Par exemple : WebView.startSafeBrowsingl'API est ajoutée sur Android 8.1 et cette fonctionnalité est fournie par WebView. Même si nous mettons à jour WebView pour avoir cette fonctionnalité dans Android 7.0, étant donné qu'Android 7.0 n'a pas d'API WebView.startSafeBrowsing, nous ne pouvons pas utiliser cette fonctionnalité.

L'implémentation de WebView est basée sur le projet open source Chromium , tandis qu'Android est basé sur le projet AOSP. Ces deux projets ont des cycles de publication différents. WebView peut généralement lancer la prochaine version en un mois, tandis qu'Android prend un an. Pour le nouveau Fonction WebView Nous avons besoin d'un an au plus tard pour l'utiliser.

L'émergence d'AndroidX Webkit

Afin de résoudre l'inadéquation ci-dessus entre les capacités de la plate-forme et WebView, nous pouvons définir un ensemble d'API WebView indépendamment de la plate-forme, et le laisser mettre à jour l'API avec la fonctionnalité WebView, qui résout le problème existant mais en introduit un autre. connectez l'API WebView nouvellement définie et WebView.

Du point de vue du développement d'applications, le système WebView est difficile à modifier, c'est une bonne solution pour compiler et personnaliser soi-même une WebView et la fournir avec l'apk. À ce stade, nous pouvons facilement résoudre le problème de connexion et ajouter et modifier arbitrairement la fonctionnalité en fonction des besoins sans attendre la mise à jour officielle. En même temps, il résout le problème de compatibilité et de fragmentation du noyau WebView. Tencent X5, UC U4, etc. sont tous cette solution. Maintenir une WebView n'est pas une tâche facile et nécessite plus de main-d'œuvre, car l'emballage de WebView dans un package s'accompagne d'une forte augmentation de la taille du package.

Du point de vue officiel d'Android, WebView peut être poussé en amont pour prendre en charge l'API WebView, et c'est la solution d'AndroidX Webkit. Android met officiellement l'API WebView définie dans la bibliothèque AndroidX Webkit pour prendre en charge les mises à jour fréquentes, et ajoute une "couche de colle" en amont de WebView pour se connecter à AndroidX Webkit, de sorte que sur l'ancienne version de la plate-forme Android, tant que la "colle " la couche est installée La WebView du code a aussi les fonctions de la nouvelle version de la plateforme.

La "couche de colle" n'est prise en charge qu'après une certaine version, et l'ancienne version du noyau WebView ne la prend pas en charge, c'est pourquoi vous devez toujours vérifier avant d'appeler isFeatureSupported.

Fonctionnalités d'AndroidX Webkit

Après une compréhension préliminaire des principes de génération et de mise en œuvre d'AndroidX Webkit, examinons les nouvelles fonctionnalités qu'il offre pour améliorer notre WebView.

rétrocompatible

Comme analysé ci-dessus, AndroidX Webkit offre une rétrocompatibilité, comme indiqué dans le code suivant, fourni par WebViewCompatdes appels d'interface compatibles.

需要注意的是在调用之前对 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 的外衣查看他的源码(如下所示),会发现如果在当前版本 Platform API 提供了接口,就会直接调用 Platform API 的接口,而对于低版本,则由 AndroidX Webkit 和 WebView 的"通道"提供服务。

// 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();
    }
}

对比上面的代码,使用平台 API(old code)时仅可以支持 90% 的用户,而使用 AndroidX Webkit(new code) 则可以覆盖大约 99% 的用户。

image.png

代理功能支持

一直以来WebView 的代理设置异常繁琐,当遇到复杂的代理规则就无能为力了。在 AndroidX Webkit 中增加了 ProxyController API 用于为 WebView 设置代理。ProxyConfig.Builder 类提供了设置代理以及配置代理的绕过方式等方法,通过组合可以满足复杂的代理场景。

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 设置了两个代理服务器,localhost:1080 仅当 localhost:7890 失败的情况下启用,addDirect 声明了如果两个服务器都失败则直连服务器,addBypassRule 规定了 www.baidu.com 和以 .so 结尾的域名始终不应该使用代理。

白名单代理

如果仅有少量的 URL 需要配置代理,我们可以使用 setReverseBypassEnabled(true) 方法将addBypassRule 添加的 URL 转变为使用代理服务器,而其他的 URL 则直连服务。

安全的 WebView 和 Native 通信支持

建立 WebView 和 Native 的双向通信是使用 Hybrid 混合开发模式的基础,在之前 Android 已经提供了一些机制能够让完成基本的通信,但是已有的接口都存在一些安全和性能问题,在 AndroidX 中增加了一个功能强大的接口 addWebMessageListener 兼顾了安全和性能等问题。

代码示例中将 JavaSript 对象 replyObject 注入到匹配 allowedOriginRules的上下文中,这样只有在可信的网站中才能被使用此对象,也就防止了不明来源的网络攻击者对该对象的利用。

// 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/)"));
// Add WebMessageListeners.

WebViewCompat.addWebMessageListener(webView, "replyObject", allowedOriginRules,myListener);

调用上述方法之后,在 JavaScript 上下文中我们就可以访问 myObject ,调用 postMessage 就可以回调 Native 端的 onPostMessage 方法并自动切换到主线程执行,当 Native 端需要发送消息给 WebView 时,可以通过 JavaScriptReplyProxy.postMessage 发送到 WebView ,并将消息传递给 onmessage 闭包。

// 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!");

文件传递

在以往的通讯机制中,如果我们想传递一个图片只能将其转换为 base64 等进行传输,如果曾经使用过 shouldOverrideUrlLoading 拦截 url 大概率会遇见传输瓶颈,AndroidX Webkit 中很贴心的提供了字节流传递机制。

Native 传递文件给 WebView

// 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!");

WebView 传递文件给 Native

// 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 10 提供了深色主题的支持,但是在 WebView 中显示的网页却不会自动显示深色主题, 这就表现出严重的割裂感,开发者只能通过修改 css 来达到目的,但这往往费时费力还存在兼容性问题,Android 官方为了改善这一用户体验,为 WebView 提供了深色主题的适配。

一个网页如何表现是和prefers-color-scheme and color-scheme 这两个 Web 标准互操作的。 Android官方提供了一张表阐述了他们之间的关系。

image.png

上面这张图比较复杂,简单来说如果你想让 WebView 的内容和应用的主题相匹配,你应该始终定义深色主题并实现 prefers-color-scheme ,而对于未定义 prefers-color-scheme 的页面,系统按照不同的策略选择算法生成或者显示默认页面。

以 Android 12 或更低版本为目标平台的应用 API 设计过于复杂,以 Android 13 或更高版本为目标平台的应用精简了 API ,具体变更请参考官方文档

JavaScript and WebAssembly 执行引擎支持

我们有时候我们会在程序中运行 JavaScript 而不显示任何 Web 内容,比如小程序的逻辑层,使用 WebView 本能够满足我们的要求但是浪费了过多的资源,我们都知道在 WebView 中真正负责执行 JavaScript 的引擎是 V8 ,但是我们又无法直接使用,所以我们的安装包中出现了各种各样的引擎:HermesJSCV8等。

Android 发现了这”群雄割据“的局面,推出了AndroidX JavascriptEngine,JavascriptEngine 直接使用了 WebView 的 V8 实现,由于不用分配其他 WebView 资源所以资源分配更低,并可以开启多个独立运行的环境,还针对传递大量数据做了优化。

代码展示了执行 JavaScript 和 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,大家可以查看发布页面查看最新的功能。

写在最后

本文从实际矛盾出发,带领大家思考 AndroidX Webkit 的产生原因和实现原理,对于AndroidX Webkit 的几个功能分别做了简单的介绍,希望大家能在这篇文章获得一点启发和帮助。

Guess you like

Origin juejin.im/post/7259762775365320741