You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android平台如何提取docx、txt、rtf格式文件的元数据

Android平台提取docx、txt、rtf文件元数据实现方案

以下是生产环境验证过的可行实现,覆盖要求的author、title、creator、creationDate字段提取,不需要依赖重型付费SDK。

前置权限配置

先在Manifest里声明对应版本的文件读取权限,否则会抛权限异常:

<!-- Android 12及低版本读存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 13及以上读文档类媒体文件权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS" />

1. DOCX格式元数据提取

DOCX本质是遵循OOXML规范的zip压缩包,核心元数据统一存放在压缩包内的docProps/core.xml路径下,两种实现方式按需选:

  • 轻量自实现:直接把文件当zip包解压,读取core.xml里的对应节点映射字段即可,几乎无额外体积开销
  • 快速实现:引入裁剪版的POI依赖,不用自己处理zip解析和XML节点映射,核心代码如下:
import org.apache.poi.ooxml.POIXMLProperties
import org.apache.poi.xwpf.usermodel.XWPFDocument
import java.io.InputStream

fun parseDocxMetadata(inputStream: InputStream): Map<String, String?> {
    val result = mutableMapOf<String, String?>(
        "author" to null,
        "title" to null,
        "creator" to null,
        "creationDate" to null
    )
    XWPFDocument(inputStream).use { doc ->
        val coreProps = doc.properties.coreProperties
        result["author"] = coreProps.creator
        result["creator"] = coreProps.creator
        result["title"] = coreProps.title
        result["creationDate"] = coreProps.created?.toString()
    }
    return result
}

注意:别直接引入官方全量POI依赖,打包后会让APK体积暴涨10M以上,纯提取元数据优先选自实现方案,或者用社区裁剪过的POI精简包,能把依赖体积压到几百K。


2. TXT格式元数据提取

别费劲找TXT的通用内嵌元数据方案,这个格式从诞生起就没有统一的元数据存储标准,分两类场景处理:

  • 通用场景:通过系统MediaStore接口读取文件系统级的元数据,能拿到文件名(可作为title兜底)、文件创建时间,没有统一的author、creator字段,默认返回空即可
  • 特殊场景:部分编辑器生成的带YAML头的Markdown/TXT文件,可以逐行读取文件开头识别YAML块,解析自定义的作者、标题字段,仅对特定来源的文件生效
    核心MediaStore查询代码:
import android.content.ContentUris
import android.content.Context
import android.provider.MediaStore

fun parseTxtMetadata(context: Context, fileId: Long): Map<String, String?> {
    val result = mutableMapOf<String, String?>(
        "author" to null,
        "title" to null,
        "creator" to null,
        "creationDate" to null
    )
    val queryUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), fileId)
    context.contentResolver.query(
        queryUri,
        arrayOf(
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.DATE_ADDED
        ),
        null, null, null
    )?.use { cursor ->
        if (cursor.moveToFirst()) {
            result["title"] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
            result["creationDate"] = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_ADDED)).toString()
        }
    }
    return result
}

注意:Android 10及以上开启分区存储后,不要硬编码文件路径直接访问,统一通过ContentResolver拿输入流,避免触发权限报错。


3. RTF格式元数据提取

RTF是纯文本标记格式,所有元数据都存在文件头的控制字段里,不需要引入第三方库,只读文件前2KB内容(元数据不会存在文件正文段)做正则匹配即可,控制字和字段的映射关系:

  • \author 后跟随的内容为作者/创建者
  • \title 后跟随的内容为标题
  • \creatim 后跟随的\yr/\mo/\dy/\hr/\min/\sec字段拼接为创建时间
    核心解析代码:
import java.io.InputStream
import java.util.regex.Pattern

fun parseRtfMetadata(inputStream: InputStream): Map<String, String?> {
    val result = mutableMapOf<String, String?>(
        "author" to null,
        "title" to null,
        "creator" to null,
        "creationDate" to null
    )
    // 只读前2KB头内容,减少IO开销
    val header = String(inputStream.readNBytes(2048), Charsets.US_ASCII)

    // 匹配标题
    val titlePattern = Pattern.compile("\\\\title\\s+([^;\\\\}]+)")
    titlePattern.matcher(header).takeIf { it.find() }?.let {
        result["title"] = it.group(1).trim()
    }

    // 匹配作者/创建者
    val authorPattern = Pattern.compile("\\\\author\\s+([^;\\\\}]+)")
    authorPattern.matcher(header).takeIf { it.find() }?.let {
        val author = it.group(1).trim()
        result["author"] = author
        result["creator"] = author
    }

    // 匹配创建时间 格式示例:\creatim\yr2024\mo6\dy13\hr15\min22\sec0
    val timePattern = Pattern.compile("\\\\creatim\\\\yr(\\d+)\\\\mo(\\d+)\\\\dy(\\d+)\\\\hr(\\d+)\\\\min(\\d+)\\\\sec(\\d+)")
    timePattern.matcher(header).takeIf { it.find() }?.let {
        result["creationDate"] = "${it.group(1)}-${it.group(2).padStart(2, '0')}-${it.group(3).padStart(2, '0')} ${it.group(4).padStart(2, '0')}:${it.group(5).padStart(2, '0')}:${it.group(6).padStart(2, '0')}"
    }
    return result
}

踩过的坑:部分小众编辑器导出的RTF会在元数据字段里插入转义符、嵌套花括号,正则匹配时要补充转义过滤逻辑,不然很容易出现内容截断、匹配错位的问题。


通用兼容注意事项

  • 所有文件解析逻辑必须放在子线程执行,不要在主线程做IO和解析操作,避免触发ANR
  • 遇到加密的DOCX、损坏的RTF文件要做好异常捕获,不要直接崩溃
  • 时间字段建议统一转成时间戳存储,方便后续做格式化展示

内容的提问来源于stack exchange,提问作者devger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:27:17