Programming with the zlib compression library in Android NDK

zlib is a widely-used, lossless data compression library, which is available for Android 1.5 system images or higher. This recipe discusses the basic usage of the zlib functions.

Getting ready...

Readers are expected to know how to create an Android NDK project. We can refer to the Writing a Hello NDK program recipe of Chapter 1, Hello NDK for detailed instructions.

How to do it...

The following steps describe how to create a simple Android application which demonstrates the usage of zlib library:

  1. Create an Android application named ZlibDemo. Set the package name as cookbook.chapter7.zlibdemo. Refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface for more detailed instructions.
  2. Right-click on the project ZlibDemo, select Android Tools | Add Native Support.
  3. Add a Java file named MainActivity.java in the cookbook.chapter7.zlibdemo package. The MainActivity.java file loads the ZlibDemo native library, and calls the native methods.
  4. Add mylog.h, ZlibDemo.cpp, and GzFileDemo.cpp files under the jni folder. The mylog.h header file contains the Android native logcat utility functions, while ZlibDemo.cpp and GzFileDemo.cpp files contain code for compression and decompression. A part of the code in ZlibDemo.cpp and GzFileDemo.cpp is shown in the following code.

    ZlibDemo.cpp contains the native code to compress and decompress data in memory.

    compressUtil compresses and decompress data in memory.

    void compressUtil(unsigned long originalDataLen) {
      int rv;
      int compressBufBound = compressBound(originalDataLen);
      compressedBuf = (unsigned char*) malloc(sizeof(unsigned char)*compressBufBound);
      unsigned long compressedDataLen = compressBufBound;
      rv = compress2(compressedBuf, &compressedDataLen, dataBuf, originalDataLen, 6);
      if (Z_OK != rv) {
        LOGE(1, "compression error");
        free(compressedBuf);
        return;
      }
      unsigned long decompressedDataLen = S_BUF_SIZE;
      rv = uncompress(decompressedBuf, &decompressedDataLen, compressedBuf, compressedDataLen);
      if (Z_OK != rv) {
        LOGE(1, "decompression error");
        free(compressedBuf);
        return;
      }
      if (0 == memcmp(dataBuf, decompressedBuf, originalDataLen)) {
        LOGI(1, "decompressed data same as original data");
      }   //free resource
      free(compressedBuf);
    }
  5. naCompressAndDecompress generates data for compression and calls the compressUtil function to compress and decompress the generated data:
    void naCompressAndDecompress(JNIEnv* pEnv, jclass clazz) {
      unsigned long originalDataLen = getOriginalDataLen();
      LOGI(1, "---------data with repeated bytes---------")
      generateOriginalData(originalDataLen);
      compressUtil(originalDataLen);
      LOGI(1, "---------data with random bytes---------")
      generateOriginalDataRandom(originalDataLen);
      compressUtil(originalDataLen);
    }

    GzFileDemo.cpp contains the native code to compress and decompress the data in file.

    writeToFile writes a string to a gzip file. Compression is applied at writing:

    int writeToFile() {
      gzFile file;
      file = gzopen("/sdcard/test.gz", "w6");
      if (NULL == file) {
        LOGE(1, "cannot open file to write");
        return 0;
      }
      const char* dataStr = "hello, Android NDK!";
      int bytesWritten = gzwrite(file, dataStr, strlen(dataStr));
      gzclose(file);
      return bytesWritten;
    }

    readFromFile reads data from the gzip file. Decompression is applied at reading:

    void readFromFile(int pBytesToRead) {
      gzFile file;
      file = gzopen("/sdcard/test.gz", "r6");
      if (NULL == file) {
        LOGE(1, "cannot open file to read");
        return;
      }
      char readStr[100];
      int bytesRead = gzread(file, readStr, pBytesToRead);
      gzclose(file);
      LOGI(1, "%d: %s", bytesRead, readStr);
    }
  6. Add an Android.mk file under the jni folder with the following content:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := ZlibDemo
    LOCAL_SRC_FILES := ZlibDemo.cpp GzFileDemo.cpp
    LOCAL_LDLIBS := -llog -lz
    include $(BUILD_SHARED_LIBRARY)
  7. Enable the naCompressAndDecompress function and disable the naGzFileDemo function, build and run the application. We can monitor the logcat output with the following command:
    $ adb logcat -v time ZlibDemo:I *:S

    The logcat output screenshot is shown as follows:

    How to do it...

    Enable the naGzFileDemo function and disable the naCompressAndDecompress function, build and run the application. The logcat output is shown in the following screenshot:

    How to do it...

How it works...

The zlib library provides compression and decompression functions for both in-memory data and files. We demonstrated both use cases. In the ZlibDemo.cpp file, we created two data buffers, one with repeated bytes, and the other with random bytes. We compress and decompress the data with the following steps:

  1. Compute the upper bound on the compressed size. This is done by the following function:
    uLong compressBound(uLong sourceLen);

    The function returns the maximum size of the compressed data after calling the compress or compress2 function on sourceLen bytes of source data.

  2. Allocate the memory for storing the compressed data.
  3. Compress the data. This is done by the following function:
    int compress2(Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen, int level);

    This function accepts five input arguments. source and sourceLen refer to the source data buffer and source data length. dest and destLen indicate the data buffer for storing the compressed data and size of this buffer. The value of destLen must be at least the value returned by compressBound when the function is called. When the function is returned, destLen is set to the actual size of the compressed data. The last input argument level can be a value between 0 and 9, where 1 gives best speed and 9 gives best compression. In our example, we set the value as 6 to compromise between speed and compression.

    Note

    We can also use the compress function to compress the data, which does not have the level input argument. Instead, it assumes a default level, which is equivalent to 6.

  4. Decompress the data. This is done by using the uncompress function:
    int uncompress(Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen);

    The input arguments have the same meaning as the compress2 function.

  5. Compare the decompressed data with the original data. This is just a simple check.

    By default, these functions use the zlib format for the compressed data.

    This library also supports reading and writing files in the gzip format. This is demonstrated in GzFileDemo.cpp. The usage of these functions is similar to the stdio functions for file reading and writing.

The steps we followed to write compressed data to a gzip file and then read the uncompressed data from it are shown as follows:

  1. Open a gzip file for writing. This is done by the following function:
    gzFile gzopen(const char *path, const char *mode);

    The function accepts a filename and open mode, and returns a gzFile object on success. The mode is similar to the fopen function, but with an optional compression level. In our example, we called the gzopen with w6 to specify the compression level as 6.

  2. Write data to a gzip file. This is done by the following function:
    int gzwrite(gzFile file, voidpc buf, unsigned len);

    This function writes uncompressed data into the compressed file. The input argument file refers to the compressed file, buf refers to the uncompressed data buffer, and len indicates the number of bytes to write. The function returns the actual number of uncompressed data written.

  3. Close the gzip file. This is done by the following function:
    int ZEXPORT    gzclose(gzFile file);

    Calling this function will flush all pending output and close the compressed file.

  4. Open the file for reading. We passed r6 to the gzopen function.
  5. Read data from the compressed file. This is done by the gzread function.
    int gzread(gzFile file, voidp buf, unsigned len);

    The function reads len number of bytes from file into buf. It returns the actual number of bytes read.

    Note

    The zlib library supports two compression formats, zlib and gzip. zlib is designed to be compact and fast, so it is best for use in memory and on communication channels. On the other hand, gzip is designed for single file compression on a filesystem, which has a larger header for maintaining the directory information, and uses a slower check method than zlib.

In order to use the zlib library, we must include the zlib.h header file in our source code and add the following line to Android.mk to link to the libz.so library:

LOCAL_LDLIBS := -lz

There's more...

Recall in the Managing assets at Android NDK recipe in Chapter 5, Android Native Application AP , we compiled the libpng library, which requires the zlib library.

We only covered a few functions provided by the zlib library. For more information, you can refer to the zlib.h and zconf.h header files in the platforms/android-<version>/arch-arm/usr/include/ folder. Detailed documentation for the zlib library can be found at http://www.zlib.net/manual.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset