23

One of several thousand customers reported an error in one of my apps. The error is:

java.lang.NoClassDefFoundError - android.security.MessageDigest

I don't use that class/method in my apps. The Google Mapkey must be ok because there are thousands running the same app with the same version happily. Here's the stacktrace:

java.lang.NoClassDefFoundError: android.security.MessageDigest
at com.google.android.maps.KeyHelper.getSignatureFingerprint(KeyHelper.java:60)
at com.google.android.maps.MapActivity.createMap(MapActivity.java:552)
at com.google.android.maps.MapActivity.onCreate(MapActivity.java:422)
at xx.yyy.zzzz.MyMapActivity.onCreate(MyMapActivity.java:41)
at xx.yyy.zzzz.TheMap.onCreate(TheMap.java:89)
at android.app.Activity.performCreate(Activity.java:4465)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1920)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
at android.app.ActivityThread.access$600(ActivityThread.java:123)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1147)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)

What is this?

Thanks in advance.

Harald Wilhelm
  • 6,656
  • 11
  • 67
  • 85

3 Answers3

20

I spent some time looking into this problem, and I'm documenting what I've found here in the hopes that it saves other people some trouble.

The error is the result of a device manufacturer or ROM creator using an older maps library with a new version of Android. Typically, this is isolated to obscure tablets, but it could theoretically appear in other situations. It's possible to recreate this problem in an emulator using following these steps:

  1. Create and load an emulator for an older API (10 or less) which includes the Google APIs
  2. Extract the maps jar from the emulator: adb pull /system/framework/com.google.android.maps.jar <destination_folder> You can close the emulator once you've done this.
  3. Create and load an emulator for a new API (11 or above) which includes the Google APIs
  4. Remount /system in the emulator so you can write to it: adb remount
  5. Put the extracted maps jar into the new emulator: adb push <destination_folder>/com.google.android.maps.jar /system/framework
  6. Reboot the emulator. This is supposed to be doable by adb reboot but that just hung the emulator. Instead, you'll need to kill a particular process which has the same effect. In Eclipse/DDMS it will be called system_process and you can kill it there. Alternatively, you can run this command:adb shell ps | grep system_server | awk '{print $2}' | xargs adb shell kill
  7. After reboot you can use the emulator as you normally would. Running any app with an embedded Google map will fail.

This process isn't permanent. Restarting the emulator reverts it to its normal working state.

It's possible to detect this problem by getting the KeyHelper.getSignatureFingerprint() method in the maps library via reflection and invoking it - passing a PackageManager and your package name as arguments. Alternatively, you can trap the error in onCreate() and load a new activity instead.

blazeroni
  • 8,240
  • 1
  • 20
  • 13
  • 1
    I agree with the explanation. I only have this problem with "not very standard" devices. – lujop Nov 17 '12 at 22:51
9

The MessageDigest class is a helper class used to encode/decode keys, using common methods such as MD5 or SHA-1.

It seems that the class android.security.MessageDigest was removed from Honeycomb and later releases of Android, and must be replaced by java.security.MessageDigest (see this page)

Try downloading the latest version of the Google Maps API and rebuild your application with targetSDK set to the highest available (as of today it should be 16 / Jelly Bean).

XGouchet
  • 10,002
  • 10
  • 48
  • 83
  • 1
    Thanks. On 4.x Devices the same app works perfect, on 2.x Devices too. Did Google remove that API call in 3.x and re-add it in 4.x? Whow! – Harald Wilhelm Jul 10 '12 at 08:30
  • Well that would be strange. My best guess would be to contact Google directly to have an official info on this – XGouchet Jul 10 '12 at 09:08
  • 1
    I do own a 2.3.7 device and a 4.0.3 device - both work. I can surely confirm that. The error comes from an unknown customers stacktrace in the Developer Console. I usually check them all - even if it's code outside of my source. I mark your answer as ok because you directed me to the correct location. – Harald Wilhelm Jul 10 '12 at 14:27
  • If targeting API 16 the problem is solved? I'm only getting that error from some users with "strange devices" (at least not common ones ATROPLUS, Scroll engage, Fly touch,... – lujop Oct 19 '12 at 20:58
  • I set target api to 17, but still receive this exception on some devices! – Nik Feb 22 '13 at 06:26
  • This crash is only happening to me on OS 4.0.3, 4.0.4, 4.1.5 and 4.2.1 (sample size >250,000 for an OS 2.3+ app). – Dan J Mar 14 '13 at 16:03
9

I have found simple work around! Just create in src directory package android\security and place MessageDigest.java inside.

package android.security;

import java.security.NoSuchAlgorithmException;

public class MessageDigest
{
    private java.security.MessageDigest instance;

    public MessageDigest() {}

    private MessageDigest(java.security.MessageDigest instance)
    {
        this.instance = instance;
    }

    public static MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException
    {
        if (algorithm == null) return null;

        try
        {
            if (algorithm.equals("SHA-1"))
                return (MessageDigest) Class.forName("android.security.Sha1MessageDigest").newInstance();
            else if (algorithm.equals("MD5"))
                return (MessageDigest) Class.forName("android.security.Md5MessageDigest").newInstance();
        }
        catch (Exception e) {}

        return new MessageDigest(java.security.MessageDigest.getInstance(algorithm));
    }

    public void update(byte[] input)
    {
        instance.update(input);
    }

    public byte[] digest()
    {
        return instance.digest();
    }

    public byte[] digest(byte[] input)
    {
        return instance.digest(input);
    }
}

It is works, but may accrue other exceptions because of map library not match android version!

Nik
  • 7,114
  • 8
  • 51
  • 75
  • This workaround actually works and is the most non-intrusive. I have not seen any side effects with this yet. If I run into any I will keep you posted. Thanks for the suggestion!!!!! – openmobster Mar 06 '13 at 06:07