Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android app crash NoClassDefFoundError on Samsung Lollipop devices

We recently bumped the minSdkVersion of our app from 16 (Jellybean) to 21 (Lollipop). Although we did extensive testing with our app predominantly using debug builds, we are now facing a slew of production crashes at app startup, predominantly on older Samsung devices - (Note3 and S4 are the top crashers) and always on Lollipop.

The error is

Fatal Exception: java.lang.NoClassDefFoundError: com.retailconvergence.ruelala.delegate.GoogleLoginDelegate
   at com.retailconvergence.ruelala.delegate.LifecycleDelegateManager.addDelegateOfType(LifecycleDelegateManager.java:48)
   at com.retailconvergence.ruelala.extensions.activity.LifecycleDelegateActivity.addDelegateOfType(LifecycleDelegateActivity.java:55)
   at com.retailconvergence.ruelala.activity.SplashActivity.setupDelegates(SplashActivity.java:198)
   at com.retailconvergence.ruelala.activity.SplashActivity.onCreate(SplashActivity.java:60)
   at android.app.Activity.performCreate(Activity.java:6288)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2758)
   at android.app.ActivityThread.access$900(ActivityThread.java:177)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1448)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:145)
   at android.app.ActivityThread.main(ActivityThread.java:5942)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

The SplashActivity is the initial lauching activity of the app. The class not found is a long-established class not something newly introduced. As a side note, as part of this latest release we upgraded to Android Studio 3 and introduced Kotlin code, but I dont think these are related to the issue. We are not using proguard in the build.

I'm aware that there was a significant change for builds when the minSdkVersion is 21 and above, relating to the use of ART instead of Dalvik, so I'm wondering if there is some flaw with Samsung Lollipop devices still looking for a class in the primary dex file now?

The module-level build.gradle:

import java.text.SimpleDateFormat
import java.util.concurrent.TimeUnit

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'io.fabric'
apply plugin: 'spoon'

// Manifest version information
def versionMajor = 4
def versionMinor = 2
def versionPatch = 0
def versionBuild = 0  // bump for dogfood builds, public betas, etc.
ext.versionReleaseDate="OCT-13-2017"    // UPDATE THIS WHEN YOU BUMP THE VERSIONS ABOVE FOR A NEW RELEASE MMM-dd-yyy


repositories {
    mavenCentral()
    maven { url 'https://maven.fabric.io/public' }
    maven { url 'http://salesforce-marketingcloud.github.io/JB4A-SDK-Android/repository' }
    maven { url "https://maven.google.com" }
    maven { url "http://maven.tealiumiq.com/android/releases/" }
}

def getCountOfHoursSinceVersionUpdate() {
    def currentDate = new Date()
    def format = new SimpleDateFormat("MMM-dd-yyyy")
    def buildDate = (Date)format.parse(versionReleaseDate)
    return (Integer)((currentDate.getTime() - buildDate.getTime()) / TimeUnit.HOURS.toMillis(1))
}

android {
    compileSdkVersion 26
    buildToolsVersion '26.0.1'

    defaultConfig {
        targetSdkVersion 25

        /**
         * Increment versionCode by commit count
         */

        versionCode       versionMajor * 100000 + versionMinor * 10000 + versionPatch * 1000 + versionBuild + getCountOfHoursSinceVersionUpdate()


        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        // Enabling multidex support. :(
        multiDexEnabled true

        def manifestPath = project(':').file('app/src/androidTest/AndroidManifest.xml')
        buildConfigField "String", "MANIFEST_PATH", "\"" + manifestPath + "\""

        def resPath = project(':').file('app/src/main/res/')
        buildConfigField "String", "RES_PATH", "\"" + resPath + "\""

        def assetPath = project(':').file('app/src/prod/assets/')
        buildConfigField "String", "ASSET_PATH", "\"" + assetPath + "\""

    }

    dexOptions {
        javaMaxHeapSize "8g"
        dexInProcess true // the magic line
    }


    flavorDimensions "debugDimension"

    /**
     * productFlavors override defaultConfig properties as well as force gradle to look in the new
     * folders that we have created to differentiate the build assets and manifests.
     * src/dev, src/prod
     */
    productFlavors {
        dev    {
            minSdkVersion 21
            applicationId "com.retailconvergence.ruelala.dev"
            versionName "${versionMajor}.${versionMinor}.0${versionPatch}"
            manifestPlaceholders = [optimizelyId: "optly4740131949"]
            dimension "debugDimension"
        }

        prod {
            minSdkVersion 21
            applicationId "com.retailconvergence.ruelala"
            versionName "${versionMajor}.${versionMinor}.${versionPatch}"
            manifestPlaceholders = [optimizelyId: "optly4752051515"]
            dimension "debugDimension"
        }
    }

    signingConfigs {
        prod {
            //the key is up a level, don't include in the modules
            storeFile file("../RueLaLaKeystore")
            storePassword "Boutiques"
            keyAlias "rue la la"
            keyPassword "Boutiques"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.prod


            ext.betaDistributionReleaseNotesFilePath = 'release_notes.txt'
            ext.betaDistributionGroupAliases = 'AndroidTesters'
            ext.betaDistributionNotifications = true
        }

        debug {
            versionNameSuffix '-dev'

            signingConfig signingConfigs.prod
            // to get coverage report, set testCoverageEnabled to true and run gradle task called createDevelopmentDebugAndroidTestCoverageReport
            // Note that test coverage doesn't seem to work on Samsung devices, other brand or emulator should work though
            testCoverageEnabled = false

            ext.betaDistributionReleaseNotesFilePath = 'release_notes.txt'
            ext.betaDistributionGroupAliases = 'AndroidTesters'
            ext.betaDistributionNotifications = true

        }
    }

    packagingOptions {
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
        exclude 'LICENSE.txt'
        exclude 'LICENSE'
        exclude 'READ.ME'
        exclude 'README'
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

configurations {
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    //include our modules
    compile project(':core')
    compile project(':data')

    //android
    final APP_COMPAT_VERSION = '26.1.0'

    compile "com.android.support:appcompat-v7:$APP_COMPAT_VERSION"
    compile "com.android.support:recyclerview-v7:$APP_COMPAT_VERSION"
    compile "com.android.support:design:$APP_COMPAT_VERSION"
    compile "com.android.support:multidex:1.0.0"
    compile "com.android.support:cardview-v7:$APP_COMPAT_VERSION"

    // google
    final PLAY_SERVICES_VERSION = '10.2.4'
    compile "com.google.android.gms:play-services-wallet:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-location:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-gcm:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-plus:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-identity:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-analytics:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-auth:$PLAY_SERVICES_VERSION"
    compile "com.google.android.gms:play-services-maps:$PLAY_SERVICES_VERSION"

    // facebook

    compile 'com.facebook.android:facebook-android-sdk:4.8.+'
    compile 'com.facebook.stetho:stetho:1.1.0'

    //markdown4j
    compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'

    //crashlytics
    compile('com.crashlytics.sdk.android:crashlytics:2.5.2@aar') {
        transitive = true;
    }

    //image zoom
    compile 'com.github.chrisbanes.photoview:library:1.2.3'

    //square
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.makeramen:roundedimageview:2.2.1'


    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'

    compile 'com.jakewharton:butterknife:8.6.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'

    // optimizely
    compile('com.optimizely:optimizely:1.4.2@aar') {
        transitive = true
    }

    //braintree
    compile 'com.braintreepayments.api:braintree:2.6.0'
    compile 'com.braintreepayments.api:data-collector:2.+'

    // guava
    compile 'com.google.guava:guava:19.0'

    // sticky headers
    compile 'com.github.mtotschnig:StickyListHeaders:2.7.1'

    // expandable recyclerview
    compile 'eu.davidea:flexible-adapter:5.0.0-rc2'

    //recyclerview animations
    compile 'jp.wasabeef:recyclerview-animators:2.2.3'

    // tooltip
    compile 'com.github.michaelye.easydialog:easydialog:1.4'

    // tealium
    compile 'com.tealium:library:5.3.0'

    // circle indicator
    compile 'me.relex:circleindicator:1.2.2@aar'

    //testing
    final HAMCREST_VERSION = '1.3'

    def jUnit = "junit:junit:4.12"

    // ExactTarget SDK
    compile ('com.salesforce.marketingcloud:marketingcloudsdk:5.0.5') {
        exclude module: 'android-beacon-library' //remove to use Proximity messaging
        exclude module: 'play-services-location' //remove to use Geofence or Proximity messaging
    }

    androidTestCompile jUnit

    // Unit tests dependencies
    testCompile jUnit
    testCompile "org.hamcrest:hamcrest-core:$HAMCREST_VERSION"
    testCompile "org.hamcrest:hamcrest-library:$HAMCREST_VERSION"
    testCompile "org.hamcrest:hamcrest-integration:$HAMCREST_VERSION"
    testCompile 'org.robolectric:robolectric:3.1'
    testCompile 'org.mockito:mockito-core:1.+'
    testCompile 'com.google.guava:guava:19.0'
    testCompile("com.android.support:support-v4:$APP_COMPAT_VERSION") {
        exclude module: 'support-annotations'
    }
    testCompile('org.powermock:powermock-api-mockito:1.6.4') {
        exclude module: 'objenesis'
    }
    testCompile('org.powermock:powermock-module-junit4:1.6.4') {
        exclude module: 'objenesis'
    }
    testCompile 'io.reactivex:rxandroid:1.0.1'
    testCompile 'io.reactivex:rxjava:1.1.0'

    // Espresso
    androidTestCompile('com.android.support.test:runner:0.5') {
        exclude module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test:rules:0.5') {
        exclude module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
        exclude module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
        exclude module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
        exclude module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
        exclude module: 'support-annotations'
        exclude module: 'recyclerview-v7'
        exclude module: 'appcompat-v7'
        exclude module: 'design'
        exclude module: 'support-v4'
    }

    // allows java 8 compile
    compile 'com.annimon:stream:1.1.2'

    // For taking screenshots
    androidTestCompile 'com.squareup.spoon:spoon-client:1.7.0'

    testCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}

apply plugin: 'com.google.gms.google-services'

spoon {
    noAnimations = true
    grantAllPermissions = true
}

apply plugin: 'devicefarm'

devicefarm {
    projectName "Rue Mobile"
    devicePool "Smoke Test Pool"
    useUnmeteredDevices()
    authentication {
        accessKey System.getenv("AWS_DEVICE_FARM_ACCESS_KEY")
        secretKey System.getenv("AWS_DEVICE_FARM_SECRET_KEY")
    }
}
like image 657
Michael Scott Avatar asked Mar 07 '23 19:03

Michael Scott


1 Answers

The fix for this was to disable pre-dexing:

dexOptions {
    preDexLibraries false
}

in the app build.gradle. Inspiration for this came from this link regarding a Picasso class not found error on Lollipop: see here

Its not entirely clear to me why disabling pre-dexing solves the problem, but I can only theorize that there is some optimization going on with the build process that affects the way classes are ordered in the dex files of the apk that then affects the app installation on these Samsung Lollipop devices. In theory ART should take care of all of that, but clearly there is a dependency between pre-dex optimization and some Lollipop devices.

like image 123
Michael Scott Avatar answered Mar 10 '23 11:03

Michael Scott