javaandroidapache-commons-compress

Apache Commons Compress Cannot Resolve Class SeekableInMemoryByteChannel Prior to Android N


Whenever I try to use the SeekableInMemoryByteChannel class from the Apache commons-compress package the application crashes with a java.lang.NoClassDefFoundError: org.apache.commons.compress.utils.SeekableInMemoryByteChannel exception.

The particularly interesting part of this, for me at least, is that the issue does not occur on devices running Android 7+, but does on all other versions that I have tested. To be more specific I have observed that this issue occurs on Android 6.0.1, 6.0, 5.1.1 but does not occur on Android 7.0 or 7.1.1. Each of these devices comes from a different vendor.

So I'm facing a severe case of ghost class. At first I thought that this may have been related to the fact that my app was using MultiDex, or that the build process was incorrectly configured. But the same happens on a completely new project that does not have these features. I've tried to use different classes from the same package and they are resolved successfully. I added the package to ProGuard but no luck.

This is the code that replicates the issue:

package lt.kaz.compresstest;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ZipArchiveEntry zipEntry = new ZipArchiveEntry("hello");
        zipEntry.setSize(600);

        SevenZArchiveEntry sevenZipEntry = new SevenZArchiveEntry();
        sevenZipEntry.setName("sevenZip");

        try {
            MainActivity.class.getClassLoader().loadClass("org.apache.commons.compress.utils.ChecksumCalculatingInputStream");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[]{5, 10, 15, 10});
        // App never gets here prior to Android N
        Log.d("lt.kaz", channel.toString());
    }
}

... and the gradle file ...

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId 'lt.kaz.compresstest'
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:design:26.+'
    compile 'org.apache.commons:commons-compress:1.14'
    testCompile 'junit:junit:4.12'
}

... and ProGuard ...

-keep public class org.apache.commons.compress

Any help would be really appreciated. What could be the reason for such behaviour? How can it be solved?


Solution

  • Apparently, the SeekableByteChannel interface form the java.nio.channels package is only available from Android API level 24 (hence android N).

    My final solution, since my original intent was to use the SevenZFile class, was to downgrade commons-compress to version 1.12, since the implementation there doesn't use the the SeekableByteChannel class.