Android Gradle Build Plugin 4.0.0 & R8 Desugaring not working on API 19
Asked Answered
G

5

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!

Gilkey answered 1/9, 2020 at 13:26 Comment(9)
Did you try to build it with minifyEnabled false?Dishpan
The same thing happens with minifyEnabled false @DishpanGilkey
I'm facing the same issue. Did you find an answer?Intension
No answer yet, @DaniilPopovGilkey
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.Canter
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.Gilkey
Additionally if you'd like to write up your comment as an answer @Canter I'll mark it as the accepted one.Gilkey
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.Canter
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).Gilkey
G
1

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

Gilkey answered 8/11, 2021 at 14:2 Comment(0)
D
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.

Diatomaceous answered 22/3, 2021 at 17:47 Comment(1)
Thank you, this is a really good explanation. Your time and effort are much appreciated!Gilkey
I
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.

Intension answered 8/9, 2020 at 8:10 Comment(2)
Unfortunately this does not resolve the issue, but thank you anyway!Gilkey
Sorry to hear that :(Intension
G
1

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

Gilkey answered 8/11, 2021 at 14:2 Comment(0)
R
1

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

Regimen answered 10/4, 2023 at 16:35 Comment(0)
C
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.

Canter answered 8/9, 2020 at 6:40 Comment(1)
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()...Gilkey

© 2022 - 2024 — McMap. All rights reserved.