当前位置: 首页 > news >正文

封装从url 拉取 HTML 并加载到 WebView 的完整流程

在 Android 中从 URL 拉取 HTML 并加载到 WebView 的完整流程

下面我将详细介绍在 Android 应用中从 URL 获取 HTML 内容并加载到 WebView 的完整实现流程,包括必要的权限、网络请求处理和 WebView 配置。

基本流程概述

  1. 1.

    添加网络权限

  2. 2.

    创建 WebView 并配置基本设置

  3. 3.

    使用 HttpURLConnection 或第三方库(如 OkHttp)从 URL 获取 HTML 内容

  4. 4.

    将获取的 HTML 内容加载到 WebView 中

  5. 5.

    处理加载过程中的各种状态和错误

问题思考:

原始 url 会重定向到目标 url1。关键是要拿到最终重定向后的地址作为 baseUrl,否则相对资源路径会404或跨域受限。

关键处理点

  • 使用 OkHttp 的最终请求地址作为 baseUrl:response.request().url().toString()
  • 确保跟随重定向已开启(默认是开启的),必要时显式设置
  • 同步 Cookie 到 WebView,保持同域资源请求一致性
  • 传递合理的 UA、Referer,避免服务端按 UA 返回不同内容
  • 若页面依赖 JS 动态渲染,仅抓 HTML 无法还原,需要考虑直接 loadUrl 或注入必要资源

下面给出“重定向取最终地址 + 编码稳妥解析 + Cookie 同步 + 优先用 loadDataWithBaseURL 渲染,必要时回退到 loadUrl”的完整实现,已逐行注释,并在后面附上简要逻辑整理。

private void getAsyncOkHttp(final String newsUrl) { // 异步获取 HTML 并加载到 WebView 的入口// 1) 兜底判空,避免空 URL 触发异常if (newsUrl == null || newsUrl.trim().length() == 0) { // 判断入参有效性LogUtils.loge("getAsyncOkHttp() newsUrl is empty"); // 记录日志if (handler != null) handler.sendMessage(handler.obtainMessage(400)); // 通知错误return; // 提前返回} // endtry { // 捕获整体流程异常,保证不崩溃// 2) 复用/延迟初始化 OkHttpClient(连接池/缓存/拦截器集中配置)//    注意:生产请不要使用全信任证书;此处保留你原项目的 SSL 定制入口,但默认走系统信任更安全final long CACHE_SIZE = 50L * 1024 * 1024; // 50MB 缓存大小if (OkHttpHolder.sClient == null) { // 单例初始化,避免每次 new 带来性能与资源浪费synchronized (OkHttpHolder.class) { // 双重检查锁,线程安全if (OkHttpHolder.sClient == null) { // 二次判空Cache cache = null; // 本地 HTTP 缓存try { // 创建缓存目录File cacheDir = new File(getCacheDir(), "http_cache"); // App 缓存目录cache = new Cache(cacheDir, CACHE_SIZE); // 实例化缓存对象} catch (Throwable e) { // 缓存创建失败不影响主流程LogUtils.loge("create http cache fail: " + e.getMessage()); // 记录异常} // endOkHttpClient.Builder b = new OkHttpClient.Builder() // 构建器.followRedirects(true) // 允许 HTTP 重定向.followSslRedirects(true) // 允许 HTTPS 重定向.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS) // 连接超时.readTimeout(15, java.util.concurrent.TimeUnit.SECONDS) // 读取超时.writeTimeout(15, java.util.concurrent.TimeUnit.SECONDS) // 写入超时.callTimeout(20, java.util.concurrent.TimeUnit.SECONDS); // 整体调用最大耗时if (cache != null) b.cache(cache); // 挂载缓存(可提升重复请求性能)// 统一 Header:UA/语言。Referer/Cookie 由具体请求决定(或在需要时追加)b.addInterceptor(chain -> { // 应用层拦截器,给所有请求加通用头Request origin = chain.request(); // 原始请求Request.Builder rb = origin.newBuilder() // 构建新请求.header("User-Agent", getDefaultUA()) // 统一 UA(使用 WebView UA 更贴近站点判断).header("Accept-Language", "zh-CN,zh;q=0.9"); // 统一语言偏好return chain.proceed(rb.build()); // 继续链路}); // end interceptor// 如你的项目必须自定义 SSL(不建议全信任),在此启用;默认注释更安全// b.sslSocketFactory(SSLSocketClient.createSSLSocketFactory(), new SSLSocketClient.TrustAllCerts());// b.hostnameVerifier(SSLSocketClient.getHostnameVerifier());OkHttpHolder.sClient = b.build(); // 构建单例客户端} // end second null-check} // end synchronized} // end if client null// 3) 构建请求(不在此加 Referer,避免与最终 baseUrl 不一致;必要时在回退 loadUrl 再补)Request request = new Request.Builder() // 新建请求.get() // GET 方法.url(newsUrl) // 目标 URL(可能发生重定向).build(); // 构建完成// 4) 发起异步请求OkHttpHolder.sClient.newCall(request).enqueue(new Callback() { // 异步回调@Overridepublic void onFailure(Call call, IOException e) { // 网络/IO 失败runOnUiThread(() -> { // 回到主线程LogUtils.loge("getAsyncOkHttp onFailure: " + e.getMessage()); // 记录异常if (handler != null) handler.sendMessage(handler.obtainMessage(400)); // 通知错误}); // end UI} // end onFailure@Overridepublic void onResponse(Call call, Response response) throws IOException { // 成功拿到响应try { // 处理响应体的异常保护// 5) 状态码检查(OkHttp 已跟随重定向;此处判断最终状态)if (!response.isSuccessful()) { // 非 2xxthrow new IOException("HTTP " + response.code()); // 抛出异常走统一兜底} // end// 6) 取最终 URL(重定向后的地址,作 baseUrl 解析相对资源)final String finalUrl = response.request().url().toString(); // 最终有效地址// 7) 读取响应体字节,注意先判空再取长度,避免 NPEResponseBody body = response.body(); // 响应体byte[] bytes = (body != null) ? body.bytes() : null; // 取字节数组if (bytes == null || bytes.length == 0) { // 判空throw new IOException("empty body"); // 统一异常处理} // end// 8) 基于 Content-Type 推断 charset,缺省回退 UTF-8(更稳)MediaType ct = (body != null) ? body.contentType() : null; // 内容类型java.nio.charset.Charset charset = (ct != null && ct.charset() != null)? ct.charset(): java.nio.charset.StandardCharsets.UTF_8; // 字符集final String htmlSource = new String(bytes, charset); // 以 charset 解码为字符串// 9) 简单“空页面/壳页面”启发式判断(SPA 可能首屏很少内容)final boolean looksEmpty = isProbablyEmptyHtml(htmlSource); // 启发式判断// 10) 将 Cookie 同步到 WebView(以便后续资源/XHR 保持会话一致)final java.util.List<String> setCookies = response.headers("Set-Cookie"); // 取服务端下发的 cookie 列表// 11) 回到主线程,进行 WebView 加载runOnUiThread(() -> { // UI 线程更新try { // UI 层异常保护if (webview == null) { // WebView 可能已销毁LogUtils.loge("webview is null (destroyed?)"); // 记录日志if (handler != null) handler.sendMessage(handler.obtainMessage(400)); // 通知错误return; // 结束} // end// 12) 基础 WebView 设置(只做关键能力开启,避免覆盖外部定制)enableWebViewBasics(webview); // 开启 JS/DOMStorage/混合内容/第三方 Cookie// 13) 同步服务端 Set-Cookie 到 WebView 的 CookieManagertry { // Cookie 同步过程保护if (setCookies != null && !setCookies.isEmpty()) { // 有服务端下发 cookieandroid.webkit.CookieManager cm = android.webkit.CookieManager.getInstance(); // Cookie 管理

文章转载自:

http://NdMKuZnG.fhtmp.cn
http://mhm7h8L3.fhtmp.cn
http://YHe8yYWQ.fhtmp.cn
http://POLAMuKO.fhtmp.cn
http://D6EUJWaN.fhtmp.cn
http://WHu1yZOo.fhtmp.cn
http://qVuMpol2.fhtmp.cn
http://jSBY3IVn.fhtmp.cn
http://mLJInh3s.fhtmp.cn
http://eu6O5VKh.fhtmp.cn
http://RkNSJnRC.fhtmp.cn
http://QtZ69cSR.fhtmp.cn
http://pLjAL1d1.fhtmp.cn
http://DL8gTP2z.fhtmp.cn
http://1FW34CHZ.fhtmp.cn
http://Ind0o4eZ.fhtmp.cn
http://4DuXvjkA.fhtmp.cn
http://HXY0QJG6.fhtmp.cn
http://LtLj5bIi.fhtmp.cn
http://iHHMhfdu.fhtmp.cn
http://u7jDmmqq.fhtmp.cn
http://UEmMunvp.fhtmp.cn
http://wiVqJj35.fhtmp.cn
http://e4d4xecG.fhtmp.cn
http://QI90pmsu.fhtmp.cn
http://Eqmv0o0S.fhtmp.cn
http://ZBbjqgQ1.fhtmp.cn
http://6MGT5QEm.fhtmp.cn
http://u23omX3i.fhtmp.cn
http://yXSjlm6J.fhtmp.cn
http://www.dtcms.com/a/380186.html

相关文章:

  • Python 批量处理:Markdown 与 HTML 格式相互转换
  • SOME/IP 协议深度解析
  • 变分自编码器详解与实现
  • 危险的PHP命令执行方法
  • 设计模式(C++)详解—抽象工厂模式 (Abstract Factory)(1)
  • 芯科科技FG23L无线SoC现已全面供货,为Sub-GHz物联网应用提供最佳性价比
  • 4步OpenCV-----扫秒身份证号
  • Qt的数据库模块介绍,Qt访问SQLite详细示例
  • 线性预热机制(Linear Warmup):深度学习训练稳定性的关键策略
  • 【Ansible】管理复杂的Play和Playbook知识点
  • 微软图引擎GraphEngine深度解析:分布式内存计算的技术革命
  • TBBT: FunWithFlags靶场渗透
  • Git .gitignore 文件不生效的原因及解决方法
  • Elasticsearch面试精讲 Day 16:索引性能优化策略
  • 开源AI大模型AI智能名片S2B2C商城小程序在互联网族群化中的作用与影响
  • 定制开发开源AI智能名片S2B2C商城小程序在互联网族群化中的作用与影响
  • 《人工智能AI之机器学习基石》系列 第 16 篇:关联规则与数据挖掘——“啤酒与尿布”传奇背后的增长秘密
  • DevExpress中Word Processing Document API学习记录
  • MR智能互动沙盘,让虚拟仿真实训更智能更高效
  • Linux基础命令:文件操作与系统管理
  • 在UniApp跨平台开发中实现相机自定义滤镜的链式处理架构
  • SigNoz分布式追踪新体验:cpolar实现远程微服务监控
  • 嵌入式数据结构笔记三——单向链表下
  • Proxmox VE远程管理虚拟化隐形入口用cpolar实现
  • discuz所有下载版本和升级工具
  • # AI(学习笔记第八课) 使用langchain的embedding models
  • 2025年渗透测试面试题总结-67(题目+回答)
  • 城市二次供水物联网监测管控管理平台御控解决方案:构建全链路智能水务新生态
  • Python Yolo8 物体识别
  • 一款VS Code连接和管理PostgreSQL的扩展插件,支持AI智能辅助和代理模式