Webview详解(上)
第一阶段:基础入门
WebView基础概念
什么是Webview?
WebView是一种用于在移动应用程序中展示网页内容的嵌入式浏览器组件。它允许开发者将网页内容直接加载到应用界面中,用户无需离开应用即可浏览网页。WebView 通常用于加载 HTML、CSS、JavaScript 等 Web 技术构建的内容,广泛应用于混合移动应用开发(Hybrid App)或需要展示 Web 内容的场景。
主要特点:
- 嵌入网页:可以直接在应用中加载和显示网页。
- 支持 Web 技术:支持 HTML、CSS、JavaScript 等标准 Web 技术。
- 与原生应用交互:可以通过 JavaScript 与原生代码(如 Java、Kotlin、Swift)进行通信。
- 轻量级:相比完整的浏览器,WebView 更轻量,适合嵌入应用。
常见用途:
- 在应用中展示动态更新的 Web 内容(如新闻、博客)。
- 实现混合应用开发,结合 Web 和原生功能。
- 加载在线帮助文档或用户协议页面。
平台支持:
- Android:通过
WebView类实现。 - iOS:通过
WKWebView组件实现。
简单来说,WebView 是移动应用中的一个窗口,用于展示网页内容,同时保持应用的完整性和用户体验。
WebView的常见应用场景
1. 混合开发(Hybrid Development)
混合开发结合了原生应用(Native App)和网页技术(Web App)的优势,使用 WebView 来加载网页内容,同时通过 JavaScript 与原生代码交互。常见的应用场景包括:
- 跨平台开发:通过 WebView 加载基于 HTML、CSS 和 JavaScript 开发的页面,可以减少为不同平台(如 iOS 和 Android)单独开发的工作量。
- 快速迭代:由于网页内容可以远程更新,开发者可以直接修改服务器端的 HTML 文件,而无需发布新的应用版本。
- 轻量级功能:对于一些不需要复杂原生功能的部分(如帮助文档、活动页面、用户协议等),可以直接用 WebView 加载网页。
- 第三方集成:集成第三方服务(如支付、地图、广告等)时,可以通过 WebView 加载其提供的网页界面。
2. 动态内容加载(Dynamic Content Loading)
WebView 可以加载远程或本地的 HTML 内容,适合需要动态更新内容的场景。常见的应用场景包括:
- 实时内容更新:从服务器加载最新的 HTML 内容,例如新闻、公告、活动详情等,用户无需更新应用即可获取最新信息。
- 富文本展示:加载包含图片、视频、表格等复杂格式的内容,例如文章详情页、产品介绍页等。
- 离线缓存:通过缓存机制,WebView 可以在离线状态下加载本地存储的网页内容,提升用户体验。
- 动态表单:加载动态生成的表单页面,例如问卷调查、用户反馈等。
其他常见应用场景
除了上述两种主要场景,WebView 还可以用于以下场景:
- OAuth 授权:通过 WebView 加载第三方登录页面,例如使用 Google、Facebook 登录。
- 内嵌广告:在应用中嵌入广告页面,广告内容由广告平台动态提供。
- 教育类应用:加载在线课程、电子书或交互式学习内容。
- 企业应用:加载企业内部的管理系统或工作平台。
Android WebView vs iOS WKWebView 核心差异
1. 底层引擎
-
Android WebView:
- 基于 Chromium 内核(从 Android 4.4 开始)。
- 在 Android 7.0 及以上版本中,WebView 是一个独立的模块,可以通过 Google Play 更新。
- 支持最新的 Web 标准(如 HTML5、CSS3、JavaScript)。
-
iOS WKWebView:
- 基于 Safari 的 WebKit 引擎。
- 从 iOS 8 开始引入,取代了旧的
UIWebView。 - 同样支持最新的 Web 标准,性能优于
UIWebView。
WKWebView 的性能通常优于 Android WebView,特别是在 JavaScript 执行和渲染效率方面。
2. 性能
-
Android WebView:
- 性能较好,但在低端设备上可能出现卡顿。
- 内存占用较高,特别是在加载复杂网页时。
-
iOS WKWebView:
- 性能显著优于 Android WebView,特别是在 JavaScript 执行和页面渲染方面。
- 内存管理更高效,独立于应用进程运行,减少了内存泄漏的风险。
3. 进程模型
-
Android WebView:
- 运行在应用的主进程中,与应用共享内存。
- 如果 WebView 崩溃,可能会导致整个应用崩溃。
-
iOS WKWebView:
- 运行在独立的进程中,与应用主进程分离。
- 如果 WKWebView 崩溃,不会影响应用的主进程。
4. API 设计
-
Android WebView:
- 提供了丰富的 API,允许开发者自定义 WebView 的行为。
- 支持通过
WebViewClient和WebChromeClient处理网页加载、JavaScript 交互等事件。 - 支持通过
addJavascriptInterface实现 JavaScript 与原生代码的交互。
-
iOS WKWebView:
- API 设计更加现代化和简洁。
- 通过
WKNavigationDelegate和WKUIDelegate处理网页加载和用户交互。 - 支持通过
evaluateJavaScript执行 JavaScript 代码,并通过WKScriptMessageHandler实现 JavaScript 与原生代码的交互。
5. 缓存与存储
-
Android WebView:
- 支持缓存网页内容,但缓存管理功能较弱。
- 支持通过
WebSettings配置缓存行为。
-
iOS WKWebView:
- 提供了更强大的缓存管理功能,支持 HTTP 缓存、本地存储等。
- 支持通过
WKWebsiteDataStore管理缓存和存储数据。
6. 安全性
-
Android WebView:
- 默认安全性较低,开发者需要手动配置以提高安全性。
- 支持通过
WebSettings禁用 JavaScript、限制文件访问等。
-
iOS WKWebView:
- 默认安全性较高,独立进程模型减少了安全风险。
- 支持通过
WKPreferences配置安全性选项。
7. 兼容性
-
Android WebView:
- 兼容性较好,但由于 Android 设备碎片化,不同设备上的表现可能存在差异。
- 需要针对不同 Android 版本进行适配。
-
iOS WKWebView:
- 兼容性较高,所有 iOS 设备上的表现基本一致。
- 需要 iOS 8 及以上版本支持。
8. 开发体验
-
Android WebView:
- 开发文档详细,但 API 较多,学习曲线较陡。
- 调试工具依赖于 Chrome DevTools。
-
iOS WKWebView:
- 开发文档清晰,API 设计简洁,学习曲线较平缓。
- 调试工具依赖于 Safari Web Inspector。
总结
| 特性 | Android WebView | iOS WKWebView |
|---|---|---|
| 底层引擎 | Chromium | WebKit |
| 性能 | 较好,低端设备可能卡顿 | 更优,特别在 JavaScript 执行 |
| 进程模型 | 运行在主进程 | 独立进程 |
| API 设计 | 灵活但复杂 | 现代且简洁 |
| 缓存与存储 | 功能较弱 | 功能强大 |
| 安全性 | 默认较低,需手动配置 | 默认较高 |
| 兼容性 | 设备碎片化,需适配 | 设备一致,兼容性好 |
| 开发体验 | 文档详细,学习曲线陡 | 文档清晰,学习曲线平缓 |
环境搭建与基础使用
初始化WebView并加载网页
Android 初始化 WebView 并加载网页
1. 加载 URL
// 在 Activity 或 Fragment 中
import android.os.Bundle;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 WebViewWebView webView = findViewById(R.id.webView);// 启用 JavaScriptwebView.getSettings().setJavaScriptEnabled(true);// 加载 URLString url = "https://www.example.com";webView.loadUrl(url);}
}
2. 加载本地 HTML
// 在 Activity 或 Fragment 中
import android.os.Bundle;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 WebViewWebView webView = findViewById(R.id.webView);// 启用 JavaScriptwebView.getSettings().setJavaScriptEnabled(true);// 加载本地 HTML 文件String htmlContent = "<html><body><h1>Hello, World!</h1></body></html>";webView.loadData(htmlContent, "text/html", "UTF-8");// 或者从 assets 文件夹加载本地 HTML 文件// webView.loadUrl("file:///android_asset/index.html");}
}
iOS 初始化 WKWebView 并加载网页
1. 加载 URL
import UIKit
import WebKitclass ViewController: UIViewController {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 初始化 WKWebViewwebView = WKWebView(frame: self.view.frame)self.view.addSubview(webView)// 加载 URLif let url = URL(string: "https://www.example.com") {let request = URLRequest(url: url)webView.load(request)}}
}
2. 加载本地 HTML
import UIKit
import WebKitclass ViewController: UIViewController {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 初始化 WKWebViewwebView = WKWebView(frame: self.view.frame)self.view.addSubview(webView)// 加载本地 HTML 字符串let htmlContent = "<html><body><h1>Hello, World!</h1></body></html>"webView.loadHTMLString(htmlContent, baseURL: nil)// 或者从本地文件加载 HTMLif let filePath = Bundle.main.path(forResource: "index", ofType: "html") {let fileURL = URL(fileURLWithPath: filePath)webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)}}
}
注意事项
- Android:
- 在
AndroidManifest.xml中添加网络权限:<uses-permission android:name="android.permission.INTERNET" />。 - 使用
WebViewClient处理页面加载事件(如重定向、错误处理)。
- 在
- iOS:
- 在
Info.plist中允许加载 HTTP 资源(如果需要):添加NSAppTransportSecurity配置。 - 使用
WKNavigationDelegate处理页面加载事件。
- 在
第二阶段:核心交互与功能
WebView与JavaScript交互
原生调用js方法,js调用原生接口
原生调用 JavaScript 方法
1. Android 实现
在 Android 中,可以通过 WebView 的 evaluateJavascript 方法执行 JavaScript 代码。
// 在 Activity 或 Fragment 中
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 WebViewwebView = findViewById(R.id.webView);webView.getSettings().setJavaScriptEnabled(true);// 设置 WebViewClient,确保页面加载完成后再执行 JavaScriptwebView.setWebViewClient(new WebViewClient() {@Overridepublic void onPageFinished(WebView view, String url) {super.onPageFinished(view, url);// 调用 JavaScript 方法String jsCode = "alert('Hello from Android!')";webView.evaluateJavascript(jsCode, null);}});// 加载网页webView.loadUrl("https://www.example.com");}
}
2. iOS 实现
在 iOS 中,可以通过 WKWebView 的 evaluateJavaScript(_:completionHandler:) 方法执行 JavaScript 代码。
import UIKit
import WebKitclass ViewController: UIViewController, WKNavigationDelegate {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 初始化 WKWebViewwebView = WKWebView(frame: self.view.frame)webView.navigationDelegate = selfself.view.addSubview(webView)// 加载网页if let url = URL(string: "https://www.example.com") {let request = URLRequest(url: url)webView.load(request)}}// 页面加载完成后调用 JavaScriptfunc webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {let jsCode = "alert('Hello from iOS!')"webView.evaluateJavaScript(jsCode, completionHandler: nil)}
}
JavaScript 调用原生接口
1. Android 实现(JSBridge)
在 Android 中,可以通过 addJavascriptInterface 将 Java 对象暴露给 JavaScript。
// 在 Activity 或 Fragment 中
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 WebViewwebView = findViewById(R.id.webView);webView.getSettings().setJavaScriptEnabled(true);// 将 NativeBridge 对象暴露给 JavaScriptwebView.addJavascriptInterface(new NativeBridge(), "NativeBridge");// 加载网页webView.loadUrl("file:///android_asset/index.html");}// 定义 NativeBridge 类public class NativeBridge {@JavascriptInterfacepublic void showToast(String message) {// 在原生代码中处理 JavaScript 调用runOnUiThread(() -> {Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();});}}
}
在 HTML/JavaScript 中调用原生方法:
<!DOCTYPE html>
<html>
<head><title>JSBridge Example</title><script>function callNative() {// 调用原生方法NativeBridge.showToast("Hello from JavaScript!");}</script>
</head>
<body><button onclick="callNative()">Call Native</button>
</body>
</html>
2. iOS 实现(WKScriptMessageHandler)
在 iOS 中,可以通过 WKUserContentController 和 WKScriptMessageHandler 实现 JavaScript 调用原生接口。
import UIKit
import WebKitclass ViewController: UIViewController, WKScriptMessageHandler {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 配置 WKWebViewlet config = WKWebViewConfiguration()let userContentController = WKUserContentController()// 注册消息处理器userContentController.add(self, name: "nativeBridge")config.userContentController = userContentController// 初始化 WKWebViewwebView = WKWebView(frame: self.view.frame, configuration: config)self.view.addSubview(webView)// 加载网页if let filePath = Bundle.main.path(forResource: "index", ofType: "html") {let fileURL = URL(fileURLWithPath: filePath)webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)}}// 处理 JavaScript 消息func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {if message.name == "nativeBridge", let messageBody = message.body as? String {// 处理 JavaScript 调用showToast(message: messageBody)}}// 显示 Toastfunc showToast(message: String) {let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)self.present(alert, animated: true, completion: nil)DispatchQueue.main.asyncAfter(deadline: .now() + 1) {alert.dismiss(animated: true, completion: nil)}}
}
在 HTML/JavaScript 中调用原生方法:
<!DOCTYPE html>
<html>
<head><title>WKScriptMessageHandler Example</title><script>function callNative() {// 调用原生方法window.webkit.messageHandlers.nativeBridge.postMessage("Hello from JavaScript!");}</script>
</head>
<body><button onclick="callNative()">Call Native</button>
</body>
</html>
总结
| 功能 | Android | iOS |
|---|---|---|
| 原生调用 JavaScript | webView.evaluateJavascript(jsCode, null) | webView.evaluateJavaScript(jsCode, nil) |
| JavaScript 调用原生 | addJavascriptInterface + @JavascriptInterface | WKUserContentController + WKScriptMessageHandler |
参数传递与异步通信设计
参数传递
1. Android 实现
在 Android 中,可以通过 @JavascriptInterface 注解的方法传递参数。
- JavaScript 调用原生并传递参数:
// 在 NativeBridge 类中
@JavascriptInterface
public void showToast(String message) {runOnUiThread(() -> {Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();});
}
- JavaScript 调用:
NativeBridge.showToast("Hello from JavaScript!");
- 传递复杂参数:
@JavascriptInterface
public void handleData(String jsonData) {// 解析 JSON 数据try {JSONObject json = new JSONObject(jsonData);String name = json.getString("name");int age = json.getInt("age");// 处理数据} catch (JSONException e) {e.printStackTrace();}
}
- JavaScript 调用:
let data = { name: "John", age: 30 };
NativeBridge.handleData(JSON.stringify(data));
2. iOS 实现
在 iOS 中,可以通过 WKScriptMessageHandler 接收 JavaScript 传递的参数。
- JavaScript 调用原生并传递参数:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {if message.name == "nativeBridge", let messageBody = message.body as? [String: Any] {// 处理参数let name = messageBody["name"] as? Stringlet age = messageBody["age"] as? Int// 处理数据}
}
- JavaScript 调用:
let data = { name: "John", age: 30 };
window.webkit.messageHandlers.nativeBridge.postMessage(data);
异步通信设计
1. Android 实现
在 Android 中,可以通过回调机制实现异步通信。
- 定义回调接口:
public interface JsCallback {void onResult(String result);
}
- 暴露给 JavaScript 的方法:
@JavascriptInterface
public void fetchData(String request, final JsCallback callback) {// 模拟异步操作new Thread(() -> {try {Thread.sleep(2000); // 模拟网络请求final String result = "Response for: " + request;runOnUiThread(() -> callback.onResult(result));} catch (InterruptedException e) {e.printStackTrace();}}).start();
}
- JavaScript 调用:
NativeBridge.fetchData("Hello", function(result) {console.log(result); // 输出:Response for: Hello
});
2. iOS 实现
在 iOS 中,可以通过 evaluateJavaScript 实现异步通信。
- JavaScript 调用原生并传递回调:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {if message.name == "nativeBridge", let request = message.body as? String {// 模拟异步操作DispatchQueue.global().async {Thread.sleep(forTimeInterval: 2) // 模拟网络请求let result = "Response for: \(request)"DispatchQueue.main.async {// 调用 JavaScript 回调self.webView.evaluateJavaScript("handleResponse('\(result)')", completionHandler: nil)}}}
}
- JavaScript 调用:
function fetchData(request) {window.webkit.messageHandlers.nativeBridge.postMessage(request);
}function handleResponse(result) {console.log(result); // 输出:Response for: Hello
}fetchData("Hello");
总结
| 功能 | Android | iOS |
|---|---|---|
| 参数传递 | 通过 @JavascriptInterface 方法接收参数 | 通过 WKScriptMessageHandler 接收参数 |
| 复杂参数 | 使用 JSON 字符串传递 | 直接传递字典对象 |
| 异步通信 | 使用回调接口实现 | 使用 evaluateJavaScript 实现 |
页面生命周期控制
处理页面加载状态,错误处理与网络异常监控
Android 实现
在 Android 中,可以通过 WebViewClient 监听页面加载状态,并通过 onReceivedError 和 onReceivedHttpError 处理错误。
1. 监听页面加载状态
import android.os.Bundle;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 WebViewwebView = findViewById(R.id.webView);webView.getSettings().setJavaScriptEnabled(true);// 设置 WebViewClient 监听加载状态webView.setWebViewClient(new WebViewClient() {@Overridepublic void onPageStarted(WebView view, String url, Bitmap favicon) {super.onPageStarted(view, url, favicon);// 页面开始加载showLoadingIndicator();}@Overridepublic void onPageFinished(WebView view, String url) {super.onPageFinished(view, url);// 页面加载完成hideLoadingIndicator();}@Overridepublic void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {super.onReceivedError(view, request, error);// 页面加载失败handlePageLoadError(error.getDescription().toString());}@Overridepublic void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {super.onReceivedHttpError(view, request, errorResponse);// 处理 HTTP 错误handleHttpError(errorResponse.getStatusCode());}});// 加载网页webView.loadUrl("https://www.example.com");}private void showLoadingIndicator() {// 显示加载指示器(例如 ProgressBar)}private void hideLoadingIndicator() {// 隐藏加载指示器}private void handlePageLoadError(String errorDescription) {// 显示错误页面或错误提示String errorHtml = "<html><body><h1>Error: " + errorDescription + "</h1></body></html>";webView.loadData(errorHtml, "text/html", "UTF-8");}private void handleHttpError(int statusCode) {// 根据 HTTP 状态码处理错误String errorMessage = "HTTP Error: " + statusCode;showToast(errorMessage);}private void showToast(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}
}
2. 处理 HTTPS 错误
如果需要处理 HTTPS 错误,可以重写 onReceivedSslError 方法:
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {// 处理 SSL 错误handler.cancel(); // 取消加载handlePageLoadError("SSL Error: " + error.toString());
}
3. 监控网络异常
可以通过 ConnectivityManager 监控网络状态:
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;private boolean isNetworkAvailable() {ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}@Override
protected void onResume() {super.onResume();if (!isNetworkAvailable()) {showToast("No network connection");handlePageLoadError("No network connection");}
}
iOS 实现
在 iOS 中,可以通过 WKNavigationDelegate 监听页面加载状态,并通过 didFail 和 didFailProvisionalNavigation 处理错误。
1. 监听页面加载状态
import UIKit
import WebKitclass ViewController: UIViewController, WKNavigationDelegate {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 初始化 WKWebViewwebView = WKWebView(frame: self.view.frame)webView.navigationDelegate = selfself.view.addSubview(webView)// 加载网页if let url = URL(string: "https://www.example.com") {let request = URLRequest(url: url)webView.load(request)}}// 页面开始加载func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {showLoadingIndicator()}// 页面加载完成func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {hideLoadingIndicator()}// 页面加载失败func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {handlePageLoadError(error.localizedDescription)}// 页面加载失败(临时导航)func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {handlePageLoadError(error.localizedDescription)}private func showLoadingIndicator() {// 显示加载指示器(例如 UIActivityIndicatorView)}private func hideLoadingIndicator() {// 隐藏加载指示器}private func handlePageLoadError(_ errorDescription: String) {// 显示错误页面或错误提示let errorHtml = "<html><body><h1>Error: \(errorDescription)</h1></body></html>"webView.loadHTMLString(errorHtml, baseURL: nil)}
}
2. 处理 HTTPS 错误
如果需要处理 HTTPS 错误,可以在 didFailProvisionalNavigation 方法中检查错误码:
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {if (error as NSError).code == NSURLErrorServerCertificateUntrusted {handlePageLoadError("SSL Error: \(error.localizedDescription)")}
}
3. 监控网络异常
可以通过 Network 框架监控网络状态:
import Networklet monitor = NWPathMonitor()override func viewDidLoad() {super.viewDidLoad()monitor.pathUpdateHandler = { path inif path.status == .satisfied {print("Network is available")} else {DispatchQueue.main.async {self.showToast("No network connection")self.handlePageLoadError("No network connection")}}}let queue = DispatchQueue(label: "NetworkMonitor")monitor.start(queue: queue)
}private func showToast(_ message: String) {let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)self.present(alert, animated: true, completion: nil)DispatchQueue.main.asyncAfter(deadline: .now() + 1) {alert.dismiss(animated: true, completion: nil)}
}
总结
| 功能 | Android | iOS |
|---|---|---|
| 监听页面加载开始 | onPageStarted | webView(_:didStartProvisionalNavigation:) |
| 监听页面加载完成 | onPageFinished | webView(_:didFinish:) |
| 监听页面加载失败 | onReceivedError | webView(_:didFail:withError:) |
| 处理 HTTPS 错误 | onReceivedSslError | webView(_:didFailProvisionalNavigation:withError:) |
| 监控网络异常 | ConnectivityManager | NWPathMonitor |
内存泄漏预防(Android的独立进程方案)
独立进程方案的优势
- 彻底释放内存:
- WebView 运行在独立进程中,关闭页面后可以直接销毁进程,释放所有相关内存。
- 隔离崩溃风险:
- WebView 的崩溃不会影响主进程的稳定性。
- 优化性能:
- 独立进程可以充分利用多核 CPU,提升性能。
实现步骤
1. 在 Manifest 中声明独立进程
在 AndroidManifest.xml 中为需要运行 WebView 的 Activity 指定独立进程:
<activityandroid:name=".WebViewActivity"android:process=":webview_process" />
通过 android:process 属性,WebViewActivity 将运行在一个独立的进程中。
2. 启动独立进程的 Activity
从主进程启动独立进程的 Activity:
Intent intent = new Intent(this, WebViewActivity.class);
startActivity(intent);
3. 在独立进程中初始化 WebView
在 WebViewActivity 中初始化 WebView:
public class WebViewActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_webview);webView = findViewById(R.id.webView);webView.getSettings().setJavaScriptEnabled(true);// 加载网页webView.loadUrl("https://www.example.com");}
}
4. 销毁独立进程
在页面关闭时,主动销毁独立进程:
@Override
protected void onDestroy() {super.onDestroy();// 销毁 WebViewif (webView != null) {webView.stopLoading();webView.destroy();webView = null;}// 销毁进程android.os.Process.killProcess(android.os.Process.myPid());
}
注意事项
-
进程间通信:
- 独立进程与主进程之间的通信可以通过
Intent、BroadcastReceiver或AIDL实现。 - 示例:通过
BroadcastReceiver发送消息:// 主进程中注册 BroadcastReceiver BroadcastReceiver receiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String message = intent.getStringExtra("message");Log.d("WebViewProcess", "Received message: " + message);} }; registerReceiver(receiver, new IntentFilter("com.example.WEBVIEW_MESSAGE"));// 独立进程中发送消息 Intent intent = new Intent("com.example.WEBVIEW_MESSAGE"); intent.putExtra("message", "Hello from WebView process!"); sendBroadcast(intent);
- 独立进程与主进程之间的通信可以通过
-
资源释放:
- 确保在
onDestroy中释放 WebView 资源,并销毁独立进程。
- 确保在
-
性能开销:
- 独立进程会增加一定的内存和 CPU 开销,适用于需要频繁加载和销毁 WebView 的场景。
-
兼容性问题:
- 在某些低端设备上,独立进程可能会影响应用的启动速度和稳定性。
优化建议
-
WebView 复用:
- 如果不需要频繁销毁 WebView,可以考虑复用 WebView,而不是每次创建独立进程。
-
内存监控:
- 使用工具(如 Android Profiler)监控内存使用情况,确保独立进程的内存被正确释放。
-
错误处理:
- 在独立进程中处理 WebView 的崩溃和错误,避免影响主进程。
总结
| 方案 | 优点 | 缺点 |
|---|---|---|
| 独立进程方案 | 彻底释放内存,隔离崩溃风险,优化性能 | 增加内存和 CPU 开销,兼容性问题 |
| WebView 复用 | 减少进程创建开销,适合频繁使用 WebView 的场景 | 需要手动管理 WebView 的生命周期,容易泄漏 |
相关文章:
Webview详解(上)
第一阶段:基础入门 WebView基础概念 什么是Webview? WebView是一种用于在移动应用程序中展示网页内容的嵌入式浏览器组件。它允许开发者将网页内容直接加载到应用界面中,用户无需离开应用即可浏览网页。WebView 通常用于加载 HTML、CSS、J…...
Docker镜像相关命令(Day2)
文章目录 前言一、问题描述二、相关命令1.查看镜像2.搜索镜像3.拉取镜像4.删除镜像5.镜像的详细信息6.标记镜像 三、验证与总结 前言 Docker 是一个开源的容器化平台,它让开发者能够将应用及其依赖打包到一个标准化的单元(容器)中运行。在 D…...
C++值传递和引用传递
系列文章目录 值传递和引用传递是 C 中两种常见的参数传递方式,它们的主要区别在于函数内部对参数的操作是否会影响原始数据 C值传递和引用传递 系列文章目录1、值传递2、引用传递3 、常量引用传递4、值传递 vs 引用传递总结 1、值传递 值传递会复制传入的参数&…...
LangChain4J开源开发框架简介
目录 1.1、前言1.2、集成方式简单1.3、核心功能与优势1.4、两种调用方式1.5、链式调用示例代码1.6、AI服务调用示例代码1.7、典型使用场景1.8、总结 1.1、前言 LangChain4J 是一个专为 Java 开发者设计的开源框架,旨在简化大型语言模型(LLMs)…...
Qt图形视图框架在项目中的应用
一、基本概念 Qt 的图形视图框架(Graphics View Framework)提供了一套用于显示和管理2D图形对象的框架。它提供了一组类,这些类可以组合使用来构建用户界面、处理图形对象、实现缩放、平移、旋转等操作。以下是Qt GraphicsView框架的主要组件…...
SpringBoot集成Elasticsearch 7.x spring-boot-starter-data-elasticsearch 方式
SpringBoot集成Elasticsearch 7.x | spring-boot-starter-data-elasticsearch 方式 前言添加maven依赖配置application.properties测试实体类 方式一:继承 ElasticsearchRepository(适合简单查询) 直接使用想自定义自己的Repository接口 方式…...
STM32蜂鸣器播放音乐
STM32蜂鸣器播放音乐 STM32蜂鸣器播放音乐 Do, Re, Mi, Fa, 1. 功能概述 本系统基于STM32F7系列微控制器,实现了以下功能: 通过7个按键控制蜂鸣器发声,按键对应不同的音符。每个按键对应一个音符(Do, Re, Mi, Fa, Sol, La, Si&a…...
GitLab 中文版17.10正式发布,27项重点功能解读【二】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
解码未来:DeepSeek开源FlashMLA,推理加速核心技术,引领AI变革
前言: DeepSeek 兑现了自己的诺言,开源了一款用于 Hopper GPU 的高效型 MLA 解码核:FlashMLA。 项目地址:https://github.com/deepseek-ai/FlashMLA 1:FlashMLA 是什么呀? MLA是DeepSeek大模型的重要技术创新点&…...
leetcode:136. 只出现一次的数字(python3解法)
难度:简单 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。 示例 1 …...
Isaac Sim与Isaac Lab初使用
目录 基于Omiverse下载Isaacsim安装Isaac Lab配置isaacsim环境测试克隆仓库配置python环境强化学习训练的测试 IsaacLab模板配置vscode环境ros接口安装 作为nvidia出品的仿真软件,很多机器人、机器狗【具身智能】都可以有很不错的效果,所以会使用isaac s…...
Spring AI Alibaba 工具(Function Calling)使用
一、工具(Function Calling)简介 Spring AI Alibaba工具(Function Calling):https://java2ai.com/docs/1.0.0-M6.1/tutorials/function-calling/ 1、工具(Function Calling) “工具(Tool)”或“功能调用(Function Calling…...
Touch Diver:Weart为XR和机器人遥操作专属设计的触觉反馈动捕手套
在虚拟现实(VR)和扩展现实(XR)领域,触觉反馈技术正逐渐成为提升沉浸感和交互体验的重要因素。Weart作为这一领域的创新者,凭借其TouchDIVER Pro和TouchDIVER G1触觉手套,为用户带来了高度逼真的…...
[深度学习]图片分类任务
图片分类任务 文章目录 图片分类任务分类任务回归和分类如何做分类的输出 图片分类卷积神经网络保持特征图大小不变更大的卷积核和更多的卷积核层数特征图怎么变小卷积神经网络中特征图改变卷积到全连接分类任务的LOSS一个基本的分类神经网络 经典神经网络AlexNetVggNetResNet …...
关系图:赋能数据可视化的动态扩展
关系图 关系图是一种用于展示节点之间关系和连接的图表类型。具有高度的可定制性、丰富的交互功能和动画效果,能够展示节点之间的和连接,以及随着数据的变化而呈现的动态效果。 【组件概述】 1.节点和边的可定制性: 关系图提供了丰富的配置…...
k8s存储介绍(三)valume概述与emptydir
目录 一、Kubernetes 中的 Volume 详解 基本概念 Volume 的主要类型(这里简单介绍,后续章节会详细介绍) 1. 本地存储类型 2. 网络存储类型 3. 云提供商存储 4. 特殊用途类型 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) S…...
Nodejs 项目打包部署方式
方式一:PM2 一、准备工作 确保服务器上已安装 Node.js 环境建议使用 PM2 进行进程管理(需要额外安装) 二、部署步骤 1.首先在服务器上安装 PM2(推荐): npm install -g pm22.将项目代码上传到服务器&…...
uv - Getting Started 开始使用 [官方文档翻译]
文章目录 uv亮点安装项目脚本工具Python 版本pip 接口了解更多 入门安装 uv安装方法独立安装程序PyPICargoHomebrewWinGetScoopDockerGitHub 发布 升级 uvShell 自动补全卸载 第一次使用 uv特性Python 版本脚本项目工具pip 接口实用工具 获取帮助帮助菜单查看版本故障排除问题在…...
C++类与对象的的第三个简单的实战练习-3.25笔记
哔哩哔哩C面向对象高级语言程序设计教程(118集全) 简单实战三 创建项目 打开VS,点击创建一个新项目 创建一个空项目 点击下一步 点击工程名称,选择添加 选择新建项 选择C类 取名 点击确定,这时候还需要一个main.cpp …...
CentOS安装sshpass工具-自动化SSH密码认证
sshpass是一个在Linux环境下用于自动化SSH密码认证的工具。 一、功能特点 自动化SSH登录:sshpass允许用户在命令行中直接传递密码,从而无需在SSH连接时手动输入密码。这对于自动化脚本和批处理任务非常有用,因为它可以在非交互式环境下完成…...
k8s中service概述(一)ClusterIP
ClusterIP 是 Kubernetes 中最基础且常用的 Service 类型,主要用于在集群内部提供稳定的网络访问端点。以下是关于 ClusterIP Service 的详细说明: 1. ClusterIP 的核心功能 集群内部访问:ClusterIP 提供一个集群内部的虚拟 IP(VI…...
详解接口的常见请求方式
详解接口的常见请求方式 一、 常见接口请求方式1. GET2. POST3. PUT4. DELETE5. PATCH6. HEAD7. OPTIONS 二、 实现方法1. 前端实现2. 后端实现 三、 作用与主要区别四、 举例讲解1. 创建 Spring Boot 工程2. 添加依赖3. 编写 Controller 实现接口关键点说明 4. 启动与测试5. 总…...
HarmonyOS-ArkUI Grip组件
我们在学习List的时候,已经捎带引入了Grid。讲解如下图所示: 也就是,如果一个表,长宽基本都是一致的,那么此时可以完全不用Grid也可以实现,并且,优先考虑的就是List。 如果List实现不了的情况下…...
2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip
一、资料列表 第一课:Deepseek基础入门 第二课:DeepSeek赋能职场 第三课:普通人如何抓住DeepSeek红利 第四课:让科研像聊天一样简单 第五课:DeepSeek与AI幻觉 第六课:基于DeepSeek的AI音乐词曲的创造法 第…...
jupyter使用过程中遇到的问题
1、No module named ‘notebook.extensions’ 报错内容为: No module named notebook.extensions解决办法 出现这个错误代表你尝试给 Jupyter notebook 安装自动补全的插件,但是 notebook 没安装成功; 解决办法:不用 pip 安装 n…...
mac vim命令快捷键
目录 移动光标插入模式复制/粘贴删除搜索/替换退出 移动光标 快捷键说明0 / ^跳到行首,移动到光标所在行的"行首"$跳到行末,移动到光标所在行的"行尾"gg跳到文件第一行G移动到文章的最后[n]G跳到第n行w光标跳到下个字的开头e光标跳…...
【Golang】defer与recover的组合使用
在Go语言中,defer和recover是两个关键特性,通常结合使用以处理资源管理和异常恢复。以下是它们的核心应用场景及使用示例: 1. defer 的应用场景 defer用于延迟执行函数调用,确保在函数退出前执行特定操作。主要用途包括ÿ…...
低代码配置式Web组态解析
低代码配置式Web组态技术通过可视化操作和预置组件库,大幅降低开发门槛,适用于工业控制、物联网监控、数据可视化等场景。以下是综合行业实践和产品特性的分析: 一、核心功能与优势 可视化编辑与拖拽布局 提供图形化编辑器࿰…...
KiLog2MaximumIncrement的由来和KiMaximumIncrementReciprocal的由来
第一部分:KiLog2MaximumIncrement的由来 i 1; j KeMaximumIncrement; while ((1UI64<<i) < KeMaximumIncrement) { i; } KiLog2MaximumIncrement i; 2^17131072 2^18262144 i18KiLog2MaximumIncrement 中…...
基于web的家政服务网站
内容摘要 由于互联网的使用,人们在管理、应用、服务等领域使用数据更加简洁、方便,大大提高了工作效率。互联网正逐渐融入我们的生活,影响和改变我们的生活。 家政服务管理系统是典型的信息管理系统(MIS)。其开发主要…...
