AndroidZip笔记

Android zip详解

一:解析Zip

有两种方法一种是Android Open Source Project 开源的util 可以不用解压解析zip

package sw.superb.collage2.utils;

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


public class ZipResourceFile {

    //
    // Read-only access to Zip archives, with minimal heap allocation.
    //
    static final String LOG_TAG = "zipro";
    static final boolean LOGV = false;
    private static final String TAG =  "ZipResourceFile";

    // 4-byte number
    static private int swapEndian(int i)
    {
        return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >>> 8)
                + ((i >>> 24) & 0xff);
    }

    // 2-byte number
    static private int swapEndian(short i)
    {
        return ((i & 0x00FF) << 8 | (i & 0xFF00) >>> 8);
    }

    /*
     * Zip file constants.
     */
    static final int kEOCDSignature = 0x06054b50;
    static final int kEOCDLen = 22;
    static final int kEOCDNumEntries = 8; // offset to #of entries in file
    static final int kEOCDSize = 12; // size of the central directory
    static final int kEOCDFileOffset = 16; // offset to central directory

    static final int kMaxCommentLen = 65535; // longest possible in ushort
    static final int kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen);

    static final int kLFHSignature = 0x04034b50;
    static final int kLFHLen = 30; // excluding variable-len fields
    static final int kLFHNameLen = 26; // offset to filename length
    static final int kLFHExtraLen = 28; // offset to extra length

    static final int kCDESignature = 0x02014b50;
    static final int kCDELen = 46; // excluding variable-len fields
    static final int kCDEMethod = 10; // offset to compression method
    static final int kCDEModWhen = 12; // offset to modification timestamp
    static final int kCDECRC = 16; // offset to entry CRC
    static final int kCDECompLen = 20; // offset to compressed length
    static final int kCDEUncompLen = 24; // offset to uncompressed length
    static final int kCDENameLen = 28; // offset to filename length
    static final int kCDEExtraLen = 30; // offset to extra length
    static final int kCDECommentLen = 32; // offset to comment length
    static final int kCDELocalOffset = 42; // offset to local hdr

    static final int kCompressStored = 0; // no compression
    static final int kCompressDeflated = 8; // standard deflate

    /*
     * The values we return for ZipEntryRO use 0 as an invalid value, so we want
     * to adjust the hash table index by a fixed amount. Using a large value
     * helps insure that people don't mix & match arguments, e.g. to
     * findEntryByIndex().
     */
    static final int kZipEntryAdj = 10000;

    static public final class ZipEntryRO {
        public ZipEntryRO(final String zipFileName, final File file, final String fileName) {
            mFileName = fileName;
            mZipFileName = zipFileName;
            mFile = file;
        }

        public final File mFile;
        public final String mFileName;
        public final String mZipFileName;
        public long mLocalHdrOffset; // offset of local file header

        /* useful stuff from the directory entry */
        public int mMethod;
        public long mWhenModified;
        public long mCRC32;
        public long mCompressedLength;
        public long mUncompressedLength;

        public long mOffset = -1;

        public void setOffsetFromFile(RandomAccessFile f, ByteBuffer buf) throws IOException {
            long localHdrOffset = mLocalHdrOffset;
            try {
                f.seek(localHdrOffset);
                f.readFully(buf.array());
                if (buf.getInt(0) != kLFHSignature) {
                    Log.w(LOG_TAG, "didn't find signature at start of lfh");
                    throw new IOException();
                }
                int nameLen = buf.getShort(kLFHNameLen) & 0xFFFF;
                int extraLen = buf.getShort(kLFHExtraLen) & 0xFFFF;
                mOffset = localHdrOffset + kLFHLen + nameLen + extraLen;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }

        /**
         * Calculates the offset of the start of the Zip file entry within the
         * Zip file.
         * 
         * @return the offset, in bytes from the start of the file of the entry
         */
        public long getOffset() {
            return mOffset;
        }

        /**
         * isUncompressed
         * 
         * @return true if the file is stored in uncompressed form
         */
        public boolean isUncompressed() {
            return mMethod == kCompressStored;
        }

        public AssetFileDescriptor getAssetFileDescriptor() {
            if (mMethod == kCompressStored) {
                ParcelFileDescriptor pfd;
                try {
                    pfd = ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY);
                    return new AssetFileDescriptor(pfd, getOffset(), mUncompressedLength);
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return null;
        }

        public String getZipFileName() {
            return mZipFileName;
        }

        public File getZipFile() {
            return mFile;
        }

    }

    public HashMap<String, ZipEntryRO> mHashMap = new HashMap<String, ZipEntryRO>();

    /* for reading compressed files */
    public HashMap<File, ZipFile> mZipFiles = new HashMap<File, ZipFile>();

    public ZipResourceFile(String zipFileName) throws IOException {
        addPatchFile(zipFileName);
    }

    ZipEntryRO[] getEntriesAt(String path) {
        Vector<ZipEntryRO> zev = new Vector<ZipEntryRO>();
        Collection<ZipEntryRO> values = mHashMap.values();
        if (null == path)
            path = "";
        int length = path.length();
        for (ZipEntryRO ze : values) {
            if (ze.mFileName.startsWith(path)) {
                if (-1 == ze.mFileName.indexOf('/', length)) {
                    zev.add(ze);
                }
            }
        }
        ZipEntryRO[] entries = new ZipEntryRO[zev.size()];
        return zev.toArray(entries);
    }

    public ZipEntryRO[] getAllEntries() {
        Collection<ZipEntryRO> values = mHashMap.values();
        return values.toArray(new ZipEntryRO[values.size()]);
    }

    /**
     * getAssetFileDescriptor allows for ZipResourceFile to directly feed
     * Android API's that want an fd, offset, and length such as the
     * MediaPlayer. It also allows for the class to be used in a content
     * provider that can feed video players. The file must be stored
     * (non-compressed) in the Zip file for this to work.
     * 
     * @param assetPath
     * @return the asset file descriptor for the file, or null if the file isn't
     *         present or is stored compressed
     */
    public AssetFileDescriptor getAssetFileDescriptor(String assetPath) {
        ZipEntryRO entry = mHashMap.get(assetPath);
        if (null != entry) {
            return entry.getAssetFileDescriptor();
        }
        return null;
    }

    /**
     * getInputStream returns an AssetFileDescriptor.AutoCloseInputStream
     * associated with the asset that is contained in the Zip file, or a
     * standard ZipInputStream if necessary to uncompress the file
     * 
     * @param assetPath
     * @return an input stream for the named asset path, or null if not found
     * @throws IOException
     */
    public InputStream getInputStream(String assetPath) throws IOException {
        ZipEntryRO entry = mHashMap.get(assetPath);
        if (null != entry) {
            if (entry.isUncompressed()) {
                return entry.getAssetFileDescriptor().createInputStream();
            } else {
                ZipFile zf = mZipFiles.get(entry.getZipFile());
                /** read compressed files **/
                if (null == zf) {
                    zf = new ZipFile(entry.getZipFile(), ZipFile.OPEN_READ);
                    mZipFiles.put(entry.getZipFile(), zf);
                }
                ZipEntry zi = zf.getEntry(assetPath);
                if (null != zi)
                    return zf.getInputStream(zi);
            }
        }
        return null;
    }

    ByteBuffer mLEByteBuffer = ByteBuffer.allocate(4);

    static private int read4LE(RandomAccessFile f) throws EOFException, IOException {
        return swapEndian(f.readInt());
    }
    
    /*
     * Opens the specified file read-only. We memory-map the entire thing and
     * close the file before returning.
     */
    void addPatchFile(String zipFileName) throws IOException
    {
        File file = new File(zipFileName);
        RandomAccessFile f = new RandomAccessFile(file, "r");
        long fileLength = f.length();
        if (fileLength < kEOCDLen) {
            throw new IOException();
        }

        long readAmount = kMaxEOCDSearch;
        if (readAmount > fileLength)
            readAmount = fileLength;

        /*
         * Make sure this is a Zip archive.
         */
        f.seek(0);

        int header = read4LE(f);
        if (header == kEOCDSignature) {
            Log.i(LOG_TAG, "Found Zip archive, but it looks empty");
            throw new IOException();
        } else if (header != kLFHSignature) {
            Log.v(LOG_TAG, "Not a Zip archive");
            throw new IOException();
        }

        /*
         * Perform the traditional EOCD snipe hunt. We're searching for the End
         * of Central Directory magic number, which appears at the start of the
         * EOCD block. It's followed by 18 bytes of EOCD stuff and up to 64KB of
         * archive comment. We need to read the last part of the file into a
         * buffer, dig through it to find the magic number, parse some values
         * out, and use those to determine the extent of the CD. We start by
         * pulling in the last part of the file.
         */
        long searchStart = fileLength - readAmount;

        f.seek(searchStart);
        ByteBuffer bbuf = ByteBuffer.allocate((int) readAmount);
        byte[] buffer = bbuf.array();
        f.readFully(buffer);
        bbuf.order(ByteOrder.LITTLE_ENDIAN);

        /*
         * Scan backward for the EOCD magic. In an archive without a trailing
         * comment, we'll find it on the first try. (We may want to consider
         * doing an initial minimal read; if we don't find it, retry with a
         * second read as above.)
         */

        // EOCD == 0x50, 0x4b, 0x05, 0x06
        int eocdIdx;
        for (eocdIdx = buffer.length - kEOCDLen; eocdIdx >= 0; eocdIdx--) {
            if (buffer[eocdIdx] == 0x50 && bbuf.getInt(eocdIdx) == kEOCDSignature)
            {
                if (LOGV) {
                    Log.v(LOG_TAG, "+++ Found EOCD at index: " + eocdIdx);
                }
                break;
            }
        }

        if (eocdIdx < 0) {
            Log.d(LOG_TAG, "Zip: EOCD not found, " + zipFileName + " is not zip");
        }

        /*
         * Grab the CD offset and size, and the number of entries in the
         * archive. After that, we can release our EOCD hunt buffer.
         */

        int numEntries = bbuf.getShort(eocdIdx + kEOCDNumEntries);
        long dirSize = bbuf.getInt(eocdIdx + kEOCDSize) & 0xffffffffL;
        long dirOffset = bbuf.getInt(eocdIdx + kEOCDFileOffset) & 0xffffffffL;

        // Verify that they look reasonable.
        if (dirOffset + dirSize > fileLength) {
            Log.w(LOG_TAG, "bad offsets (dir " + dirOffset + ", size " + dirSize + ", eocd "
                    + eocdIdx + ")");
            throw new IOException();
        }
        if (numEntries == 0) {
            Log.w(LOG_TAG, "empty archive?");
            throw new IOException();
        }

        if (LOGV) {
            Log.v(LOG_TAG, "+++ numEntries=" + numEntries + " dirSize=" + dirSize + " dirOffset="
                    + dirOffset);
        }

        MappedByteBuffer directoryMap = f.getChannel()
                .map(FileChannel.MapMode.READ_ONLY, dirOffset, dirSize);
        directoryMap.order(ByteOrder.LITTLE_ENDIAN);

        byte[] tempBuf = new byte[0xffff];

        /*
         * Walk through the central directory, adding entries to the hash table.
         */

        int currentOffset = 0;

        /*
         * Allocate the local directory information
         */
        ByteBuffer buf = ByteBuffer.allocate(kLFHLen);
        buf.order(ByteOrder.LITTLE_ENDIAN);

        for (int i = 0; i < numEntries; i++) {
            if (directoryMap.getInt(currentOffset) != kCDESignature) {
                Log.w(LOG_TAG, "Missed a central dir sig (at " + currentOffset + ")");
                throw new IOException();
            }

            /* useful stuff from the directory entry */
            int fileNameLen = directoryMap.getShort(currentOffset + kCDENameLen) & 0xffff;
            int extraLen = directoryMap.getShort(currentOffset + kCDEExtraLen) & 0xffff;
            int commentLen = directoryMap.getShort(currentOffset + kCDECommentLen) & 0xffff;

            /* get the CDE filename */

            directoryMap.position(currentOffset + kCDELen);
            directoryMap.get(tempBuf, 0, fileNameLen);
            directoryMap.position(0);

            /* UTF-8 on Android */
            String str = new String(tempBuf, 0, fileNameLen);
            if (LOGV) {
                Log.v(LOG_TAG, "Filename: " + str);
            }

            ZipEntryRO ze = new ZipEntryRO(zipFileName, file, str);
            ze.mMethod = directoryMap.getShort(currentOffset + kCDEMethod) & 0xffff;
            ze.mWhenModified = directoryMap.getInt(currentOffset + kCDEModWhen) & 0xffffffffL;
            ze.mCRC32 = directoryMap.getLong(currentOffset + kCDECRC) & 0xffffffffL;
            ze.mCompressedLength = directoryMap.getLong(currentOffset + kCDECompLen) & 0xffffffffL;
            ze.mUncompressedLength = directoryMap.getLong(currentOffset + kCDEUncompLen) & 0xffffffffL;
            ze.mLocalHdrOffset = directoryMap.getInt(currentOffset + kCDELocalOffset) & 0xffffffffL;

            // set the offsets
            buf.clear();
            ze.setOffsetFromFile(f, buf);

            // put file into hash
            mHashMap.put(str, ze);

            // go to next directory entry
            currentOffset += kCDELen + fileNameLen + extraLen + commentLen;
        }
        if (LOGV) {
            Log.v(LOG_TAG, "+++ zip good scan " + numEntries + " entries");
        }
    }
}
package sw.superb.collage2.utils;
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.content.Context;
import android.os.Environment;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

public class APKExpansionSupport {
   // The shared path to all app expansion files
   private final static String EXP_PATH = "/Android/obb/";

   public static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
      String packageName = ctx.getPackageName();
      Vector<String> ret = new Vector<String>();
      if (Environment.getExternalStorageState().equals(
            Environment.MEDIA_MOUNTED)) {
         // Build the full path to the app's expansion files
         File root = Environment.getExternalStorageDirectory();
         File expPath = new File(root.toString() + EXP_PATH + packageName);

         // Check that expansion file path exists
         if (expPath.exists()) {
            if ( mainVersion > 0 ) {
               String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb";
               File main = new File(strMainPath);
               if ( main.isFile() ) {
                  ret.add(strMainPath);
               }
            }
            if ( patchVersion > 0 ) {
               String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb";
               File main = new File(strPatchPath);
               if ( main.isFile() ) {
                  ret.add(strPatchPath);
               }
            }
         }
      }
      String[] retArray = new String[ret.size()];
      ret.toArray(retArray);
      return retArray;
   }





   static public ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {
        ZipResourceFile apkExpansionFile = null;
        for (String expansionFilePath : expansionFiles) {
            if ( null == apkExpansionFile ) {
                apkExpansionFile = new ZipResourceFile(expansionFilePath);
            } else {
                apkExpansionFile.addPatchFile(expansionFilePath);
            }
        }
        return apkExpansionFile;
   }
   
   static public ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{
      String[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);
      return getResourceZipFile(expansionFiles);
   }



   public static String readDataFile(String file) throws Exception {
      //截取路径的文件名 res
      String fileName = file.substring(file.length() - 9, file.length() - 4);
      ZipFile zf = new ZipFile(file);
      InputStream in = new BufferedInputStream(new FileInputStream(file));
      ZipInputStream zin = new ZipInputStream(in);
      ZipEntry ze;
      while ((ze = zin.getNextEntry()) != null) {
         if (ze.isDirectory()) {
            //Do nothing
         } else {
            if (ze.getName().equals(fileName + "describe")) {
               BufferedReader br = new BufferedReader(
                     new InputStreamReader(zf.getInputStream(ze)));
               String line;
               while ((line = br.readLine()) != null) {
                  return line;
               }
               br.close();
            }
         }
      }
      zin.closeEntry();
      return "";
   }
}

另外一种可以通过zipFile和ZipOutputStream解析zip,ZipFile和ZipOutputStream解压zip的区别在于正常情况下都是用zipFile解压zip,但是在从网络中下载zip边下载边解压的时候是用ZipOutputStream是最理想的选择。这个就是解压缩zip文件的工具类

/**
 * 解压zip到指定的路径
 * @param zipFileString  ZIP的名称
 * @param outPathString   要解压缩路径
 * @throws Exception
 */
public static void UnZipFolder(String zipFileString, String outPathString) throws Exception {
    ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString));
    ZipEntry zipEntry;
    String  szName = "";
    while ((zipEntry = inZip.getNextEntry()) != null) {
        szName = zipEntry.getName();
        if (zipEntry.isDirectory()) {
            //获取部件的文件夹名
            szName = szName.substring(0, szName.length() - 1);
            File folder = new File(outPathString + File.separator + szName);
            folder.mkdirs();
        } else {
            Log.e(TAG,outPathString + File.separator + szName);
            File file = new File(outPathString + File.separator + szName);
            if (!file.exists()){
                Log.e(TAG, "Create the file:" + outPathString + File.separator + szName);
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            // 获取文件的输出流
            FileOutputStream out = new FileOutputStream(file);
            int len;
            byte[] buffer = new byte[1024];
            // 读取(字节)字节到缓冲区
            while ((len = inZip.read(buffer)) != -1) {
                // 从缓冲区(0)位置写入(字节)字节
                out.write(buffer, 0, len);
                out.flush();
            }
            out.close();
        }
    }
    inZip.close();
}

二:压缩zip

/**
 * 压缩文件和文件夹
 *
 * @param srcFileString 要压缩的文件或文件夹
 * @param zipFileString 压缩完成的Zip路径
 * @throws Exception
 * @return
 */
public static void ZipFolder(String srcFileString, String zipFileString) throws Exception {
    //创建ZIP
    ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(zipFileString));
    //创建文件
    File file = new File(srcFileString);
    //压缩
    ZipFiles(file.getParent()+ File.separator, file.getName(), outZip);
    Log.e(TAG, "ZipFolderq: "+zipFileString );
    //完成和关闭
    outZip.finish();
    outZip.close();
}
/**
 * 压缩文件
 *
 * @param folderString
 * @param fileString
 * @param zipOutputSteam
 * @throws Exception
 */
private static void ZipFiles(String folderString, String fileString, ZipOutputStream zipOutputSteam) throws Exception {

    if (zipOutputSteam == null)
        return;
    File file = new File(folderString + fileString);
    if (file.isFile()) {
        ZipEntry zipEntry = new ZipEntry(fileString);
        FileInputStream inputStream = new FileInputStream(file);
        zipOutputSteam.putNextEntry(zipEntry);
        int len;
        byte[] buffer = new byte[4096];
        while ((len = inputStream.read(buffer)) != -1) {
            zipOutputSteam.write(buffer, 0, len);
        }
        zipOutputSteam.closeEntry();
    } else {
        //文件夹
        String fileList[] = file.list();
        //没有子文件和压缩
        if (fileList.length <= 0) {
            ZipEntry zipEntry = new ZipEntry(fileString + File.separator);
            zipOutputSteam.putNextEntry(zipEntry);
            zipOutputSteam.closeEntry();
        }
        //子文件和递归
        for (int i = 0; i < fileList.length; i++) {
            ZipFiles(folderString+fileString+"/", fileList[i], zipOutputSteam);
        }
    }
}

三:关于Android文件保存

在后面Android的版本中,一般给Android app一个单独的文件夹创建由开发者使用代码创建的File,具体目录结构为/android/data/你的应用的AppID/ 然后代码一般为

File file;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    file = new File(context.getExternalFilesDir(TypeName) + File.separator ,
            "shapeframe"+Number);
} else {
    file = new File(Environment.getExternalStorageDirectory() + "/Android",
            "shapeframe"+Number);
}

在androidQ即以上使用 getExternalFilesDir得到这个目录,以下使用getExternalStorageDirectory,其中TypeName中带有"File.separator"可创建二级目录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值