如何在Android中以编程方式解压文件?

150

我需要一个小的代码片段,可以从给定的 .zip 文件中解压一些文件,并按照它们在压缩文件中的格式分别提取出这些文件。请分享您的知识并帮助我。


3
你可以在这里获取 Kotlin 的解决方案 - https://stackoverflow.com/a/50990992/1162784 - arsent
16个回答

2
这里是一个 ZipFileIterator(类似于 Java 的 Iterator,但用于 zip 文件):
package ch.epfl.bbp.io;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipFileIterator implements Iterator<File> {

    private byte[] buffer = new byte[1024];

    private FileInputStream is;
    private ZipInputStream zis;
    private ZipEntry ze;

    public ZipFileIterator(File file) throws FileNotFoundException {
    is = new FileInputStream(file);
    zis = new ZipInputStream(new BufferedInputStream(is));
    }

    @Override
    public boolean hasNext() {
    try {
        return (ze = zis.getNextEntry()) != null;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
    }

    @Override
    public File next() {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int count;

        String filename = ze.getName();
        File tmpFile = File.createTempFile(filename, "tmp");
        tmpFile.deleteOnExit();// TODO make it configurable
        FileOutputStream fout = new FileOutputStream(tmpFile);

        while ((count = zis.read(buffer)) != -1) {
        baos.write(buffer, 0, count);
        byte[] bytes = baos.toByteArray();
        fout.write(bytes);
        baos.reset();
        }
        fout.close();
        zis.closeEntry();

        return tmpFile;

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    }

    @Override
    public void remove() {
    throw new RuntimeException("not implemented");
    }

    public void close() {
    try {
        zis.close();
        is.close();
    } catch (IOException e) {// nope
    }
    }
}

你认为同样的代码可以用于解压或拆包APK扩展文件obb文件吗? - LOG_TAG

2
根据zapl的回答,在Closeable周围添加try()会在使用后自动关闭流。
public static void unzip(File zipFile, File targetDirectory) {
    try (FileInputStream fis = new FileInputStream(zipFile)) {
        try (BufferedInputStream bis = new BufferedInputStream(fis)) {
            try (ZipInputStream zis = new ZipInputStream(bis)) {
                ZipEntry ze;
                int count;
                byte[] buffer = new byte[Constant.DefaultBufferSize];
                while ((ze = zis.getNextEntry()) != null) {
                    File file = new File(targetDirectory, ze.getName());
                    File dir = ze.isDirectory() ? file : file.getParentFile();
                    if (!dir.isDirectory() && !dir.mkdirs())
                        throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath());
                    if (ze.isDirectory())
                        continue;
                    try (FileOutputStream fout = new FileOutputStream(file)) {
                        while ((count = zis.read(buffer)) != -1)
                            fout.write(buffer, 0, count);
                    }
                }
            }
        }
    } catch (Exception ex) {
        //handle exception
    }
}

使用从Jon Skeet的答案中得到的C# .NET 4中的Constant.DefaultBufferSize(65536)Stream.CopyTo,链接在这里:https://dev59.com/B3RC5IYBdhLWcg3wFdJx#411605 我总是看到帖子使用byte[1024]或byte[4096]缓冲区,从来不知道它可以更大,这可以提高性能并仍然正常工作。
这是Stream源代码:https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs
//We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K).
// The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant
// improvement in Copy performance.

private const int _DefaultCopyBufferSize = 81920;
然而,我将其降低到65536,这也是4096的倍数,只是为了更安全。

1
这是该线程中最佳的解决方案。此外,我还会在FileOutputStream的堆栈中使用BufferedOutputStream。 - MarkoR

2

这是一个简单的示例,我用它来从我的zip文件中解压缩特定文件到我的应用程序缓存文件夹中。然后我使用不同的方法读取清单文件。

private void unzipUpdateToCache() {
    ZipInputStream zipIs = new ZipInputStream(context.getResources().openRawResource(R.raw.update));
    ZipEntry ze = null;

    try {

        while ((ze = zipIs.getNextEntry()) != null) {
            if (ze.getName().equals("update/manifest.json")) {
                FileOutputStream fout = new FileOutputStream(context.getCacheDir().getAbsolutePath() + "/manifest.json");

                byte[] buffer = new byte[1024];
                int length = 0;

                while ((length = zipIs.read(buffer))>0) {
                    fout.write(buffer, 0, length);
                }
                zipIs .closeEntry();
                fout.close();
            }
        }
        zipIs .close();

    } catch (IOException e) {
        e.printStackTrace();
    }

}

1

密码保护的压缩文件

如果您想要使用密码压缩文件,可以查看 这个库,它可以轻松地对文件进行加密压缩:

Zip:

ZipArchive zipArchive = new ZipArchive();
zipArchive.zip(targetPath,destinationPath,password);

解压缩:
ZipArchive zipArchive = new ZipArchive();
zipArchive.unzip(targetPath,destinationPath,password);

Rar:

RarArchive rarArchive = new RarArchive();
rarArchive.extractArchive(file archive, file destination);

这个库的文档已经很好了,我只是从中添加了一些示例。它完全免费,并专门为安卓编写。

0

这里是更简洁的 @arsent 解决方案:

fun File.unzip(to: File? = null) {
    val destinationDir = to ?: File(parentFile, nameWithoutExtension)
    destinationDir.mkdirs()

    ZipFile(this).use { zipFile ->
        zipFile
            .entries()
            .asSequence()
            .filter { !it.isDirectory }
            .forEach { zipEntry ->
                val currFile = File(destinationDir, zipEntry.name)
                currFile.parentFile?.mkdirs()
                zipFile.getInputStream(zipEntry).use { input ->
                    currFile.outputStream().use { output -> input.copyTo(output) }
                }
            }
    }
}

0
在 Kotlin 中,这个函数可能会有帮助:
如果此路径是内部存储,则无需任何权限。
例如:
val file = File(context.getDir("Music", AppCompatActivity.MODE_PRIVATE),
                        "/$myFilename$myExtensionVar")

unpackZipFile(file.path,"zipFileName")

否则请为外部存储添加读/写权限。
 private fun unpackZipFile(path: String, zipFileName: String): Boolean {
        val `is`: InputStream
        val zis: ZipInputStream
        try {
            var filename: String
            // Here put the Zip file path
            `is` = FileInputStream(path+zipFileName)
            zis = ZipInputStream(BufferedInputStream(`is`))
            var ze: ZipEntry
            val buffer = ByteArray(1024)
            var count: Int
            
            while (zis.nextEntry.also { ze = it } != null) {
                filename = ze.name
                
                if (ze.isDirectory) {
                    val fmd = File(path+ filename )
                    fmd.mkdirs()
                    continue
                }
                val fileOutputStream = FileOutputStream(path +filename )
                while (zis.read(buffer).also { count = it } != -1) {
                    fileOutputStream.write(buffer, 0, count)
                }
                fileOutputStream.close()
                zis.closeEntry()
            }
            zis.close()



        } catch (e: IOException) {
            e.printStackTrace()
            return false
        }catch (e:NullPointerException){
            e.printStackTrace()
            return false
        }
        return true
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接