首页 > 工作 > gzip在Android4.4下面崩溃的问题

gzip在Android4.4下面崩溃的问题

最近在搞的一个项目,在Android 4.4(API 19)下,gzip会崩溃。
相关的调用代码如下:

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream gos = null;
        try {
            gos = new GZIPOutputStream(baos);
            gos.write(srcData);
            gos.finish();
            gos.flush();//这里会崩溃
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            gos.close();
        }

崩溃时的日志如下:

11-25 15:33:13.757: E/AndroidRuntime(10723): FATAL EXCEPTION
11-25 15:33:13.757: E/AndroidRuntime(10723): Process: com.xxxxx, PID: 10723
11-25 15:33:13.757: E/AndroidRuntime(10723): java.util.zip.DataFormatException: stream error
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at java.util.zip.Deflater.deflateImpl(Native Method)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at java.util.zip.Deflater.deflateImpl(Deflater.java:241)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at java.util.zip.Deflater.deflate(Deflater.java:232)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at java.util.zip.DeflaterOutputStream.flush(DeflaterOutputStream.java:193)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at com.xxxxx.xx.Xxx.compress(Xxx.java:727)
...此处略去N行无关信息
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at android.os.Handler.dispatchMessage(Handler.java:102)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at android.os.Looper.loop(Looper.java:137)
11-25 15:33:13.757: E/AndroidRuntime(10723): 	at android.os.HandlerThread.run(HandlerThread.java:61)


看了一下的源码注释,给出的示例代码中未发现对finish和flush的调用:
java.util.zip.GZIPOutputStream

The GZIPOutputStream class is used to write data to a stream in the GZIP storage format.

Example
Using GZIPOutputStream is a little easier than ZipOutputStream because GZIP is only for compression, and is not a container for multiple files. This code creates a GZIP stream, similar to the gzip(1) utility.

 OutputStream os = ...
 byte[] bytes = ...
 GZIPOutputStream zos = new GZIPOutputStream(new BufferedOutputStream(os));
 try {
     zos.write(bytes);
 } finally {
     zos.close();
 }

下面分析一下finish和出现崩溃的flush方法调用

去看close方法的实现,发现里面对finish做了校验,可以不显式调用,当然了,显式调用也没有问题。

    @Override
    public void close() throws IOException {
        // everything closed here should also be closed in ZipOutputStream.close()
        if (!def.finished()) {
            finish();
        }
        def.end();
        out.close();
    }

真正出现崩溃问题的flush方法的实现如下

public void flush() throws IOException {
        if (syncFlush) {
            int byteCount;
            while ((byteCount = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH)) != 0) {
                out.write(buf, 0, byteCount);
            }
        }
        out.flush();
    }

其中out.flush();因为ByteArrayOutputStream并未对flush进行重载,使用的还是OutputStream中的空实现。

比较了一下17和19的GZIPOutputStream实现,能明显发现syncFlush的默认值改了。
API 17的syncFlush构造源码如下:

    public GZIPOutputStream(OutputStream os) throws IOException {//入口
        this(os, BUF_SIZE);
    }
    public GZIPOutputStream(OutputStream os, int size) throws IOException {
        super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size);
        ...
    }
    //super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size);对应的实现如下
  public DeflaterOutputStream(OutputStream os, Deflater def, int bsize) {
        this(os, def, bsize, false);//注意这个地方是false,默认是不打开syncFlush的
    }
    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize, boolean syncFlush) {
        ....
        this.syncFlush = syncFlush;//这个this.syncFlush正是在flush时检查的条件,当值为false的时候,里面嘛也没干。
        ...
    }

现在看一下API 19中的实现,就会发现问题所在

    public GZIPOutputStream(OutputStream os) throws IOException {
        this(os, BUF_SIZE, true);//入口时,上来就是默认值true
    }
    public GZIPOutputStream(OutputStream os, int bufferSize, boolean syncFlush) throws IOException {
        super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize, syncFlush);//再往下的代码之前分析过了
        ...
    }

至此,可以发现,在Android 4.4(API 19)之前版本中,调用flush实际上嘛都没干。先是if为false,跟着的flush内部还是空实现。Android 4.4的if条件为真,出现问题。

再看这个出现问题的flush中的def.deflate

    /**
     * Flush buffers so recipients can immediately decode the data sent thus
     * far. This mode may degrade compression.
     * @since 1.7
     */
    public static final int SYNC_FLUSH = 2;

    /**
     * Deflates data (previously passed to {@link #setInput setInput}) into a specific
     * region within the supplied buffer, optionally flushing the input buffer.
     *
     * @param flush one of {@link #NO_FLUSH}, {@link #SYNC_FLUSH} or {@link #FULL_FLUSH}.
     * @return the number of compressed bytes written to {@code buf}. If this
     *      equals {@code byteCount}, the number of bytes of input to be flushed
     *      may have exceeded the output buffer's capacity. In this case,
     *      finishing a flush will require the output buffer to be drained
     *      and additional calls to {@link #deflate} to be made.
     * @throws IllegalArgumentException if {@code flush} is invalid.
     * @since 1.7
     */
    public synchronized int deflate(byte[] buf, int offset, int byteCount, int flush) {
        if (flush != NO_FLUSH && flush != SYNC_FLUSH && flush != FULL_FLUSH) {
            throw new IllegalArgumentException("Bad flush value: " + flush);
        }
        return deflateImpl(buf, offset, byteCount, flush);
    }

    private synchronized int deflateImpl(byte[] buf, int offset, int byteCount, int flush) {
        checkOpen();
        Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
        if (inputBuffer == null) {
            setInput(EmptyArray.BYTE);
        }
        return deflateImpl(buf, offset, byteCount, streamHandle, flush);
    }

    private native int deflateImpl(byte[] buf, int offset, int byteCount, long handle, int flushParm);

从字面的理解,就是可以随意的立即编码并输出,能够及时返回结果,因为压缩的源数量有可能变少,所以压缩效果会跟着变会差。
没去跟natvie的实现,不确定内部为什么会出java.util.zip.DataFormatException: stream error的异常。
怀疑(仅仅是猜测)是finish后无法进行此项操作。
其实可以通过简单测试得到结果:把finish注释掉,保留flush,如果不崩溃,就能印证是flush不能在finish后。

  1. 无名
    2015年2月1日16:13 | #1

    @边城冰草
    十分感谢边城兄相告,BUG已解决!另外,不知此问题是否会在5.0系统上复现?我手头上暂无5.0的机器

  2. 边城冰草
    2015年1月30日10:38 | #2

    @无名
    当时分析的结果:flush的实现为空时正常,新版本中不为空,就有问题。你不调用flush应该就可以了。

    希望对你有所帮助~

  3. 无名
    2015年1月24日15:10 | #3

    你好,我做的一个项目中也遇到了跟您一样的问题,不知您是怎么解决的?可否提示一二?感激不尽

  1. 本文目前尚无任何 trackbacks 和 pingbacks.