Android下zip压缩文件加密解密的完美解决方案

前言

参考博客:java

 ZIP4J,做为解决了个人问题的终极解决方案,原本一开始在搜索引擎上就看到了它的踪影,但因天朝的网络环境问题,zip4j的官网一直没法访问,最终使我多走了好多冤枉路,期间试过JDK的zip包,试过Apache的zip解决方案,也试过如winzipaes等其它的开源框架,最终没有知足本身的需求,最后,我不得已挂了一下代理将zip4j下载了下来,试用了一下,果真威力无比,所到之处所向披靡...算法

闲话少说,若是须要能够到zip4j的官网下载该开源项目:数组

http://www.lingala.net/zip4j/网络

不过须要提醒的是可能没法直接访问,若是没法正常访问,请自行准备代理访问,若是各位嫌麻烦,能够到这里下载:框架

http://pan.baidu.com/s/1kUBFSwv   ximuide

官网上下载的资源好像是不带API帮助文档的,我利用其源码生成了一份,也一并打在个人资源文件中,但愿能帮到你们。工具

 

ZIP4J的官方说明

(本身翻译了一下,英文很差,呵呵...)post

Key features(主要特性):学习

  • Create, Add, Extract, Update, Remove files from a Zip file
    针对ZIP压缩文件建立、添加、抽出、更新和移除文件
  • Read/Write password protected Zip files
    (读写有密码保护的Zip文件)
  • Supports AES 128/256 Encryption
    (支持AES 128/256算法加密)
  • Supports Standard Zip Encryption
    (支持标准Zip算法加密)
  • Supports Zip64 format
    (支持zip64格式)
  • Supports Store (No Compression) and Deflate compression method
    (支持Store(非压缩)和Deflate压缩方法---不太明白)
  • Create or extract files from Split Zip files (Ex: z01, z02,...zip)
    (针对分块zip文件建立和抽出文件)
  • Supports Unicode file names
    (支持Unicode编码文件名)
  • Progress Monitor
    (进度监控)

从上面的主要特性能够看出,zip4j的功能是很是强大的,彻底能够利用其写个相似于好压的zip文件管理软件,但咱们用地最多的可能仍是利用其做一些简单的解压和压缩操做,其它的功能极少触碰,我也同样,呵呵...测试

 

使用注意点

zip4j默认采用UTF-8编码,因此它支持中文,同时也支持密码,并且支持多种压缩算法,能够说功能强大,但使用起来却很是简单,固然,若是需求比较复杂,那就得好好去研究了。若是你仅仅是简单地解压一个zip压缩文件,那么只须要简单地几步便可:

public static void unzip(File zipFile, String dest, String passwd) throws ZipException {
   ZipFile zFile = new ZipFile(zipFile);  // 首先建立ZipFile指向磁盘上的.zip文件
   zFile.setFileNameCharset("GBK");       // 设置文件名编码,在GBK系统中须要设置
   if (!zFile.isValidZipFile()) {   // 验证.zip文件是否合法,包括文件是否存在、是否为zip文件、是否被损坏等
      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);      // 将文件抽出到解压目录(解压)
}

固然将指定文件压缩成zip文件也是很是简单的事,此处再也不贴代码,若有须要请参看下面的完整示例。

 提示:若是将要解压的压缩文件中的文件名含有中文,解压时须要注意一点,就是设置文件名字符集方法

zFile.setFileNameCharset("GBK");

这个方法的调用必定要靠前,要靠多前呢?其实最好在建立ZipFile以后就设置上,至少要在

zFile.isValidZipFile();

这个方法调用以前调用,我在应用时由于这个问题耽误很久,最后查看源码才弄明白,好像是ZipFile在验证方法中就将编码设置好,之后就再也不对文件名编码做改变了。

完整示例

package com.ninemax.cul.util;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

/**
 * 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 (!passwd.isEmpty()) {
         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 (destParam.isEmpty()) {
         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();
//      }
   }
}

须要学习的东西太多,没太多时间(或许只是借口)去研究它,上面的例子仅是简单地解压和压缩操做;但在使用中能够发现Zip4J功能比较完备,若是须要更多地支持,那就真要好好去研究一下它,也许它真的不会使您失望。。。

 

补充

删除压缩文件中的目录

看到有朋友在问如何删除压缩文件中的目录,在这里补充一下。

利用zip4j删除压缩文件中的目录,查阅API后很容易想到这样的方式:

ZipFile zipFile = new ZipFile("d:\\FeiQ-V2.5.zip");
zipFile.setFileNameCharset("GBK");
zipFile.removeFile("sounds/");      // soundszip文件中的一个目录 

但这种直接删除压缩文件中非空目录的方式是不会成功的,你会看到zip文件丝毫没有变化,虽然目录对应的FileHeader已被删除(表现就是若是这时再将目录下的全部文件删除,则该目录随之消失) ;所以咱们须要将该目录下全部的文件都删除掉,最后再将目录删除,根据这个思路,咱们很容易造成以下的代码:

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 allHeaders = zipFile.getFileHeaders();
   for(int i=0, len = allHeaders.size(); i<len; i++) {
      FileHeader subHeader = (FileHeader) allHeaders.get(i);
      if (subHeader.getFileName().startsWith(dirHeader.getFileName())
            && !subHeader.getFileName().equals(dirHeader.getFileName())) {
         zipFile.removeFile(subHeader);
      }
   }
   // 最后删除指定目录  
   zipFile.removeFile(dirHeader);
}

这样仍然解决不了问题,若是你这样作了,那么你将会获得一个java.lang.IndexOutOfBoundsException异常,那么看似正常的代码为何会报索引越界异常呢?其实咱们经过zipFile.getFileHeaders()方法获得的List会随遍历中的删除操做而发生变化,也就是说咱们删除了某个FileHeader,将会反映到该List中。每成功删除一个FileHeader,List长度就减1,而i一直在0至List的初始长度之间递增,反复几回后就可能出现越界异常。

为了不这种状况发生,咱们能够多作一些操做,好比能够在遍历中暂不进行删除操做,而只是将要删除的文件记录下来,遍历结束后再统一删除,最后将目录删除,经测试,这个思路能够解决问题。

简单示例代码:

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);
}

 

 

我本身的使用实例:

public class MainActivity extends AppCompatActivity {

   private TextView textView;
   private static final String TAG = "MainActivity";

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      textView = (TextView) findViewById(R.id.textView);
      try {
         Unzip(new File("/mnt/usb_storage/USB_DISK1/abc.zip"), "/mnt/usb_storage/USB_DISK1/", "fe9d814e380c829b20ba0f561f70fa1d", "gbk", false);
      } catch (ZipException e) {
         e.printStackTrace();
         Log.e(TAG, "解压错误:" + e.toString());
      }
   }

   Handler mHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
         switch (msg.what) {
            case CompressStatus.START: {
               textView.setText("Start...");
               break;
            }
            case CompressStatus.HANDLING: {
               Bundle bundle = msg.getData();
               int percent = bundle.getInt(CompressKeys.PERCENT);
               setTitle(percent + "%");
               textView.setText(percent + "%");
               break;
            }
            case CompressStatus.ERROR: {
               Bundle bundle = msg.getData();
               String error = bundle.getString(CompressKeys.ERROR);
               setTitle(error);
               break;
            }
            case CompressStatus.COMPLETED: {
               textView.setText("Completed");
               break;
            }

         }
      }
   };


   public void Unzip(final File zipFile, String dest, String passwd, String charset, final boolean isDeleteZipFile) throws ZipException {
      ZipFile zFile = new ZipFile(zipFile);
      if (TextUtils.isEmpty(charset)) {
         charset = "gbk";
      }
      zFile.setFileNameCharset(charset);
      if (!zFile.isValidZipFile()) {
         throw new ZipException("Compressed files are not illegal, may be damaged.");
      }
      File destDir = new File(dest);
      if (destDir.isDirectory() && !destDir.exists()) {
         destDir.mkdir();
      }
      if (zFile.isEncrypted()) {
         zFile.setPassword(passwd.toCharArray());
      }

      final ProgressMonitor progressMonitor = zFile.getProgressMonitor();

      Thread progressThread = new Thread(new Runnable() {
         @Override
         public void run() {
            Bundle bundle = null;
            Message msg = null;
            try {
               int percentDone = 0;
               if (mHandler == null) {
                  return;
               }
               mHandler.sendEmptyMessage(CompressStatus.START);
               while (true) {
                  Thread.sleep(1000);
                  percentDone = progressMonitor.getPercentDone();
                  bundle = new Bundle();
                  bundle.putInt(CompressKeys.PERCENT, percentDone);
                  msg = new Message();
                  msg.what = CompressStatus.HANDLING;
                  msg.setData(bundle);
                  mHandler.sendMessage(msg);
                  if (percentDone >= 100) {
                     break;
                  }
               }
               mHandler.sendEmptyMessage(CompressStatus.COMPLETED);
            } catch (InterruptedException e) {
               bundle = new Bundle();
               bundle.putString(CompressKeys.ERROR, e.getMessage());
               msg = new Message();
               msg.what = CompressStatus.ERROR;
               msg.setData(bundle);
               mHandler.sendMessage(msg);
               e.printStackTrace();
            } finally {
               if (isDeleteZipFile) {
                  zipFile.deleteOnExit();
               }
            }
         }
      });

      progressThread.start();
      zFile.setRunInThread(true);
      zFile.extractAll(dest);
   }


}
public class CompressStatus {

   public final static int START = 0;
   public final static int HANDLING = 1;
   public final static int COMPLETED = 2;
   public final static int ERROR = 3;

}
public class CompressKeys {
   public final static String PERCENT="PERCENT";
   public final static String ERROR="ERROR";
}
相关文章
相关标签/搜索