zip/compress a folder full of files on android

Viewed 39845

I need to zip up a "project" folder to allow users to share projects via email. I found a class for zipping up multiple files into one zip, but I need to keep the folder structure in my zip. Is there any way to achieve this on android? Thanks in advance.

8 Answers

I have reworked the code from HailZeon to work properly under windows. Zip Entries must be closed before new ones are started and a starting "/" at entry names like "/file.txt" makes also problems

/**
 * Zips a Folder to "[Folder].zip"
 * @param toZipFolder Folder to be zipped
 * @return the resulting ZipFile
 */
public static File zipFolder(File toZipFolder) {
    File ZipFile = new File(toZipFolder.getParent(), format("%s.zip", toZipFolder.getName()));
    try {
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(ZipFile));
        zipSubFolder(out, toZipFolder, toZipFolder.getPath().length());
        out.close();
        return ZipFile;
    } catch (Exception ex) {
        ex.printStackTrace();
        return null;
    }
}

/**
 * Main zip Function
 * @param out Target ZipStream
 * @param folder Folder to be zipped
 * @param basePathLength Length of original Folder Path (for recursion)
 */
private static void zipSubFolder(ZipOutputStream out, File folder, int basePathLength) throws IOException {

    final int BUFFER = 2048;

    File[] fileList = folder.listFiles();
    BufferedInputStream origin = null;
    for (File file : fileList) {
        if (file.isDirectory()) {
            zipSubFolder(out, file, basePathLength);
        } else {
            byte data[] = new byte[BUFFER];

            String unmodifiedFilePath = file.getPath();
            String relativePath = unmodifiedFilePath.substring(basePathLength + 1);

            FileInputStream fi = new FileInputStream(unmodifiedFilePath);
            origin = new BufferedInputStream(fi, BUFFER);

            ZipEntry entry = new ZipEntry(relativePath);
            entry.setTime(file.lastModified()); // to keep modification time after unzipping
            out.putNextEntry(entry);

            int count;
            while ((count = origin.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }
            origin.close();
            out.closeEntry();
        }
    }
}

Just converting @HailZeon's answer to Kotlin:

import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import java.util.zip.ZipException

private const val BUFFER = 2048

/**
 * Compresses a file into a zip file
 * @author Arnau Mora
 * @since 20210318
 * @param file The source file
 * @param target The target file
 *
 * @throws NullPointerException If the entry name is null
 * @throws IllegalArgumentException If the entry name is longer than 0xFFFF byte
 * @throws SecurityException If a security manager exists and its SecurityManager.checkRead(String)
 * method denies read access to the file
 * @throws ZipException If a ZIP format error has occurred
 * @throws IOException If an I/O error has occurre
 */
@Throws(
    NullPointerException::class,
    IllegalArgumentException::class,
    SecurityException::class,
    ZipException::class,
    IOException::class
)
fun zipFile(file: File, target: File) {
    val origin: BufferedInputStream
    val dest: FileOutputStream
    var zipOutput: ZipOutputStream? = null
    try {
        dest = target.outputStream()
        zipOutput = ZipOutputStream(dest.buffered(BUFFER))
        if (file.isDirectory)
            zipSubFolder(zipOutput, file, file.parent!!.length)
        else {
            val data = ByteArray(BUFFER)
            val fi = file.inputStream()
            origin = fi.buffered(BUFFER)
            val entry = ZipEntry(getLastPathComponent(file.path))
            entry.time = file.lastModified()
            zipOutput.putNextEntry(entry)
            var count = origin.read(data, 0, BUFFER)
            while (count != -1) {
                zipOutput.write(data, 0, count)
                count = origin.read(data, 0, BUFFER)
            }
        }
    } finally {
        zipOutput?.close()
    }
}

private fun zipSubFolder(zipOutput: ZipOutputStream, folder: File, basePathLength: Int) {
    val files = folder.listFiles() ?: return
    var origin: BufferedInputStream? = null
    try {
        for (file in files) {
            if (file.isDirectory)
                zipSubFolder(zipOutput, folder, basePathLength)
            else {
                val data = ByteArray(BUFFER)
                val unmodifiedFilePath = file.path
                val relativePath = unmodifiedFilePath.substring(basePathLength)
                val fi = FileInputStream(unmodifiedFilePath)
                origin = fi.buffered(BUFFER)
                val entry = ZipEntry(relativePath)
                entry.time = file.lastModified()
                zipOutput.putNextEntry(entry)
                var count = origin.read(data, 0, BUFFER)
                while (count != -1) {
                    zipOutput.write(data, 0, count)
                    count = origin.read(data, 0, BUFFER)
                }
            }
        }
    } finally {
        origin?.close()
    }
}

/*
 * gets the last path component
 *
 * Example: getLastPathComponent("downloads/example/fileToZip");
 * Result: "fileToZip"
 */
private fun getLastPathComponent(filePath: String): String {
    val segments = filePath.split("/").toTypedArray()
    return if (segments.isEmpty()) "" else segments[segments.size - 1]
}

If someone comes here trying to find a java8 cleaner refactored version of the @HailZeon code. Here it is:

    private static final int BUFFER_SIZE = 2048;

    public File zip(File source, String zipFileName) throws IOException {
        File zipFile = null;
        if(source != null) {
            File zipFileDestination = new File(getCacheDir(), zipFileName);
            if (zipFileDestination.exists()) {
                zipFileDestination.delete();
            }
            zipFile = zip(source, zipFileDestination);
        }
        return zipFile;
    }

    public File zip(File source, File zipFile) throws IOException {
        int relativeStartingPathIndex = zipFile.getAbsolutePath().lastIndexOf("/") + 1;
        if (source != null && zipFile != null) {
            try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream( new FileOutputStream(zipFile)))) {
                if (source.isDirectory()) {
                    zipSubDir(out, source, relativeStartingPathIndex);
                } else {
                    try (BufferedInputStream origin = new BufferedInputStream(new FileInputStream(source))) {
                        zipEntryFile(origin, out, source, relativeStartingPathIndex);
                    }
                }
            }
        }

        return zipFile;
    }

    private void zipSubDir(ZipOutputStream out, File dir, int relativeStartingPathIndex) throws IOException {

        File[] files = dir.listFiles();
        if (files != null) {

            for(File file : files) {
                if(file.isDirectory()) {
                    zipSubDir(out, file, relativeStartingPathIndex);
                } else {
                    try (BufferedInputStream origin = new BufferedInputStream(new FileInputStream(file))) {
                        zipEntryFile(origin, out, file, relativeStartingPathIndex);
                    }
                }
            }

        }
    }

    private void zipEntryFile(BufferedInputStream origin, ZipOutputStream out, File file, int relativeStartingPathIndex) throws IOException {
        String relativePath = file.getAbsolutePath().substring(relativeStartingPathIndex);
        ZipEntry entry = new ZipEntry(relativePath);
        entry.setTime(file.lastModified()); // to keep modification time after unzipping
        out.putNextEntry(entry);
        byte[] data = new byte[BUFFER_SIZE];
        int count;
        while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
            out.write(data, 0, count);
        }
    }
Related