使用 AndroidX 增强 WebView 的能力
在App开发过程中,为了在多个平台上保持一致的用户体验和提高开发效率,许多应用程序选择使用 H5 技术。在 Android 平台上,通常使用 WebView 组件来承载 H5 内容以供展示。
一.WebView 存在的问题
自 Android Lollipop 起,WebView 组件的升级已经独立于 Android 平台。然而,控制 WebView 的 API(android.webkit) 仍然与平台升级相关。这意味着应用开发者只能使用当前平台所定义的接口,而无法充分利用 WebView 的全部能力。例如: WebView.startSafeBrowsing API 在 Android 8.1 上被添加,该 Feature 由 WebView 提供,即使我们在 Android 7.0 更新 WebView 拥有了该 Feature ,由于 Android 7.0 没有 WebView.startSafeBrowsing API ,我们也没办法使用该功能。
WebView 的实现基于 Chromium 开源项目,而 Android 则基于 AOSP 项目,这两个项目有着不同的发布周期,WebView 往往一个月就可以推出下一个版本,而 Android 则需要一年的时间,对于 WebView 新增的 Feature 我们最迟需要一年才能使用。
二.AndroidX Webkit 的出现
为了解决上面平台能力和 WebView 不匹配的问题,我们可以独立于平台之外定义一套 WebView API ,并让它随着 WebView 的 Feature 更新 API ,这样解决了现有的问题却导入了另一个问题——如何将新定义的 WebView API 和 WebView 进行衔接。
从应用开发的角度,系统 WebView 难以修改,自己编译定制一个 WebView 并随着 apk 提供是一个很好方案。这时候,我们可以轻松的解决衔接问题,并能够按照需求,任意增改 Feature 而不必等官方更新。同时解决了兼容问题和 WebView 内核碎片化的问题。腾讯 X5 ,UC U4 等都是这个方案。维护一份 WebView 并不是一件容易的事,需要投入更多的人力支持,因为将 WebView 打入包中,还伴随着包体积的急剧增加。
从 Android 官方的角度,可以推动 WebView 上游支持该 WebView API , 而这正是 AndroidX Webkit 的解决方案。Android 官方将定义的 WebView API 放置到 AndroidX Webkit 库,以支持频繁的更新,并在 WebView 上游增加“胶水层”与 AndroidX Webkit 进行衔接,这样在旧版的 Android 平台上,只要安装了拥有"胶水"层代码的 WebView ,也就拥有了新版平台的功能。
备注:
“胶水层” 是在某个版本之后才后才支持的,旧版本的 WebView 内核并不支持,这也是为什么在调用之前始终应该检查 isFeatureSupported 的原因。
三.AndroidX Webkit 的功能
初步了解了 AndroidX Webkit 的产生和实现原理,下面带领大家看一下它都提供了哪些新能力能够增强我们的 WebView 。
向下兼容
如上文分析,AndroidX Webkit 提供了向下的兼容,如下面代码所示,由 WebViewCompat 提供兼容的接口调用。
需要注意的是在调用之前对 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% 的用户。
代理功能支持
一直以来 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() {@Overridepublic 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);
调用上述方法之后,在 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() {@Overridepublic 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() {@Overridepublic 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`[1] and `color-scheme`[2] 这两个 Web 标准互操作的。Android 官方提供了一张表阐述了他们之间的关系。
上面这张图比较复杂,简单来说如果你想让 WebView 的内容和应用的主题相匹配,你应该始终定义深色主题并实现 prefers-color-scheme ,而对于未定义 prefers-color-scheme 的页面,系统按照不同的策略选择算法生成或者显示默认页面。
以 Android 12 或更低版本为目标平台的应用 API 设计过于复杂,以 Android 13 或更高版本为目标平台的应用精简了 API ,具体变更请参考官方文档[3]
JavaScript and WebAssembly 执行引擎支持
我们有时候我们会在程序中运行 JavaScript 而不显示任何 Web 内容,比如小程序的逻辑层,使用 WebView 本能够满足我们的要求但是浪费了过多的资源,我们都知道在 WebView 中真正负责执行 JavaScript 的引擎是 V8,但是我们又无法直接使用,所以我们的安装包中出现了各种各样的引擎:Hermes、JSC 、V8等。
Android 发现了这”群雄割据“的局面,推出了AndroidX JavascriptEngine[4],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>() {@Overridepublic void onSuccess(String result) {text.append(result);}@Overridepublic 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,大家可以查看最新的功能。
参考:
1.color-scheme:
https://www.w3.org/TR/css-color-adjust-1/#propdef-color-scheme
2.官方文档:
https://developer.android.com/develop/ui/views/layout/webapps/dark-theme#12
3.AndroidX JavascriptEngine:
https://developer.android.com/jetpack/androidx/releases/javascriptengine?hl=zh-cn
4.发布页面:
https://developer.android.com/jetpack/androidx/releases/webkit?hl=zh-cn#declaring_dependencies
相关文章:

使用 AndroidX 增强 WebView 的能力
在App开发过程中,为了在多个平台上保持一致的用户体验和提高开发效率,许多应用程序选择使用 H5 技术。在 Android 平台上,通常使用 WebView 组件来承载 H5 内容以供展示。 一.WebView 存在的问题 自 Android Lollipop 起,WebVie…...

Maven基础之仓库、命令、插件机制
文章目录 Maven 仓库中央仓库和本地仓库中央仓库本地仓库 Maven 命令generate 命令compile 命令clean 命令test 命令package 命令install 命令 Maven 插件机制官方插件:Compile 插件Tomcat 7 插件 Maven 仓库 中央仓库和本地仓库 [✎] 简单一点说 中央仓库是一个网…...

【ArcGIS】经纬度数据转化成平面坐标数据
将点位置导入Gis中,如下(经纬度表征位置): 如何利用Gis将其转化为平面坐标呢? Step1 坐标变换 坐标变换,打开ArcToolbox,找到“数据管理工具”->“投影和变换”->“要素”->“投影”…...
使用自己的数据利用pytorch搭建全连接神经网络进行回归预测
使用自己的数据利用pytorch搭建全连接神经网络进行回归预测 1、导入库2、数据准备3、数据拆分4、数据标准化5、数据转换6、模型搭建7、模型训练8、模型预测9、完整代码 1、导入库 引入必要的库,包括PyTorch、Pandas等。 import numpy as np import pandas as pd f…...
103.216.154.X服务器出现漏洞了有什么办法?
服务器出现漏洞是一种严重的安全风险,需要及时采取措施来应对。以下是一些常见的应对措施: 及时更新补丁:确保服务器上的操作系统、应用程序和软件都是最新版本,并及时应用相关的安全补丁,以修复已知的漏洞。 强化访问…...

数据结构:堆的实现(C实现)
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》 文章目录 一、堆二、实现思路1. 结构的定义2. 堆的构建 (HeapInit)3. 堆的销毁 (HeapDestroy)4. 堆的插入 (HeapPush)5. 堆的删除 (HeapPop)6. 取堆顶的数据 (HeapTop)7. 堆的数据个数 (HeapSize…...

数据分析两件套ClickHouse+Metabase(一)
ClickHouse篇 安装ClickHouse ClickHouse有中文文档, 安装简单 -> 文档 官方提供了四种包的安装方式, deb/rpm/tgz/docker, 自行选择适合自己操作系统的安装方式 这里我们选deb的方式, 其他方式看文档 sudo apt-get install -y apt-transport-https ca-certificates dirm…...

urllib爬虫模块
urllib爬取数据 import urllib.request as request# 定义url url "https://www.baidu.com" #模拟浏览器发起请求获取响应对象 response request.urlopen(url)""" read方法返回的是字节形式的二进制数据 二进制--》字符串 解码 decode( 编码的格式…...

TCP消息传输可靠性保证
TCP链接与断开 -- 三次握手&四次挥手 三次握手 TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一…...
Visual Studio 与QT ui文件
对.ui文件鼠标右键,然后单击 Open with…在弹出的窗口中,选中左侧的 Qt Designer,然后单击右侧的 Add 按钮,随后会弹出一个窗口,在 Program: 输入框中输入 Qt Designer 的路径,最后单击 OK找到 Qt Designer…...

竞赛项目 深度学习验证码识别 - 机器视觉 python opencv
文章目录 0 前言1 项目简介2 验证码识别步骤2.1 灰度处理&二值化2.2 去除边框2.3 图像降噪2.4 字符切割2.5 识别 3 基于tensorflow的验证码识别3.1 数据集3.2 基于tf的神经网络训练代码 4 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 &#x…...

ORA-00845: MEMORY_TARGET not supported on this system
处理故障时,发现startup实例失败,报错ORA-00845: MEMORY_TARGET not supported on this system SYSorcl1> startup; ORA-00845: MEMORY_TARGET not supported on this system 查看alert日志,报错如下 Starting ORACLE instance (normal…...

wps设置一键标题字体和大小
参考 wps设置一键标题字体和大小:https://www.kafan.cn/A/7v5le1op3g.html 统一一键设置...

TIA博途WINCC_如何在IO域中保证输入数值只能为正数?
TIA博途WINCC_如何在IO域中保证输入数值只能为正数? 在某些情况下,输入的数值受到限制,本例就以输入的数值必须为正整数为例进行说明。 如下图所示,在PLC的全局DB块中添加一个测试变量,数据类型为Int(该数据类型的范围为-32768~+32767), 如下图所示,将该测试变量拖拽到…...

《Linux从练气到飞升》No.13 Linux进程状态
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...

安卓快速开发
1.环境搭建 Android Studio下载网页:https://developer.android.google.cn/studio/index.html 第一次新建工程需要等待很长时间,新建一个Empty Views Activity 项目,右上角选择要运行的机器,运行就安装上去了(打开USB调试)。 2…...

SpringCloud微服务之间如何进行用户信息传递(涉及:Gateway、OpenFeign组件)
目录 1、想达到的效果2、用户信息在微服务之间传递的两种途径3、用RuoYi-Cloud为例进行演示说明(1)网关将用户信息写在请求头中(2)业务微服务之间通过OpenFeign进行调用,并且将用户信息写在OpenFeign准备的请求头中&am…...
RabbitMQ之TTL+死信队列实现延迟队列
RabbitMQ是一个流行的消息队列系统,它提供了许多有用的功能,其中之一是TTL(Time To Live)和死信队列。这些功能可以用来实现延迟队列,让我们来看看如何使用它们。 首先,什么是TTL?TTL是消息的存…...

GrapeCity Documents for PDF (GcPdf) 6.2 Crack
GrapeCity PDF 文档 (GcPdf) 改进了对由 GcPdf 以外的软件生成的现有 PDF 文档的处理 在新的 v6.2 版本中,GcPdf 增强了 PDF 文档的加载和保存,并提供以下优势: GcPdf 现在可以加载和保存可能不严格符合 PDF 规范的 PDF 文档。GcPdf 现在将…...
【Sklearn】基于随机森林算法的数据分类预测(Excel可直接替换数据)
【Sklearn】基于随机森林算法的数据分类预测(Excel可直接替换数据) 1.模型原理1.1 模型原理1.2 数学模型2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果1.模型原理 随机森林(Random Forest)是一种集成学习方法,通过组合多个决策树来构建强大的分类或回归…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...