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

如何无需解压预览Zip、Rar、7z等压缩包内的图片?

无需解压预览压缩包内图片(基于7-Zip-JBinding)

我需要实现无需解压就能预览Zip、Rar、7z等压缩文件里的图片。之前用ZipFile和ZipEntry只能处理Zip格式,所以换成了7-Zip-JBinding库,现在已经能获取到压缩包内图片的元数据,但不知道怎么实现图片预览功能。以下是我目前用这个库写的代码:

private suspend fun checkFileZip(filePath: String): MutableList<PhotoModel> {
    return withContext(Dispatchers.IO) {
        val list: MutableList<PhotoModel> = mutableListOf()
        runCatching {
            val randomAccessFile = RandomAccessFile(filePath, "r")
            val randomAccessFileStream = RandomAccessFileInStream(randomAccessFile)
            val inArchive = SevenZip.openInArchive(null, randomAccessFileStream)

            inArchive.simpleInterface.archiveItems?.forEach { item ->
                if (!item.isFolder) {
                    if (item.path.contains(".jpg") || item.path.contains(".png")) {
                        if (item.size > 10000) {
                            val photoModel = PhotoModel(
                                "$filePath/${item.path.toFolderName()}",
                                resolution = "",
                                sizePhoto = item.size,
                                lastModifiedPhoto = item.lastWriteTime.time,
                                isFileInApk = true,
                                zipFileString = filePath,
                                zipEntryString = filePath,
                            )
                            addPhotoModel(photoModel)
                            list.add(photoModel)
                        }
                    }
                }
            }
        }.onFailure {
            Log.d("error", "$filePath - ${it.message}")
        }
        return@withContext list
    }
}

解决方案:直接提取图片字节流并转换为可显示的图片对象

核心思路是从压缩包中直接提取图片的字节数据,不写入本地文件,再将字节流转换成对应平台的图片对象(比如Android的Bitmap)来实现预览。

1. 实现压缩项字节提取方法

添加工具方法,利用7-Zip-JBinding的回调接口提取指定压缩项的字节数组:

private fun extractItemBytes(inArchive: IInArchive, item: ArchiveItem): ByteArray? {
    return runCatching {
        val outStream = ByteArrayOutputStream()
        val extractCallback = object : IArchiveExtractCallback {
            override fun setTotal(total: Long) {}
            override fun setCompleted(complete: Long) {}
            override fun getStream(index: Int, askExtractMode: ExtractAskMode): ISequentialOutStream {
                return ISequentialOutStream { data ->
                    outStream.write(data)
                    data.size.toLong()
                }
            }
            override fun prepareOperation(extractAskMode: ExtractAskMode) {}
            override fun setOperationResult(extractResult: ExtractOperationResult) {}
        }
        // 获取当前压缩项的索引
        val itemIndex = inArchive.simpleInterface.archiveItems?.indexOf(item) ?: -1
        if (itemIndex != -1) {
            inArchive.extract(arrayOf(itemIndex.toLong()), false, extractCallback)
        }
        outStream.toByteArray()
    }.getOrNull()
}

2. 修改PhotoModel存储图片字节

给PhotoModel添加字段存储图片的字节数组,方便后续在UI层转换:

data class PhotoModel(
    val path: String,
    val resolution: String,
    val sizePhoto: Long,
    val lastModifiedPhoto: Long,
    val isFileInApk: Boolean,
    val zipFileString: String,
    val zipEntryString: String,
    var imageBytes: ByteArray? = null // 新增字段存储图片字节
) {
    // 重写equals和hashCode,处理字节数组的比较
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as PhotoModel
        if (path != other.path) return false
        if (resolution != other.resolution) return false
        if (sizePhoto != other.sizePhoto) return false
        if (lastModifiedPhoto != other.lastModifiedPhoto) return false
        if (isFileInApk != other.isFileInApk) return false
        if (zipFileString != other.zipFileString) return false
        if (zipEntryString != other.zipEntryString) return false
        if (imageBytes != null) {
            if (other.imageBytes == null) return false
            if (!imageBytes.contentEquals(other.imageBytes)) return false
        } else if (other.imageBytes != null) return false
        return true
    }

    override fun hashCode(): Int {
        var result = path.hashCode()
        result = 31 * result + resolution.hashCode()
        result = 31 * result + sizePhoto.hashCode()
        result = 31 * result + lastModifiedPhoto.hashCode()
        result = 31 * result + isFileInApk.hashCode()
        result = 31 * result + zipFileString.hashCode()
        result = 31 * result + zipEntryString.hashCode()
        result = 31 * result + (imageBytes?.contentHashCode() ?: 0)
        return result
    }
}

3. 更新压缩包遍历逻辑,提取图片字节

修改原有的checkFileZip方法,遍历图片项时提取字节并存储到PhotoModel,同时注意释放资源:

private suspend fun checkFileZip(filePath: String): MutableList<PhotoModel> {
    return withContext(Dispatchers.IO) {
        val list: MutableList<PhotoModel> = mutableListOf()
        runCatching {
            val randomAccessFile = RandomAccessFile(filePath, "r")
            val randomAccessFileStream = RandomAccessFileInStream(randomAccessFile)
            val inArchive = SevenZip.openInArchive(null, randomAccessFileStream)

            inArchive.simpleInterface.archiveItems?.forEach { item ->
                if (!item.isFolder) {
                    val lowerPath = item.path.lowercase()
                    // 更准确的图片格式判断
                    if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".png")) {
                        if (item.size > 10000) {
                            // 提取当前图片项的字节数组
                            val itemBytes = extractItemBytes(inArchive, item)
                            val photoModel = PhotoModel(
                                "$filePath/${item.path.toFolderName()}",
                                resolution = "",
                                sizePhoto = item.size,
                                lastModifiedPhoto = item.lastWriteTime.time,
                                isFileInApk = true,
                                zipFileString = filePath,
                                zipEntryString = item.path, // 修正为图片项的路径,原代码写错了
                                imageBytes = itemBytes
                            )
                            addPhotoModel(photoModel)
                            list.add(photoModel)
                        }
                    }
                }
            }
            // 释放资源,避免内存泄漏
            inArchive.close()
            randomAccessFile.close()
        }.onFailure {
            Log.d("error", "$filePath - ${it.message}")
        }
        return@withContext list
    }
}

4. 在UI层转换字节为图片并显示

在主线程(Android环境)中,将PhotoModel的字节数组转换为Bitmap,大图片建议做压缩处理避免OOM:

// 压缩大图片的工具方法
fun decodeSampledBitmapFromBytes(bytes: ByteArray, reqWidth: Int, reqHeight: Int): Bitmap {
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
    // 计算采样率
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
    options.inJustDecodeBounds = false
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {
        val halfHeight = height / 2
        val halfWidth = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize
}

// 在UI层使用示例
photoModel.imageBytes?.let { bytes ->
    // 根据ImageView的尺寸压缩图片
    val bitmap = decodeSampledBitmapFromBytes(bytes, imageView.width, imageView.height)
    imageView.setImageBitmap(bitmap)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 09:35:57