Bug 40515 - Unable to successfully build a MultiDex enabled app that utilizes a custom Application class that will run on pre - Lollipop devices?
Summary: Unable to successfully build a MultiDex enabled app that utilizes a custom Ap...
Status: RESOLVED DUPLICATE of bug 40976
Alias: None
Product: Android
Classification: Xamarin
Component: MSBuild (show other bugs)
Version: 6.0.0
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Atsushi Eno
URL:
Depends on:
Blocks:
 
Reported: 2016-04-19 11:21 UTC by Bradley Locke
Modified: 2017-09-23 17:34 UTC (History)
6 users (show)

See Also:
Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
MultiDexMainDexList (65.86 KB, text/plain)
2016-04-21 09:44 UTC, Bradley Locke
Details
Working MultiDex Application class (2.14 KB, text/plain)
2016-05-04 17:03 UTC, Bradley Locke
Details
mainDexClasses.bat (3.85 KB, text/plain)
2016-07-06 18:11 UTC, Bradley Locke
Details

Description Bradley Locke 2016-04-19 11:21:12 UTC
There are a quite a few posts scattered around about this but my question is:

Are we able to successfully build a MultiDex enabled app that utilizes a custom Application class that will run on pre - Lollipop devices?

The issue is that when creating a MultiDex app that uses a custom application class (I'd guess the vast majority of apps over the 65k limit would be in this category), that class does not end up in the first dex file which causes devices without built in MultiDex support to crash with the error

[AndroidRuntime] java.lang.RuntimeException: Unable to instantiate application md5a3fc106bb082f0a6c07b5025b0a464e3.MyApplication: java.lang.ClassNotFoundException: Didn't find class "md5a3fc106bb082f0a6c07b5025b0a464e3.MyApplication" on path

I've tried all of the listed solutions, including from Xamarin support in order to get to this to work.

There are numerous articles about it:

https://bugzilla.xamarin.com/show_bug.cgi?id=35491
https://bugzilla.xamarin.com/show_bug.cgi?id=38693
https://forums.xamarin.com/discussion/57485/multiple-issues-with-library-project-and-multidex

What I've done:

I have created a custom MultiDexMainDexList file by using a modified version of the mainDexClasses.bat file (as per the articles).

I then modified the command that Xamarin throws at it to use the full path names as Xamarin just tries to use "obj\Debug"

mainDexClasses_fixed.bat --output C:\Users\bradl_000\Documents\blocke79\App\App\obj\Debug\multidex.keep "'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid\v6.0\mono.android.jar';'C:\androidsdk\extras\android\support\multidex\library\libs\android-support-multidex.jar';'C:\Users\bradl_000\Documents\blocke79\App\App\obj\Debug__library_projects__\Aniways.AndroidBinding\library_project_imports\aniways-V2.3.08.jar'" (have trimmed this massive list for reading)

This produces a MultiDexMainDexList file with all of the classes in my project. I then add this to my project with the new build action of "MultiDexMainDexList" (only available in v6).

This gets rid of the error regarding the application class but instead throws a new error 

[AndroidRuntime] Shutting down VM
[AndroidRuntime] FATAL EXCEPTION: main
[AndroidRuntime] Process: com.eroad.driverna, PID: 31444
[AndroidRuntime] java.lang.NoClassDefFoundError: mono.MonoPackageManager_Resources
[AndroidRuntime]    at mono.MonoPackageManager.LoadApplication(MonoPackageManager.java:52)
[AndroidRuntime]    at mono.MonoRuntimeProvider.attachInfo(MonoRuntimeProvider.java:22)
[AndroidRuntime]    at android.app.ActivityThread.installProvider(ActivityThread.java:5199)
[AndroidRuntime]    at android.app.ActivityThread.installContentProviders(ActivityThread.java:4794)
[AndroidRuntime]    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4734)
[AndroidRuntime]    at android.app.ActivityThread.access$1500(ActivityThread.java:166)
[AndroidRuntime]    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1343)
[AndroidRuntime]    at android.os.Handler.dispatchMessage(Handler.java:102)
[AndroidRuntime]    at android.os.Looper.loop(Looper.java:136)
[AndroidRuntime]    at android.app.ActivityThread.main(ActivityThread.java:5590)
[AndroidRuntime]    at java.lang.reflect.Method.invokeNative(Native Method)
[AndroidRuntime]    at java.lang.reflect.Method.invoke(Method.java:515)
[AndroidRuntime]    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
[AndroidRuntime]    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
[AndroidRuntime]    at dalvik.system.NativeStart.main(Native Method)

I then started thinking that I should trim down the entire list of classes in the MultiDexMainDexList file and keep only the ones that I think my custom application class needs to load. No matter what I've done to this list I still get the error regarding the MonoPackageManager_Resources.
Comment 1 Bradley Locke 2016-04-21 09:44:40 UTC
Created attachment 15800 [details]
MultiDexMainDexList

Here is the MultiDexMainKeepList file. I don't understand what happens if there are more entries in here than can fit into the first dex file. I have tried trimming the list down and changing the order but nothing seems to make a difference.
Comment 2 Alexandre Chohfi 2016-05-03 17:23:44 UTC
I managed to workaround this, using a custom MultiDexApplication App class, like this:

namespace Android.Support.Multidex
{
    [Register("android/support/multidex/MultiDexApplication", DoNotGenerateAcw = true)]
    public class MultiDexApplication : Application
    {
        internal new static readonly JniPeerMembers _members = (JniPeerMembers)new XAPeerMembers("android/support/multidex/MultiDexApplication", typeof(MultiDexApplication));

        internal static IntPtr java_class_handle;
        internal static IntPtr class_ref
        {
            get
            {
                return JNIEnv.FindClass("android/support/multidex/MultiDexApplication", ref java_class_handle);
            }
        }

        protected override IntPtr ThresholdClass
        {
            get { return class_ref; }
        }

        protected override global::System.Type ThresholdType
        {
            get { return typeof(MultiDexApplication); }
        }

        static IntPtr id_ctor;

        [Register(".ctor", "()V", "", DoNotGenerateAcw = true)]
        public unsafe MultiDexApplication()
            : base(IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
        {
            if (Handle != IntPtr.Zero)
                return;

            try
            {
                if (GetType() != typeof(MultiDexApplication))
                {
                    SetHandle(
                            global::Android.Runtime.JNIEnv.StartCreateInstance(GetType(), "()V"),
                            JniHandleOwnership.TransferLocalRef);
                    global::Android.Runtime.JNIEnv.FinishCreateInstance(Handle, "()V");
                    return;
                }

                if (id_ctor == IntPtr.Zero)
                    id_ctor = JNIEnv.GetMethodID(class_ref, "<init>", "()V");
                SetHandle(
                        global::Android.Runtime.JNIEnv.StartCreateInstance(class_ref, id_ctor),
                        JniHandleOwnership.TransferLocalRef);
                JNIEnv.FinishCreateInstance(Handle, class_ref, id_ctor);
            }
            finally
            {
            }
        }

        protected MultiDexApplication(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }
    }
}

IMO, Xamarin should map MultiDexApplication as a C# class officially, exactly to support this scenario.
Comment 3 Bradley Locke 2016-05-04 13:19:10 UTC
Thanks Alexandre.

What are the steps you used to get this to work?

I have created your custom MultiDex class and made my application class overload it.

Do you still use the Xamarin process of ticking enable MultiDex and using a custom MultiDexMainList file?
Comment 4 Alexandre Chohfi 2016-05-04 13:47:53 UTC
Yes, I still had to replace the bat file, then I got the NoClassDefFoundError: mono.MonoPackageManager_Resources error.
Then, using this custom class it kinda works.
I said kinda, because the debugger won't attach, but my app runs on pre 5.0 devices. If you would like to know better what is happening, attach logcat.
Comment 5 Bradley Locke 2016-05-04 16:59:08 UTC
Alexandre, thank you so much for this! Its working for me as well. Even the debugging. Your a genius.
Comment 6 Bradley Locke 2016-05-04 17:03:37 UTC
Created attachment 15907 [details]
Working MultiDex Application class

Here is the working Multi Dex application class after letting Resharper clean it up
Comment 7 Andy 2016-05-12 19:10:39 UTC
I wasn't able to get this to work in Xamarin Forms. It would make a lot more sense for there to just be a MultiDexApplication for us to inherit from instead of behind the scenes creating one for us.

We're forced to use our own Application file to use UrbanAirship push notifications, and the newest Xamarin.Forms 2.2 brought in so many Android support libraries that it's forcing us to use MultiDex.
Comment 8 Atsushi Eno 2016-05-19 07:53:53 UTC
This is technically a duplicate of #40976 which is fixed in our master.

On the historical background: custom application class with arbitrary Java application class (i.e. bindings) could not be created because Android 2.3 had different bootstrap process than Android 4.0 which forced us to create our own Application class named mono.android.app.Application.

Under such restriction, we had to add AndroidEnableMultiDex option to explicitly specify MultiDexApplication as the base class of mono.android.app.Application.

In the latest release, we removed that class generation, and do not override Application#onCreate() unless it targets API Level < 15. Since then, it is technically possible to use arbitrary Java Application class in Xamarin.Android app. Lack of MultiDexApplication support in such scenario was fallout of the change.

Now you can bind multidex support library just like other binding projects and use it, but we'd keep existing simpler checkbox option.

*** This bug has been marked as a duplicate of bug 40976 ***
Comment 9 Dimitar Dobrev 2016-07-06 17:17:23 UTC
Could you we have an official centralised documented walk-through for this one? At present it's scattered here, at https://bugzilla.xamarin.com/show_bug.cgi?id=35491, https://bugzilla.xamarin.com/show_bug.cgi?id=38693, https://forums.xamarin.com/discussion/64234/multi-dex-app-with-a-custom-application-class-that-runs-on-pre-lollipop, https://forums.xamarin.com/discussion/57485/multiple-issues-with-library-project-and-multidex and God knows where else. And most importantly - it doesn't work. We followed https://forums.xamarin.com/discussion/64234/multi-dex-app-with-a-custom-application-class-that-runs-on-pre-lollipop , which is the closest to any kind of instructions, step by step and our application.class refuses to go in classes.dex. Is this problem really solved? Is it really a problem in the Android SDK and not yours?
Comment 10 Bradley Locke 2016-07-06 18:11:28 UTC
Created attachment 16594 [details]
mainDexClasses.bat
Comment 11 Bradley Locke 2016-07-06 18:17:56 UTC
Hi Dimitar,

The fix does work for me. I did away with creating the custom MultixDexMainKeepList file. What I have done is:

1. Create a custom class MultiDexApplication (attached above)
2. Make my custom application class overload the MultiDexApplication class
3. Copy the modified mainDexClasses.bat file (attached above) to the version of the build tools that I am targeting for my project (for me this is 23). So the path where I copy the file is C:\Users\Bradley\AppData\Local\Android\sdk\build-tools\23.0.1. I renamed the original file here before copying.

After doing this my application class is included in the MainDex file and will work on pre-Lollipop devices despite having 140k methods.

Personally I think Xamarin blaming this on the Android SDK is a joke. That argument could be used for almost anything piece of functionality. Enabling MultiDex for an app built in Android Studio takes a matter of seconds. It should be just as easy with Xamarin.

Regards,
Brad
Comment 12 Dimitar Dobrev 2016-07-06 18:41:36 UTC
Bradley, thank you for the summary. I would like to post my own steps here, could you please tell me if they are any different to yours?

1. Create a custom class MultiDexApplication (attached above)
2. Make my custom application class overload the MultiDexApplication class
3. Copy the modified mainDexClasses.bat file (attached above) to the version of the build tools that I am targeting for my project (for me this is 23). So the path where I copy the file is C:\Users\Bradley\AppData\Local\Android\sdk\build-tools\23.0.1. I renamed the original file here before copying.
4. Build the application locally;
5. Go to obj/<build_config> and find multidex.keep;
6. Copy it to my project root, without renaming or editing;
7. Include it in my project and set its action to "MultiDexMainDexList";
8. Build the application again.
Comment 13 Dimitar Dobrev 2016-07-06 18:53:06 UTC
Bradley, also, even if all of this is successful, do we have to regenerate the file each time we add a new class to our project, particularly if the application class depends on this new class?
Comment 14 Bradley Locke 2016-07-06 19:59:52 UTC
Hi Dimitar,

I'm now only using steps 1 - 3. 

By placing the updated mainDexClasses.bat file in the build tools path the multidex.keep file is generated correctly in place so you no longer need to do the additional steps of copying the file and adding it to the project with the special build type.

This config has been working for some time for me without an issue. Using the older method (by manually creating the multidex.keep and adding it to the project) meant that I had to do this every time I added a new class to my code.

Regards,
Brad
Comment 15 Dimitar Dobrev 2016-07-06 20:03:00 UTC
Bradley, thank you very much. We will try again. We have also contacted Xamarin's support in case it still doesn't work. Cheers.
Comment 16 Daniel 2017-09-23 17:34:45 UTC
Be aware that right now, there's another bug (missing line breaks in the multidex.keep file) that will prevent this solution from working properly. 
Things started working again after I edited the multidex.keep file and added (Windows) line breaks after each ".class" in the file.

The new bug is documented here: https://bugzilla.xamarin.com/show_bug.cgi?id=59036

Note You need to log in before you can comment on or make changes to this bug.