1

Recently, I have been working on a requirement, which is to download a Zip file from the background interface, and then decompress the file and then load the content inside. To decompress the Zip, a password is required to decompress it.

First of all, it is to download files. To download files, you can directly use OkHttp. The corresponding download code is as follows:

 /**
     * 下载zip文件
     *
     * @param url
     */
    private void downloadFile(final String url) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS);
        Request request = new Request.Builder().url(url).build();
        builder.build().newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
 
            }
 
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                InputStream is = null;
                byte[] buf = new byte[4096];
                int len = 0;
                FileOutputStream fos = null;
                // 储存下载文件的目录
                String savePath = Tools.isExistDir_html(filePath);
                try {
 
                    is = response.body().byteStream();
                    long total = response.body().contentLength();
//                    File file = new File(savePath, getNameFromUrl(url));
                    File file = new File(savePath, getNameFromUrl(fileName));
                    fos = new FileOutputStream(file);
                    long sum = 0;
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                        sum += len;
                        int progress = (int) (sum * 1.0f / total * 100);
                        // 下载中
 
                    }
                    fos.flush();
                } catch (Exception e) {
                    //
                    e.printStackTrace();
                } finally {
                    try {
                        if (is != null)
                            is.close();
                    } catch (IOException e) {
                    }
                    try {
                        if (fos != null)
                            fos.close();
                    } catch (IOException e) {
                    }
                }
                //下载完成,先删除过时数据,并解压最新的热更新数据(必须这样操作 不然会导致无法覆盖旧数据,导致数据更新失败)
                deleteOldData();
            }
        });
    }

The next step is to delete the previous file, because overwriting the data may cause some errors, the following is the relevant code of FileUtils.

 public class FileUtils {
  
  // 删除旧数据deleteOldData
  private void deleteOldData() {
        String dirPath = Constants.BLACKTECH_HOT_UPDATE_FILE_PATH;
        List<File> files = CommandUtils.getFilesInDirectory(dirPath);
        for (File file : files) {
            String fileName = file.getName();
            if (fileName.endsWith(Constants.UXUE_TEMP_FILE_SUFFIX)) {
//                String type = fileName.substring(0, fileName.indexOf("_"));
//                CommandUtils.deleteDirectory(dirPath + fileName);
                CommandUtils.deleteDirectory(dirPath);
                try {
                    ZipFileUtil.upZipFile(file, dirPath);
                    file.delete();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

 /**
     * 获取目录中的所有一级文件(不包括目录和嵌套文件)
     *
     * @param sPath
     * @return
     */
    public static List<File> getFilesInDirectory(String sPath) {
        List<File> result = new ArrayList<>();
        // 如果sPath不以文件分隔符结尾,自动添加文件分隔符
        if (!sPath.endsWith(File.separator)) {
            sPath = sPath + File.separator;
        }
        File dirFile = new File(sPath);
        // 如果dir对应的文件不存在,或者不是一个目录,则退出
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            return result;
        }
        File[] files = dirFile.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                result.add(file);
            }
        }
        return result;
    }
 
 /**
     * 删除目录(文件夹)以及目录下的文件
     *
     * @param sPath 被删除目录的文件路径
     * @return 目录删除成功返回true,否则返回false
     */
    public static boolean deleteDirectory(String sPath) {
        // 如果sPath不以文件分隔符结尾,自动添加文件分隔符
        if (!sPath.endsWith(File.separator)) {
            sPath = sPath + File.separator;
        }
        File dirFile = new File(sPath);
        // 如果dir对应的文件不存在,或者不是一个目录,则退出
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            return false;
        }
        boolean flag = true;
        // 删除文件夹下的所有文件(包括子目录)
        File[] files = dirFile.listFiles();
        for (int i = 0; files != null && i < files.length; i++) {
            if (files[i].getName().equals("YJHtml.zip")){
                continue;
            }
            // 删除子文件
            if (files[i].isFile()) {
                flag = deleteFile(files[i].getAbsolutePath());
                if (!flag)
                    break;
            } // 删除子目录
            else {
                flag = deleteDirectory(files[i].getAbsolutePath());
                if (!flag)
                    break;
            }
        }
        if (!flag)
            return false;
        // 删除当前目录
        if (dirFile.delete()) {
            return true;
        } else {
            return false;
        }
    }
 
 /**
     * 删除单个文件
     *
     * @param sPath 被删除文件的文件名
     * @return 单个文件删除成功返回true,否则返回false
     */
    public static boolean deleteFile(String sPath) {
        boolean flag = false;
        File file = new File(sPath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists()) {
            file.delete();
            flag = true;
        }
        return flag;
    }

After the file is downloaded, the next step is to decompress the file. If there is no password, you can directly use the Java Stream stream.

 /**
     * 解压缩功能.
     * 将zipFile文件解压到folderPath目录下
     */
    public static int upZipFile(File zipFile, String folderPath) throws Exception {
        try {
            ZipFile zfile = new ZipFile(zipFile);
            Enumeration zList = zfile.entries();
            ZipEntry ze = null;
            byte[] buf = new byte[1024];
            while (zList.hasMoreElements()) {
                ze = (ZipEntry) zList.nextElement();
                if (ze.isDirectory()) {
                    //Logcat.d("upZipFile", "ze.getName() = " + ze.getName());
                    String dirstr = folderPath + ze.getName();
                    //dirstr.trim();
                    dirstr = new String(dirstr.getBytes("8859_1"), "GB2312");
                    //Logcat.d("upZipFile", "str = " + dirstr);
                    File f = new File(dirstr);
                    f.mkdir();
                    continue;
                }
                OutputStream os = new BufferedOutputStream(new FileOutputStream(getRealFileName(folderPath, ze.getName())));
                InputStream is = new BufferedInputStream(zfile.getInputStream(ze));
                int readLen = 0;
                while ((readLen = is.read(buf, 0, 1024)) != -1) {
                    os.write(buf, 0, readLen);
                }
                is.close();
                os.close();
            }
            zfile.close();
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        return 0;
    }

If the Zip file is encrypted, it is also necessary to decrypt the Zip file. After trying Apache's zip solution and open source frameworks like winzipaes, I finally settled on zip4j. zip4j is an open source decompression solution that supports the following features:

  • Create, add, extract, update and remove files from ZIP archives
  • Read and write password-protected Zip files
  • Support AES 128/256 algorithm encryption
  • Support standard Zip algorithm encryption
  • Support zip64 format
  • Supports Store (non-compressed) and Deflate compression methods---do not understand
  • Create and extract files for chunked zip files
  • Supports Unicode encoding of filenames
  • progress monitoring

In general, zip4j uses UTF-8 encoding by default, so it supports Chinese, as well as passwords, and supports a variety of compression algorithms, which can be said to be powerful. Before use, you need to add zip4j dependencies to the project, and the way of dependencies supports Maven and Gradle.

 //Maven方式
<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.10.0</version>
</dependency>

//Gradle方式
implementation "net.lingala.zip4j:zip4j:1.3.1"

The following is a packaged zip4j tool class, which can be used directly.

 /** 
 * ZIP压缩文件操作工具类 
 * 支持密码 
 * 依赖zip4j开源项目(http://www.lingala.net/zip4j/) 
 * 版本1.3.1 
 * @author ninemax 
 */  
public class CompressUtil {  
      
    /** 
     * 使用给定密码解压指定的ZIP压缩文件到指定目录 
     * <p> 
     * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 
     * @param zip 指定的ZIP压缩文件 
     * @param dest 解压目录 
     * @param passwd ZIP文件的密码 
     * @return 解压后文件数组 
     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 
     */  
    public static File [] unzip(String zip, String dest, String passwd) throws ZipException {  
        File zipFile = new File(zip);  
        return unzip(zipFile, dest, passwd);  
    }  
      
    /** 
     * 使用给定密码解压指定的ZIP压缩文件到当前目录 
     * @param zip 指定的ZIP压缩文件 
     * @param passwd ZIP文件的密码 
     * @return  解压后文件数组 
     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 
     */  
    public static File [] unzip(String zip, String passwd) throws ZipException {  
        File zipFile = new File(zip);  
        File parentDir = zipFile.getParentFile();  
        return unzip(zipFile, parentDir.getAbsolutePath(), passwd);  
    }  
      
    /** 
     * 使用给定密码解压指定的ZIP压缩文件到指定目录 
     * <p> 
     * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 
     * @param zip 指定的ZIP压缩文件 
     * @param dest 解压目录 
     * @param passwd ZIP文件的密码 
     * @return  解压后文件数组 
     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 
     */  
    public static File [] unzip(File zipFile, String dest, String passwd) throws ZipException {  
        ZipFile zFile = new ZipFile(zipFile);  
        zFile.setFileNameCharset("GBK");  
        if (!zFile.isValidZipFile()) {  
            throw new ZipException("压缩文件不合法,可能被损坏.");  
        }  
        File destDir = new File(dest);  
        if (destDir.isDirectory() && !destDir.exists()) {  
            destDir.mkdir();  
        }  
        if (zFile.isEncrypted()) {  
            zFile.setPassword(passwd.toCharArray());  
        }  
        zFile.extractAll(dest);  
          
        List<FileHeader> headerList = zFile.getFileHeaders();  
        List<File> extractedFileList = new ArrayList<File>();  
        for(FileHeader fileHeader : headerList) {  
            if (!fileHeader.isDirectory()) {  
                extractedFileList.add(new File(destDir,fileHeader.getFileName()));  
            }  
        }  
        File [] extractedFiles = new File[extractedFileList.size()];  
        extractedFileList.toArray(extractedFiles);  
        return extractedFiles;  
    }  
      
    /** 
     * 压缩指定文件到当前文件夹 
     * @param src 要压缩的指定文件 
     * @return 最终的压缩文件存放的绝对路径,如果为null则说明压缩失败. 
     */  
    public static String zip(String src) {  
        return zip(src,null);  
    }  
      
    /** 
     * 使用给定密码压缩指定文件或文件夹到当前目录 
     * @param src 要压缩的文件 
     * @param passwd 压缩使用的密码 
     * @return 最终的压缩文件存放的绝对路径,如果为null则说明压缩失败. 
     */  
    public static String zip(String src, String passwd) {  
        return zip(src, null, passwd);  
    }  
      
    /** 
     * 使用给定密码压缩指定文件或文件夹到当前目录 
     * @param src 要压缩的文件 
     * @param dest 压缩文件存放路径 
     * @param passwd 压缩使用的密码 
     * @return 最终的压缩文件存放的绝对路径,如果为null则说明压缩失败. 
     */  
    public static String zip(String src, String dest, String passwd) {  
        return zip(src, dest, true, passwd);  
    }  
      
    /** 
     * 使用给定密码压缩指定文件或文件夹到指定位置. 
     * <p> 
     * dest可传最终压缩文件存放的绝对路径,也可以传存放目录,也可以传null或者"".<br /> 
     * 如果传null或者""则将压缩文件存放在当前目录,即跟源文件同目录,压缩文件名取源文件名,以.zip为后缀;<br /> 
     * 如果以路径分隔符(File.separator)结尾,则视为目录,压缩文件名取源文件名,以.zip为后缀,否则视为文件名. 
     * @param src 要压缩的文件或文件夹路径 
     * @param dest 压缩文件存放路径 
     * @param isCreateDir 是否在压缩文件里创建目录,仅在压缩文件为目录时有效.<br /> 
     * 如果为false,将直接压缩目录下文件到压缩文件. 
     * @param passwd 压缩使用的密码 
     * @return 最终的压缩文件存放的绝对路径,如果为null则说明压缩失败. 
     */  
    public static String zip(String src, String dest, boolean isCreateDir, String passwd) {  
        File srcFile = new File(src);  
        dest = buildDestinationZipFilePath(srcFile, dest);  
        ZipParameters parameters = new ZipParameters();  
        parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);           // 压缩方式  
        parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);    // 压缩级别  
        if (!StringUtils.isEmpty(passwd)) {  
            parameters.setEncryptFiles(true);  
            parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD); // 加密方式  
            parameters.setPassword(passwd.toCharArray());  
        }  
        try {  
            ZipFile zipFile = new ZipFile(dest);  
            if (srcFile.isDirectory()) {  
                // 如果不创建目录的话,将直接把给定目录下的文件压缩到压缩文件,即没有目录结构  
                if (!isCreateDir) {  
                    File [] subFiles = srcFile.listFiles();  
                    ArrayList<File> temp = new ArrayList<File>();  
                    Collections.addAll(temp, subFiles);  
                    zipFile.addFiles(temp, parameters);  
                    return dest;  
                }  
                zipFile.addFolder(srcFile, parameters);  
            } else {  
                zipFile.addFile(srcFile, parameters);  
            }  
            return dest;  
        } catch (ZipException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
      
    /** 
     * 构建压缩文件存放路径,如果不存在将会创建 
     * 传入的可能是文件名或者目录,也可能不传,此方法用以转换最终压缩文件的存放路径 
     * @param srcFile 源文件 
     * @param destParam 压缩目标路径 
     * @return 正确的压缩文件存放路径 
     */  
    private static String buildDestinationZipFilePath(File srcFile,String destParam) {  
        if (StringUtils.isEmpty(destParam)) {  
            if (srcFile.isDirectory()) {  
                destParam = srcFile.getParent() + File.separator + srcFile.getName() + ".zip";  
            } else {  
                String fileName = srcFile.getName().substring(0, srcFile.getName().lastIndexOf("."));  
                destParam = srcFile.getParent() + File.separator + fileName + ".zip";  
            }  
        } else {  
            createDestDirectoryIfNecessary(destParam);  // 在指定路径不存在的情况下将其创建出来  
            if (destParam.endsWith(File.separator)) {  
                String fileName = "";  
                if (srcFile.isDirectory()) {  
                    fileName = srcFile.getName();  
                } else {  
                    fileName = srcFile.getName().substring(0, srcFile.getName().lastIndexOf("."));  
                }  
                destParam += fileName + ".zip";  
            }  
        }  
        return destParam;  
    }  
      
    /** 
     * 在必要的情况下创建压缩文件存放目录,比如指定的存放路径并没有被创建 
     * @param destParam 指定的存放路径,有可能该路径并没有被创建 
     */  
    private static void createDestDirectoryIfNecessary(String destParam) {  
        File destDir = null;  
        if (destParam.endsWith(File.separator)) {  
            destDir = new File(destParam);  
        } else {  
            destDir = new File(destParam.substring(0, destParam.lastIndexOf(File.separator)));  
        }  
        if (!destDir.exists()) {  
            destDir.mkdirs();  
        }  
    }  
  
    public static void main(String[] args) {  
        zip("d:\\test\\cc", "d:\\test\\cc.zip", "11");  
//      try {  
//          File[] files = unzip("d:\\test\\汉字.zip", "aa");  
//          for (int i = 0; i < files.length; i++) {  
//              System.out.println(files[i]);  
//          }  
//      } catch (ZipException e) {  
//          e.printStackTrace();  
//      }  
    }  
}

If you need to delete the unzipped files, you can use the following code.

 void removeDirFromZipArchive(String file, String removeDir) throws ZipException {  
    // 创建ZipFile并设置编码  
    ZipFile zipFile = new ZipFile(file);  
    zipFile.setFileNameCharset("GBK");  
      
    // 给要删除的目录加上路径分隔符  
    if (!removeDir.endsWith(File.separator)) removeDir += File.separator;  
      
    // 如果目录不存在, 直接返回  
    FileHeader dirHeader = zipFile.getFileHeader(removeDir);  
    if (null == dirHeader) return;  
  
    // 遍历压缩文件中所有的FileHeader, 将指定删除目录下的子文件名保存起来  
    List headersList = zipFile.getFileHeaders();  
    List<String> removeHeaderNames = new ArrayList<String>();  
    for(int i=0, len = headersList.size(); i<len; i++) {  
        FileHeader subHeader = (FileHeader) headersList.get(i);  
        if (subHeader.getFileName().startsWith(dirHeader.getFileName())  
                && !subHeader.getFileName().equals(dirHeader.getFileName())) {  
            removeHeaderNames.add(subHeader.getFileName());  
        }  
    }  
    // 遍历删除指定目录下的所有子文件, 最后删除指定目录(此时已为空目录)  
    for(String headerNameString : removeHeaderNames) {  
        zipFile.removeFile(headerNameString);  
    }  
    zipFile.removeFile(dirHeader);  
}

xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》