Use AndroidX para mejorar la capacidad de WebView

En el proceso de desarrollo de aplicaciones, para mantener una experiencia de usuario consistente y mejorar la eficiencia del desarrollo en múltiples plataformas, muchas aplicaciones optan por utilizar la tecnología H5. En la plataforma Android, el componente WebView generalmente se usa para alojar contenido H5 para su visualización.

Problemas con WebView

Desde Android Lollipop, la actualización del componente WebView ha sido independiente de la plataforma Android. Sin embargo, la API (android.webkit) que controla WebView sigue siendo relevante para las actualizaciones de la plataforma. Esto significa que los desarrolladores de aplicaciones solo pueden usar las interfaces definidas por la plataforma actual, pero no pueden utilizar todas las capacidades de WebView. Por ejemplo: WebView.startSafeBrowsingla API se agrega en Android 8.1 y WebView proporciona esta función. Incluso si actualizamos WebView para tener esta función en Android 7.0, dado que Android 7.0 no tiene API, no podemos WebView.startSafeBrowsingusar esta función.

La implementación de WebView se basa en el proyecto de código abierto Chromium , mientras que Android se basa en el proyecto AOSP. Estos dos proyectos tienen diferentes ciclos de lanzamiento. Normalmente, WebView puede lanzar la próxima versión en un mes, mientras que Android tarda un año. Para la nueva Función WebView Necesitamos un año como máximo para usarla.

La aparición de AndroidX Webkit

Para resolver la discrepancia anterior entre las capacidades de la plataforma y WebView, podemos definir un conjunto de API de WebView independientemente de la plataforma y dejar que actualice la API con la función WebView, que resuelve el problema existente pero presenta otro problema: —Cómo conecte la API de WebView recién definida y WebView.

Desde la perspectiva del desarrollo de aplicaciones, el sistema WebView es difícil de modificar. Es una buena solución para compilar y personalizar un WebView por ti mismo y proporcionarle el apk. En este momento, podemos resolver fácilmente el problema de conexión y podemos agregar y cambiar la función arbitrariamente según las necesidades sin esperar la actualización oficial. Al mismo tiempo, resuelve el problema de compatibilidad y fragmentación del kernel WebView. Tencent X5, UC U4, etc. son toda esta solución. Mantener un WebView no es una tarea fácil y requiere más apoyo de mano de obra, porque empaquetar WebView en un paquete va acompañado de un fuerte aumento en el tamaño del paquete.

Desde el punto de vista oficial de Android, WebView se puede impulsar para admitir la API de WebView, y esta es la solución de AndroidX Webkit. Android coloca oficialmente la API de WebView definida en la biblioteca de AndroidX Webkit para admitir actualizaciones frecuentes y agrega una "capa de pegamento" aguas arriba de WebView para conectarse con AndroidX Webkit, de modo que en la versión anterior de la plataforma Android, siempre que el "pegamento La capa está instalada. El WebView del código también tiene las funciones de la nueva versión de la plataforma.

La "capa adhesiva" solo se admite después de una determinada versión, y la versión anterior del kernel de WebView no la admite, por lo que siempre debe verificar antes de llamar a isFeatureSupported.

Características de AndroidX Webkit

Después de una comprensión preliminar de los principios de generación e implementación de AndroidX Webkit, echemos un vistazo a las nuevas capacidades que proporciona para mejorar nuestro WebView.

compatible con versiones anteriores

Como se analizó anteriormente, AndroidX Webkit proporciona compatibilidad con versiones anteriores, como se muestra en el siguiente código, proporcionado por WebViewCompatllamadas de interfaz 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 的几个功能分别做了简单的介绍,希望大家能在这篇文章获得一点启发和帮助。

Supongo que te gusta

Origin juejin.im/post/7259762775365320741
Recomendado
Clasificación