13

I'm switching an Android application from using Proguard's desugaring to the new R8 desugaring available in Android Gradle Build Plugin 4.0.0.

I've followed the steps as detailed in the official documentation to enable Java 8 library desugaring:

gradle.properties

projectJavaVersion = 1.8
android.useAndroidX=true
android.enableJetifier=true

app build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}
android {
    buildToolsVersion '29.0.2'
    compileSdkVersion 29
    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility projectJavaVersion
        targetCompatibility projectJavaVersion
    }
    kotlinOptions {
        jvmTarget = projectJavaVersion
    }
    defaultConfig {
        multiDexEnabled true
        minSdkVersion 19
        targetSdkVersion 23
        applicationId = 'com.example.app'
    }
    buildTypes {
        release {
            ...
            minifyEnabled true
        }
        debug {
            debuggable true
            minifyEnabled true
        }
    }
}
dependencies {
    coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10"
    ...
}

You can see that we're supporting API 19 as a minimum. There are no build errors (using Gradle 6.1.1), but there are the following warnings:

Warning in synthesized for lambda desugaring:
  Type `j$.$r8$wrapper$java$util$function$Function$-V-WRP` was not found, it is required for default or static interface methods desugaring of `java.util.Comparator java.time.chrono.-$$Lambda$AbstractChronology$j22w8kHhJoqCd56hhLQK1G0VLFw.thenComparing($-vivified-$.java.util.function.Function)`
Warning in synthesized for lambda desugaring:
  Type `j$.$r8$wrapper$java$util$function$ToLongFunction$-V-WRP` was not found, it is required for default or static interface methods desugaring of `java.util.Comparator java.time.chrono.-$$Lambda$AbstractChronology$j22w8kHhJoqCd56hhLQK1G0VLFw.thenComparingLong($-vivified-$.java.util.function.ToLongFunction)`
Warning in synthesized for lambda desugaring:
  Type `j$.$r8$wrapper$java$util$function$ToDoubleFunction$-V-WRP` was not found, it is required for default or static interface methods desugaring of `java.util.Comparator java.time.chrono.-$$Lambda$AbstractChronology$j22w8kHhJoqCd56hhLQK1G0VLFw.thenComparingDouble($-vivified-$.java.util.function.ToDoubleFunction)`
Warning in synthesized for lambda desugaring:
  Type `j$.$r8$wrapper$java$util$function$ToIntFunction$-V-WRP` was not found, it is required for default or static interface methods desugaring of `java.util.Comparator java.time.chrono.-$$Lambda$AbstractChronology$j22w8kHhJoqCd56hhLQK1G0VLFw.thenComparingInt($-vivified-$.java.util.function.ToIntFunction)`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ConcurrentHashMap$EntryIterator.class:
  Type `j$.$r8$wrapper$java$util$function$Consumer$-V-WRP` was not found, it is required for default or static interface methods desugaring of `void java.util.concurrent.ConcurrentHashMap$EntryIterator.forEachRemaining($-vivified-$.java.util.function.Consumer)`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ConcurrentHashMap$CollectionView.class:
  Type `j$.$r8$wrapper$java$util$stream$Stream$-WRP` was not found, it is required for default or static interface methods desugaring of `$-vivified-$.java.util.stream.Stream java.util.concurrent.ConcurrentHashMap$CollectionView.parallelStream()`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ConcurrentHashMap$CollectionView.class:
  Type `j$.$r8$wrapper$java$util$Spliterator$-WRP` was not found, it is required for default or static interface methods desugaring of `$-vivified-$.java.util.Spliterator java.util.concurrent.ConcurrentHashMap$CollectionView.spliterator()`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ConcurrentHashMap$CollectionView.class:
  Type `j$.$r8$wrapper$java$util$function$Predicate$-V-WRP` was not found, it is required for default or static interface methods desugaring of `boolean java.util.concurrent.ConcurrentHashMap$CollectionView.removeIf($-vivified-$.java.util.function.Predicate)`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/DesugarCollections$SynchronizedMap.class:
  Type `j$.$r8$wrapper$java$util$function$BiFunction$-V-WRP` was not found, it is required for default or static interface methods desugaring of `void java.util.DesugarCollections$SynchronizedMap.replaceAll($-vivified-$.java.util.function.BiFunction)`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/DesugarCollections$SynchronizedMap.class:
  Type `j$.$r8$wrapper$java$util$function$BiConsumer$-V-WRP` was not found, it is required for default or static interface methods desugaring of `void java.util.DesugarCollections$SynchronizedMap.forEach($-vivified-$.java.util.function.BiConsumer)`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ThreadLocalRandom.class:
  Type `j$.$r8$wrapper$java$util$stream$IntStream$-WRP` was not found, it is required for default or static interface methods desugaring of `$-vivified-$.java.util.stream.IntStream java.util.concurrent.ThreadLocalRandom.ints()`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ThreadLocalRandom.class:
  Type `j$.$r8$wrapper$java$util$stream$LongStream$-WRP` was not found, it is required for default or static interface methods desugaring of `$-vivified-$.java.util.stream.LongStream java.util.concurrent.ThreadLocalRandom.longs()`
Warning in /Users/username/.gradle/caches/modules-2/files-2.1/com.android.tools/desugar_jdk_libs/1.0.4/961afbdb3d41eebfb63b8c8ccdc97453b869964e/desugar_jdk_libs-1.0.4.jar:java/util/concurrent/ThreadLocalRandom.class:
  Type `j$.$r8$wrapper$java$util$stream$DoubleStream$-WRP` was not found, it is required for default or static interface methods desugaring of `$-vivified-$.java.util.stream.DoubleStream java.util.concurrent.ThreadLocalRandom.doubles(long)`

Warning: Type `java.util.OptionalConversions` was not found, it is required for default or static interface methods desugaring of `java.util.OptionalLong j$.$r8$wrapper$java$util$stream$LongStream$-WRP.findAny()`
Warning: Type `java.util.LongSummaryStatisticsConversions` was not found, it is required for default or static interface methods desugaring of `java.util.LongSummaryStatistics j$.$r8$wrapper$java$util$stream$LongStream$-WRP.summaryStatistics()`
Warning: Type `java.util.IntSummaryStatisticsConversions` was not found, it is required for default or static interface methods desugaring of `java.util.IntSummaryStatistics j$.$r8$wrapper$java$util$stream$IntStream$-WRP.summaryStatistics()`
Warning: Type `java.util.DoubleSummaryStatisticsConversions` was not found, it is required for default or static interface methods desugaring of `java.util.DoubleSummaryStatistics j$.$r8$wrapper$java$util$stream$DoubleStream$-WRP.summaryStatistics()`

When I run the app, launching an activity with the following Optional in the code, the app crashes with

I/ApplicationBase: Testing Java 8
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: com.example.app.DEV, PID: 8265
    java.lang.RuntimeException: An error occured while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:300)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:841)
     Caused by: java.lang.NoClassDefFoundError: j$.util.Optional
        at com.example.app.ApplicationBase.launch(ApplicationBase.java:251)
        at com.example.app.LaunchTask.doInBackground(LaunchTask.java:19)
        at com.example.app.LaunchTask.doInBackground(LaunchTask.java:6)
        at android.os.AsyncTask$2.call(AsyncTask.java:288)

ApplicationBase.java

import androidx.multidex.MultiDexApplication
import java.util.Optional;

public class ApplicationBase extends MultiDexApplication implements LaunchTask.Launcher {
  @SuppressLint("NewApi")
  @Override
  public void launch() throws IOException {
    IOHandler ioHandler = new AndroidAppIOHandler(getApplicationContext());
    Logger.i(TAG, "Testing Java 8");
    Optional.of("xyz").ifPresent(__ -> Logger.i(TAG, "Good"));
    Logger.i(TAG, "Tested Java 8");
    ...
  }
  ...
}

What am I missing?


After reading @sgjesse's answer, which seemed to identify a problem with just the launch() method, we did some further investigation. I can confirm that MultiDex.install() is being called before launch().

If I add an Optional in AndroidAppIoHandler, like this:

public AndroidAppIOHandler(Context context) {
  this.context = context;
  final Optional<Context> context1 = Optional.of(context);
  Log.d("TESTMULTIDEX1", context1.getClass() + "  " + context1.get().getClass() + " " + context1.toString());
}

then I see:

D/TESTMULTIDEX1( 2826): class j$.util.Optional  class com.example.app.ApplicationBase Optional[com.example.app.ApplicationBase@9d006a00]

Which is called from the first line of launch().

If I add an Optional to to the next line of the launch() method, like so:

public void launch() throws IOException {
  IOHandler ioHandler = new AndroidAppIOHandler(getApplicationContext());
  final Optional<IOHandler> ioHandler1 = Optional.of(ioHandler);
  ...

then I see:

java.lang.NoClassDefFoundError: j$.util.Optional

on the same execution, after printing the other message above!

Fraser
  • 315
  • 2
  • 10
  • Did you try to build it with `minifyEnabled false`? – dev.bmax Sep 01 '20 at 14:26
  • The same thing happens with `minifyEnabled false` @dev.bmax – Fraser Sep 01 '20 at 16:00
  • I'm facing the same issue. Did you find an answer? – Daniil Popov Sep 04 '20 at 15:04
  • No answer yet, @DaniilPopov – Fraser Sep 07 '20 at 07:44
  • 1
    First of all is the crash only on devices with API level 19? If so, it could be that the `MultiDex.install` has not been called before using desugared types. As the implementation of e.g. `java.util.Optional` (which is called `j$.util.Option` is in a separate DEX file `MultiDex.install` has to be called on devices which does not support native multi dex (which was added for API level 21). In `MultiDexApplication` the call to `MultiDex.install` is in `attachBaseContext`. If the `launch` method is invoked before `MultiDex.install` you will see the `NoClassDefFoundError`. – sgjesse Sep 07 '20 at 09:52
  • Thank you @sgjesse, that's nailed it. I can run the app without a crash on API 21, and moving the `Optional` to a different method (that runs after `Multidex.install`) works on API 19. I don't suppose you have any idea how I can write a unit test that fails in this case? I have several Roboelectric tests with `@Config(sdk = 19)` which all call `((ApplicationBase) ApplicationProvider.getApplicationContext()).launch()` as part of the `setUp()`, but no exceptions are thrown. – Fraser Sep 07 '20 at 11:31
  • Additionally if you'd like to write up your comment as an answer @sgjesse I'll mark it as the accepted one. – Fraser Sep 07 '20 at 14:21
  • Good to hear that this solved your problem. Added an answer with the content. I am not that familiar with Roboelectric and desugared library. As Roboelectric does not run on a device it might be that it is not possible to directly reproduce there. Maybe you could make your own implementation of `MultiDexApplication` which keeps track of when `MultiDex.install` has been called. Then you can check in `setUp` (and other methods called early in the application lifecycle) that the desugared library is indeed loaded. Not an optimal solution. Running device tests on older devices wlll be better. – sgjesse Sep 08 '20 at 06:47
  • Unfortunately this did not seem to be the cause of the issue in the end - MultiDex.install is being called before launch (we tested this with the debugger). – Fraser Sep 09 '20 at 08:30

5 Answers5

2

I faced the same issue on our legacy app, which still supports Android 4.1. After I've created a sample project and tried to reproduce the error, I've found the problem (or bug?).

Unfortunately it seems that Android devices with 4.4 or lower can't use any Java 8 class or interface which will be touched by Core Library Desugaring inside the onCreate method of the application.

It seems like a weird multi dexing issue/bug.

I tried the following:

class CoreApplication : MultiDexApplication() {
    override fun attachBaseContext(base: Context?) {
        //adding the new GMS causes to many Methods in APK, therefore configure Application as MultiDex
        super.attachBaseContext(base)
        MultiDex.install(this)
    }

    override fun onCreate() {
        super.onCreate()
        Optional.of("TEst")
    }
}

Which will result in your mentioned crash:

03-22 18:36:09.231 657-657/com.plauzeware.CoreLibraryDesugeringCrashOnAndroidKitkat E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.plauzeware.CoreLibraryDesugeringCrashOnAndroidKitkat, PID: 657
    java.lang.NoClassDefFoundError: j$.util.Optional
        at com.plauzeware.corelibrarydesugeringcrashonandroidkitkat.CoreApplication.onCreate(Application.kt:17)
        at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1030)
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4409)
        at android.app.ActivityThread.access$1500(ActivityThread.java:139)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1270)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5086)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
        at dalvik.system.NativeStart.main(Native Method)

But when I wrap the call to Optional inside a helper class

class Loader {
    companion object {
        fun load() {
            Optional.of("TEst")
        }
    }
}

and call it inside my onCreate function

class CoreApplication : MultiDexApplication() {
    override fun attachBaseContext(base: Context?) {
        //adding the new GMS causes to many Methods in APK, therefore configure Application as MultiDex
        super.attachBaseContext(base)
        MultiDex.install(this)
    }

    override fun onCreate() {
        super.onCreate()
        Loader.load()
    }
}

it will work. I think it's a bug and I will file a bug report and will update this answer with a Link to it.

Because the multidexing issue touches also basic interfaces like Iterable it's really painful to resolve those errors. Especially because we still use OrmLite and it will use Iterable all the time.

I've added my code for reference to my Github

UPDATE:

Here is the link to the bug report.

user3166372
  • 158
  • 2
  • 10
1

I was facing quite a similar issue and in my case, it turned out that it is a bug in Android Gradle Plugin. Long story short AGP generates a few the same wrapper classes in different DEX files that blow Dalvik/ART on old Android versions. And the problem was only on debug build because R8/ProGuard deduplicates that classes in release builds.

And there is a workaround for that issue:

buildscript {

    repositories {
        maven {
            url "https://storage.googleapis.com/r8-releases/raw/master" // NOTICE 'master' here!
        }
    }

    dependencies {
        classpath 'com.android.tools:r8:f03be11f11b8405b69876d05337e917a5519e52a'  // Must be before the Gradle Plugin for Android.
        classpath 'com.android.tools.build:gradle:X.Y.Z'     // Your current AGP version.
     }
}

Hopefully, this will help you.

Daniil Popov
  • 471
  • 4
  • 15
1

This was solved by upgrading to Android Gradle Build Plugin 4.2.0, where R8 desugars correctly.

Fraser
  • 315
  • 2
  • 10
1

I updated the version of my com.android.tools.build:gradle acording to this table

Leonardo Sibela
  • 1,613
  • 1
  • 18
  • 39
0

If the crash is only seen on devices with API level 19 the reason could be that the MultiDex.install has not been called before using desugared types.

The implementation of java.util.Optional (which is called j$.util.Optional in the desugared library) is in a separate DEX file. On devices which does not support native multi dex (which was added in API level 21) MultiDex.install has to be called before the desugared types can be used. In MultiDexApplication the call to MultiDex.install is in attachBaseContext. If the launch method is invoked before MultiDex.install you will see the NoClassDefFoundError.

sgjesse
  • 3,793
  • 14
  • 17
  • Hey, please see the additions I made to the question - we did some investigation following your answer and unfortunately Multidex.install is being called before the launch method. Additionally we see some weirdness where Optionals work if they are used in a different object within `launch` on API 19, but not when used directly in `launch()`... – Fraser Sep 09 '20 at 08:26