Android端Office文档预览实践:TBS与AgentWeb集成指南
2026/9/12 11:42:34 网站建设 项目流程

简介:面向Android开发者的Office文档在线预览实现方案,围绕TBS腾讯浏览服务、AgentWeb、pdfjs及系统Intent能力展开,覆盖Word、Excel、PowerPoint与PDF等常见格式的加载与展示。内容详解TBS内置Office解析原理、AgentWeb容器封装与自定义进度条、pdfjs本地化渲染方案,并兼顾后端流式传输、权限控制与加密保护,适合需要为企业应用嵌入文档预览模块或自研预览引擎的中高级开发者。

资源包共64个文件,以Java源码、XML布局与配置、Gradle构建脚本、proguard混淆规则及WebP/PNG图标资源为主,另有jar依赖与properties配置,整体约443KB,结构清晰便于直接对照工程学习。已有1903人学习下载,可借助其中完整的Android工程结构与构建文件,快速验证TBS与pdfjs的集成路径,并理解文档预览中按类型选择渲染方式、处理异步加载与错误异常等工程化设计。

1. 从 Office 预览需求说起:为什么这个功能这么难做

在企业应用和移动办公场景里,在 Android 上预览 Office 文档几乎是一个绕不开的刚需,但绝大多数开发者第一次接触这个需求时,都会被同一个问题卡住:Android 原生系统压根没有内置 Office 解析引擎。Word、Excel、PPT 这些格式本质上是 OOXML 规范下的压缩包,系统自带的 WebView 只能渲染 HTML,既不能解析 docx 的 XML 结构,也无法计算 Excel 的单元格行列。很多团队的思路是把文件传到服务器转成 PDF 再回传预览,但这引入了一个巨大的问题——先在服务器上安装 LibreOffice 或 OnlyOffice,再处理转换队列、缓存策略和并发请求,这已经完全越出了客户端开发的范畴。我接手的这个 AndroidOfficeView 项目走的是另一种更务实的路线:完全在端侧解决,用 TBS 腾讯浏览服务的文档转换内核处理 Office 格式,用 pdfjs 处理 PDF,再用 AgentWeb 作为统一的 WebView 容器来承载这些渲染逻辑。这适用于那些不想依赖服务器转换服务、文件又需要快速预览的安卓应用场景,不管你是做 OA 系统、IM 工具还是企业网盘,这套组合都能把预览功能直接嵌入到应用内部,而不需要跳转到第三方 App。后端的角色被压缩到最小——只负责文件流的分发和权限校验,文档解析和渲染全部下沉到客户端完成。

2. 预览方案选型:为什么是 TBS 加 AgentWeb 而不是原生 WebView

2.1 原生 WebView 的边界和瓶颈

Android 的 WebView 本质上是一个浏览器内核的托管封装,它擅长的是加载网页、执行 JavaScript、渲染 CSS,但没有任何一种机制能直接理解二进制格式。虽然 Android 5.0 之后系统内置了 PdfRenderer 类用于加载 PDF,但它只支持静态页面的逐页渲染,遇到分页、缩放、文字选择这些阅读器刚需就显得力不从心。而 Office 文件更麻烦,docx 的文档结构、xlsx 的工作簿结构、pptx 的幻灯片布局,WebView 连这些文件的 MIME 类型都无法正确识别,更别说解析了。用一个比喻来理解:WebView 是一个能打开任何网页内容的浏览器窗口,但 Office 文件是压缩包内的 XML 加二进制资源组合体,浏览器内核没有解压和重新排版的能力。所以在纯原生方案下,唯一的出路是调用系统 Intent 将文件抛给第三方 App(如 Microsoft Office 或 WPS),但这要求用户的设备上已经安装了这些应用,且会在应用间发生一次跳转——这两点在政企环境下往往都不可接受。这也正是 TBS 要解决的问题。

2.2 TBS 文档转换内核的工作机制

TBS(腾讯浏览服务)不是一个 WebView 替代品,它是腾讯对 Chromium 内核的一次深度定制,内置了一套完整的 Office 文档解析组件。这套组件的核心设计是:不改变文档本身,而是在客户端本地完成格式转换后以 Web 页面形式呈现。TBS 的文档预览服务会在初始化时加载一个动态链接库,这个库里封装了 OOXML 格式解析器、排版引擎和渲染器。当客户端传入一个 Office 文件的 URL 时,TBS 的加载流程是这样的:首先通过 HTTP 请求获取文件流并存入本地缓存目录,然后解析器读取文件的 ZIP 结构校验格式合法性,接着提取 document.xml、sharedStrings.xml 这些关键文件并建立文档模型,最后将模型转换为 HTML DOM 结构加载到 WebView 中。这个转换过程发生在端侧,不产生服务器转换延迟,格式还原度也比较高。要开启 TBS 的 Office 预览,需要先初始化和加载内核,主线程在 Application 的 attachBaseContext 阶段执行初始化最稳妥。下面是具体的初始化代码:

// Application类中初始化TBS内核 public class OfficeApp extends Application { @Override public void onCreate() { super.onCreate(); // TBS内核初始化(必须在Application中调用) QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() { @Override public void onCoreInitFinished() { // 内核加载完成的回调,此时可以安全地创建WebView } @Override public void onInitFinished(boolean success) { // 初始化是否成功,失败时可能需要降级到系统WebView Log.i("TBS", "init result: " + success); } }); } }

这段代码中的 initX5Environment 方法会检查设备上是否已有 TBS 内核的共享运行时,如果没有则下载内核资源。回调返回的 boolean 值直接决定后续策略,我一般会把它存到一个静态变量中,等到创建 WebView 时判断,如果内核加载失败就回退到系统 WebView,再走 Intent 跳转的逻辑。这里有两个参数很容易被忽略:onCoreInitFinished 表示可以创建 TBS WebView 实例了,但文档解析能力完整可用还需要等 onInitFinished;而第二个回调里的 success 才是真正的全局开关。初始化是异步的,所以在 Splash 页面先触发初始化、进入文档预览页时再检查状态,是最常见也最稳妥的做法。

2.3 TBS 预览 Office 文档的 URL 拼接规则

TBS 提供了一套基于 URL Scheme 的文档预览协议,它的调用方式是拼出一个特殊格式的 URL,让承载 TBS 内核的 WebView 直接加载。这个 URL 的格式有一定规律,常见的模板是:

https://docs.qq.com/smartoffice/office/office.htm?_wv=16778245&fileurl=需要编码的文件地址

其中_wv参数控制 WebView 的渲染行为,16778245 是一个经验值,展开成二进制后对应开启文件访问、允许页面脚本运行、禁用文字选择这些开关的组合。后面拼接的fileurl必须是经过 URL 编码的文件完整地址。这里有个容易踩的坑:如果传入的是一个局域网内网地址,比如http://192.168.1.100/share/report.docx,TBS 的解析器会在客户端直接发起请求读取文件流,不走页面跳转,所以内网地址不需要额外配置白名单;但如果传入的是需要鉴权的接口地址,就必须先在本地下载文件再走本地 file:// 路径传过去。下面是在 AgentWeb 中实际加载预览页的代码,同时处理了文件下载和超时逻辑:

// 使用AgentWeb加载TBS预览URL private void loadOfficeFile(String fileUrl) { String encodedUrl = Uri.encode(fileUrl, ":/?&="); // 注意第二个参数:保留特殊字符不编码,否则fileurl会被截断 String tbsPreviewUrl = "https://docs.qq.com/smartoffice/office/office.htm" + "?_wv=16778245&fileurl=" + encodedUrl; agentWeb.getWebCreator().getWebView().loadUrl(tbsPreviewUrl); // 启动一个超时监控任务,10秒内如果页面没有产生标题则判定加载失败 handler.postDelayed(new Runnable() { @Override public void run() { String title = agentWeb.getWebCreator().getWebView().getTitle(); if (title == null || title.isEmpty()) { // 加载失败,切换降级方案 openWithExternalApp(fileUrl); } } }, 10000); }

这段代码里有一个很容易理解错的参数:Uri.encode 的第二个参数列表保存了:/?&=这几个字符,是因为fileurl本身就是一个完整的 URL,如果你把冒号和斜杠都编码成%3A%2F,TBS 解析器反而会因为格式无法识别而报错。超时监控是必须的,因为loadUrl返回成功只表示 WebView 接受了这个请求,不代表文档已经解析完——TBS 的转换过程发生在页面内部,一旦文档太大或格式不兼容,页面会一直白屏而没有任何错误回调。这种场景下判断失败的依据就是 WebView 的标题是否产生,TBS 在成功解析文档后会把标题改为文件名。

3. AgentWeb 容器与 PDF 预览的完整落地

3.1 AgentWeb 的核心价值:把 WebView 工程化

在上面的代码里,AgentWeb 扮演了容器角色。原生 WebView 的一个严重问题是生命周期的管理成本很高:Activity 的 onResume 里要调用 webView.onResume,onDestroy 里要移除 view 并销毁渲染进程,这些逻辑如果散落在各个页面代码里,几乎肯定会因为遗漏而产生内存泄漏和 WebView 进程卡死。AgentWeb 把这些问题收拢到了一个统一的控制器里。它本质上不是一个独立浏览器,而是对 WebView 封装了一个完整的生命周期跟 Android 组件生命周期绑定。下面是我在预览页中实际使用的 AgentWeb 初始化代码:

public class OfficePreviewActivity extends AppCompatActivity { private AgentWeb agentWeb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 创建AgentWeb实例,传入WebView容器父布局 agentWeb = AgentWeb.with(this) .setAgentWebParent((ViewGroup) findViewById(R.id.web_container)) .useDefaultIndicator() // 加载进度条 .setMainFrameErrorView(R.layout.web_error, -1) .setOpenOtherPageWays(AgentWeb.WebParentType.NEW_WINDOW) // 新窗口打开,防止文档中的链接覆盖预览页 .createAgentWeb() .ready() .go(getPreviewUrl(getIntent().getStringExtra("file_path"))); } @Override protected void onResume() { super.onResume(); agentWeb.getWebLifeCycle().onResume(); // 让WebView内部时钟继续运行 } @Override protected void onDestroy() { agentWeb.getWebLifeCycle().onDestroy(); // 覆盖JS执行、销毁渲染进程 super.onDestroy(); } }

这里值得展开的是 setOpenOtherPageWays 这个选项。默认情况下 WebView 遇到文档内的链接跳转会在当前 WebView 内打开,一旦用户点击了 Word 文档里的某个超链接,整个预览上下文就会丢失,返回时无法恢复到原来的文档位置。设置为 NEW_WINDOW 后,链接会在一个新的窗口上下文中创建,这对文档预览场景是语义正确的。另外 useDefaultIndicator 生成的细进度条能实时反映文档加载过程,因为 TBS 解析需要时间,没有进度反馈用户会认为应用卡死了。

3.2 pdfjs 的集成:本地渲染 PDF 的关键参数

PDF 是文档分发场景中最通用的格式,TBS 本身对 PDF 的解析能力足够阅读,但它依赖网络加载内核资源,而 pdfjs 是完全本地化的方案,离线场景也能用。pdfjs(Mozilla 的 PDF.js 项目)的原理是:通过 JavaScript 对 PDF 二进制文件进行逐字节解析,用 Canvas 呈现渲染结果。Android 集成 pdfjs 通常的做法是把 pdfjs 的构建产物放到 assets 目录,然后在 WebView 里加载它的入口页面,传入 PDF 文件路径。这里的核心参数是 file:// 路径的传递方式。

// 在 assets/pdfjs/web/viewer.html 的URL参数中注入PDF路径 String viewerUrl = "file:///android_asset/pdfjs/web/viewer.html" + "?file=" + Uri.encode("file:///storage/emulated/0/Download/temp.pdf"); // 同时WebView必须开启文件访问权限,否则pdfjs无法读取本地文件 webSetting.setAllowFileAccess(true); webSetting.setAllowUniversalAccessFromFileURLs(true);

这里有一个 Android 版本差异需要特别注意:从 Android 9(API 28)开始,WebView 默认禁止 file:// 协议下的页面读取其他 file:// 路径,也就是 file 域隔离。pdfjs 的 viewer.html 是 file:// 页面,内部需要通过 XMLHttpRequest 加载目标 PDF 文件的 file:// 地址,如果不开启 setAllowUniversalAccessFromFileURLs,XHR 会直接被拦截。这个设置在 API 30 以后也被废弃了,需要在 targetSdk 不变的情况下继续使用。除了权限问题,pdfjs 在移动端的渲染性能高度依赖一个参数——Canvas 的像素比。我处理过的一个实践是打开大尺寸 PDF(如 A0 图纸扫描件),PDF.js 默认按 CSS 尺寸渲染,当缩放级别过高时会触发锯齿和模糊。可以通过覆盖 viewer.js 中的 DEFAULT_SCALE 和 MAX_AUTO_SCALE 来调节:

// 在viewer.js中调整缩放控制参数 var DEFAULT_SCALE = 0.9; // 初始缩放 var MAX_AUTO_SCALE = 1.25; // 自动缩放的极限值

调参以后的效果是:文档首次渲染时不至于为了看完整页而把字号压得太小,而双击放大时又有上限,避免页面渲染时计算量过大导致卡死。如果是大尺寸文件(超过 50MB),pdfjs 的劣势也会暴露出来——它的内存占用和解析耗时与文件页数呈正相关。所以文件大小超过 80MB 时,我会跳过 pdfjs 直接走系统 Intent 交给第三方应用,这是更务实的容错策略。

3.3 系统能力兜底:Intent 跳转与 FileProvider

不管 TBS 还是 pdfjs 都有失败的可能,这时系统的 Intent 机制就是最后的兜底。Android 有一套基于 MIME 类型的隐式 Intent 匹配机制,只要设备上安装了能处理 Office 文件的 App,系统就会把它列出来供用户选择。实现这一步的技术要点是:从 Android 7.0(API 24)开始,应用不能在 Intent 里直接传递 file:// Uri 给其他应用,必须通过 FileProvider 生成 content:// Uri。下面是具体的实现:

private void openWithExternalApp(String filePath) { File file = new File(filePath); // 通过FileProvider获取content:// URI,绕过FileUriExposedException Uri contentUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file); Intent intent = new Intent(Intent.ACTION_VIEW); // 根据扩展名动态获取MIME类型 String mimeType = getMimeType(file.getName()); intent.setDataAndType(contentUri, mimeType); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (ActivityNotFoundException e) { // 设备上没有安装任何能处理此格式的App Toast.makeText(this, "没有可用的Office应用", Toast.LENGTH_LONG).show(); } }

这个代码有两个容易被忽视的坑。第一,FileProvider 的 authorities 必须是包名加后缀,且在 Manifest 里注册的android:authorities、代码中传入的第二个参数、XML 配置文件中的android:name三处必须一致,哪怕是字母大小写不同都会导致无法生成 URI。第二,如果设备上没有安装任何匹配的应用,startActivity会抛出ActivityNotFoundException,这个异常不是可选的处理逻辑,而是必须捕获,否则应用会直接崩溃。MIME 类型的获取一般用MimeTypeMap类,但 Office 2003 格式(.doc、.xls、.ppt)的 MIME 类型在 Android 系统表中并不存在,需要手动做一个映射表,把.doc映射到application/msword,把.xls映射到application/vnd.ms-excel

4. 加载性能优化与常见的解坑路径

4.1 文件下载策略:流式缓存与断点续传

无论是 TBS 还是 pdfjs,都需要把文件先拿到本地才能解析。最直接的实现是直接使用 OkHttp 下载到临时目录,但大文件场景下要处理的问题就多了:Office 文件动辄几十 MB(特别是带大量图片的 PPT),如果程序不做处理直接下载,弱网环境基本都会超时。我采取的策略是分块下载加通知栏进度反馈。更关键的决策是缓存目录的位置规划:我建立了两个目录,一个是cache/office_cache/用于 TBS 转换后产生的中间文件,另一个是files/documents/用于存放需要持久保留的原始文件。对不需要长期保存的预览文件,直接写在缓存目录,系统会在存储空间不足时自动回收;对用户主动导入的文档则持久化存储。下载完成后要校验文件完整性,避免下载了一半就传给 TBS 去解析。

// 下载完成后进行文件头校验,防止下载错误文件 private boolean isFileComplete(String filePath) { File file = new File(filePath); if (file.length() < 100) { return false; // 文件过小,很可能是一个错误页 } // 读取文件头字节,判断文件类型是否符合扩展名 try (InputStream is = new FileInputStream(file)) { byte[] header = new byte[8]; int len = is.read(header); if (len >= 4) { // PK是ZIP格式的文件头,docx/xlsx/pptx本质都是ZIP if (header[0] == 0x50 && header[1] == 0x4B) { return true; } // %PDF是PDF文件的固定开头 if (header[0] == 0x25 && header[1] == 0x50 && header[2] == 0x44 && header[3] == 0x46) { return true; } } } catch (IOException e) { Log.e("DOC", "read header failed", e); return false; } return false; }

这里判断文件是否完整的方法基于一个事实:docx、xlsx、pptx 本质上都是 ZIP 包,文件头固定是PK,PDF 文件头的特征是%PDF。如果下载过程中网络中断导致文件不完整,要么文件大小偏小(连续 8 个字节的PK头缺失),要么文件头不符合预期。还有一种现象在服务器端很常见:开发者下载的是代理服务器返回的 302 重定向页面,文件头以HTTP/1.1开头,这类错误文件也会被这段代码拦截下来。很多团队把下载失败归咎于网络抖动,实际上有相当比例是因为服务端错误路由,在加入了文件头校验后,这个问题的定位成本大幅降低了。

4.2 并发与内存:同一页面打开多个文档

一个经常被忽略的问题是:用户在预览 A 文档的同时,又打开了 B 文档,此时 Activity 栈里有两个 WebView 实例。Android 的 WebView 渲染是独立进程,每个进程占用的内存大约在 80 到 120MB 之间,两个实例同时存在先不说会不会 OOM,光是切换时的性能损耗就是可见的掉帧。更严重的是 TBS 内核在加载第二个文档时会因为上一个 WebView 未释放而出现资源抢占,表现为第一个预览页面的内容被清空。解决方案是一个 Activity 复用 WebView,不做跳转而是做文档替换:

// 在同一个AgentWeb实例中加载新文档 private void switchDocument(String newFileUrl) { String encodedUrl = Uri.encode(newFileUrl, ":/?&="); String tbsUrl = "https://docs.qq.com/smartoffice/office/office.htm" + "?_wv=16778245&fileurl=" + encodedUrl; agentWeb.getUrlLoader().loadUrl(tbsUrl); // 同时在文档列表界面维护当前预览的索引,以供返回时恢复 }

复用同一个 WebView 之后内存占用是稳定的,因为 WebView 不会因为loadUrl就重新创建进程。但如果新旧文档格式差异很大(比如从 Word 切换到 PDF),TBS 内核会先卸载旧格式的解析组件再加载新的解析组件。这个过程中的关键是不要在主线程做任何耗时操作,loadUrl本身是异步返回的,但解析器的加载是内部同步完成的。如果此时主线程有别的阻塞任务(比如数据库查询),会让 WebView 渲染线程等待,表现为白屏时间变长。所以我通常会给 switchDocument 包上一层带加载动画的遮罩层,避免用户在等待期间反复点击入口触发多次加载。

4.3 常见异常与日志定位手段

文档预览这个功能的调试难点在于,前端 JS 执行、TBS 本地解析、HTTP 下载三处都可能报错,但错误的表现形式都是白屏。所以要有一个统一的日志埋点策略:在每次预览操作开始时生成一个唯一的跟踪 ID,把下载的 HTTP 状态码、文件大小、TBS 初始化回调结果、AgentWeb 页面加载回调(onReceivedError 和 onPageFinished)全部记录到日志。我常用的定位流程如下:

  • 如果页面加载完成,但标题为空,说明 TBS 文档解析失败。此时需要直接访问file://路径读取文件,并用unzip -l检查文件内部结构是否完整——很多从网盘下载的文件会缺少[Content_Types].xml这个必需条目。
  • 如果onReceivedErrornet::ERR_CLEARTEXT_NOT_PERMITTED,说明目标文档地址是 HTTP 明文协议,而应用 targetSdk 28 以上默认禁止明文流量。需要在 Manifest 中配置android:usesCleartextTraffic="true"或者在 networkSecurityConfig 中声明特定域名的明文许可。
  • 如果 TBS 初始化返回 false,第一选择不是立即降级,而是先检查QbSdk.getTbsVersion返回的内核版本号,如果是 0,说明内核资源未成功下载。此时尝试删除data/data/包名/app_tbs目录后重启应用,让内核重新加载。

下面是日志记录的代码实现,核心是尽量记录上下文而不是只记一个布尔值:

private void trackPreviewEvent(String eventName, String fileUrl, boolean success, String detail) { JSONObject log = new JSONObject(); try { log.put("event", eventName); log.put("url", fileUrl); log.put("success", success); log.put("detail", detail); log.put("mem_info", Debug.getMemoryInfo(Debug.MemoryInfo())); Files.writeString(Paths.get(getExternalFilesDir(null) + "/preview.log"), log.toString() + "\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (Exception e) { Log.w("TRACK", "write log failed", e); } }

mem_info字段记录了触发事件时的应用内存信息,这一步对定位内存泄漏非常有价值——如果用户反复打开和关闭预览页,而mem_info里的 PSS 数值持续增长且不回落,基本可以确认存在 WebView 内存泄漏。相比直接在 Android Studio 的 Profiler 里盯内存曲线,这种线上日志的方式能更快暴露问题。

5. 文件类型检测:通过文件头而不是扩展名

预览的第一步不是加载,而是判断文件到底是什么格式。很多下载场景的文件扩展名是不可信的:服务器的 Content-Disposition 头可能忽略原始文件名,某些网盘分享链路会把 .docx 伪装成 .bin。我的做法是先读取文件头字节,再决定走 TBS、pdfjs 还是系统 Intent。下面是一个实用的检测函数:

object FileTypeDetector { enum class DocType { WORD, EXCEL, PPT, PDF, UNSUPPORTED } fun detect(file: File): DocType { val header = ByteArray(8) file.inputStream().use { it.readFully(header) } // PK 是 ZIP 格式的标志头 if (header[0] == 0x50.toByte() && header[1] == 0x4B.toByte()) { return when { file.name.endsWith("docx") || file.name.endsWith("doc") -> DocType.WORD file.name.endsWith("xlsx") || file.name.endsWith("xls") -> DocType.EXCEL file.name.endsWith("pptx") || file.name.endsWith("ppt") -> DocType.PPT else -> DocType.UNSUPPORTED } } // %PDF 是 PDF 文件的标准头部标记 if (header[0] == 0x25.toByte() && header[1] == 0x50.toByte() && header[2] == 0x44.toByte() && header[3] == 0x46.toByte() ) { return DocType.PDF } return DocType.UNSUPPORTED } }

这个检测看起来简单,但有一个细节容易被忽视:docx 和 doc 的头部不一样吗?其实新版 doc 格式也是基于 OOXML 规范,头部同样以PK开头,旧版 doc 采用D0 CF 11 E0 A1 B1 1A E1这个OLE2复合文档头。如果只检查PK而不检查扩展名,在遇到旧版 doc 文件时会走错预览通道。所以这里我保留了一个兜底逻辑:当文件头是PK但扩展名不匹配时,按照 ZIP 内部结构解析word/document.xml是否存在来判断。这个做法能识别出重命名过的 docx 文件,但对docdocx的伪转换文件就无能为力了。OLE2 格式的旧文档在现代 Android 设备上已经很少见,如果你的业务系统还在处理 2003 版 Office 文件,最好在这些文件上游统一转换为新格式再做存储,客户端不应该承担 20 年前文档格式的兼容责任。

6. 一个容易被忽略的坑:TBS 内核文件路径带特殊字符

收在细节上。TBS 的解析器对文件路径有一套自己的校验逻辑,它要求路径中不能包含空格和 Unicode 中文字符。如果应用把文件下载到了file:///storage/emulated/0/我的文档/汇报材料.docx,TBS 解析器会静默失败,页面停在 loading 状态,不抛任何异常。这是因为 TBS 的原生解析函数使用 C++ 实现,对路径的处理没有做 UTF-8 转码,底层直接将 char* 数组按单字节处理。这个问题的修正方式是在下载文件时规范化文件名:统一使用时间戳加扩展名作为内部存储名称:

private String sanitizeFileName(String originalName) { // 用时间戳取代中文或空格,TBS对纯ASCII路径兼容性最好 String ext = originalName.contains(".") ? originalName.substring(originalName.lastIndexOf(".")) : ".docx"; return "doc_" + System.currentTimeMillis() + ext; }

对应的读取逻辑也要反过来:需要显示标题时,再在 UI 层做一层映射,把原始文件名赋给 WebView 的标题或页面的document.title。另一个收尾建议是预览结束后统一清理缓存,因为 TBS 每次解析都会在app_tbs目录下产生中间文件,这个目录不会自动回收,几十个文档解析后缓存体积可能膨胀到几百 MB。清理时要注意顺序:先退出 WebView 再删除缓存文件。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询