如何无需解压预览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
相关产品推荐
相关产品推荐

