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 structur开发者_JAVA百科e in my zip. Is there any way to achieve this on android? Thanks in advance.
This code should do the trick.
Note: you must add file write permissions to your app by adding the WRITE_EXTERNAL_STORAGE permission to your manifest.xml file.
/*
*
* Zips a file at a location and places the resulting zip file at the toLocation
* Example: zipFileAtPath("downloads/myfolder", "downloads/myFolder.zip");
*/
public boolean zipFileAtPath(String sourcePath, String toLocation) {
final int BUFFER = 2048;
File sourceFile = new File(sourcePath);
try {
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream(toLocation);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
dest));
if (sourceFile.isDirectory()) {
zipSubFolder(out, sourceFile, sourceFile.getParent().length());
} else {
byte data[] = new byte[BUFFER];
FileInputStream fi = new FileInputStream(sourcePath);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(getLastPathComponent(sourcePath));
entry.setTime(sourceFile.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);
}
}
out.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/*
*
* Zips a subfolder
*
*/
private 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);
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();
}
}
}
/*
* gets the last path component
*
* Example: getLastPathComponent("downloads/example/fileToZip");
* Result: "fileToZip"
*/
public String getLastPathComponent(String filePath) {
String[] segments = filePath.split("/");
if (segments.length == 0)
return "";
String lastPathComponent = segments[segments.length - 1];
return lastPathComponent;
}
this is how I do it:
private static void zipFolder(String inputFolderPath, String outZipPath) {
try {
FileOutputStream fos = new FileOutputStream(outZipPath);
ZipOutputStream zos = new ZipOutputStream(fos);
File srcFile = new File(inputFolderPath);
File[] files = srcFile.listFiles();
Log.d("", "Zip directory: " + srcFile.getName());
for (int i = 0; i < files.length; i++) {
Log.d("", "Adding file: " + files[i].getName());
byte[] buffer = new byte[1024];
FileInputStream fis = new FileInputStream(files[i]);
zos.putNextEntry(new ZipEntry(files[i].getName()));
int length;
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
fis.close();
}
zos.close();
} catch (IOException ioe) {
Log.e("", ioe.getMessage());
}
}
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();
}
}
}
Use the zip4j library from this location. Import the jar file to your "app/libs/"
folder. And use the following code to zip your directories/files...
try {
File input = new File("path/to/your/input/fileOrFolder");
String destinationPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "zippedItem.zip";
ZipParameters parameters = new ZipParameters();
parameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
File output = new File(destinationPath);
ZipFile zipFile = new ZipFile(output);
// .addFolder or .addFile depending on your input
if (sourceFile.isDirectory())
zipFile.addFolder(input, parameters);
else
zipFile.addFile(input, parameters);
// Your input file/directory has been zipped at this point and you
// can access it as a normal file using the following line of code
File zippedFile = zipFile.getFile();
} catch (ZipException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
If you use a java.util.zip object then you can write a script that does not modify the directory structure.
public static boolean zip(File sourceFile, File zipFile) {
List<File> fileList = getSubFiles(sourceFile, true);
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
int bufferSize = 1024;
byte[] buf = new byte[bufferSize];
ZipEntry zipEntry;
for(int i = 0; i < fileList.size(); i++) {
File file = fileList.get(i);
zipEntry = new ZipEntry(sourceFile.toURI().relativize(file.toURI()).getPath());
zipOutputStream.putNextEntry(zipEntry);
if (!file.isDirectory()) {
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
int readLength;
while ((readLength = inputStream.read(buf, 0, bufferSize)) != -1) {
zipOutputStream.write(buf, 0, readLength);
}
}
}
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
IoUtils.closeOS(zipOutputStream);
}
return true;
}
public static List<File> getSubFiles(File baseDir, boolean isContainFolder) {
List<File> fileList = new ArrayList<>();
File[] tmpList = baseDir.listFiles();
for (File file : tmpList) {
if (file.isFile()) {
fileList.add(file);
}
if (file.isDirectory()) {
if (isContainFolder) {
fileList.add(file); //key code
}
fileList.addAll(getSubFiles(file));
}
}
return fileList;
}
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);
}
}
精彩评论