21

What does this message in Eclipse's logcat for Android mean?

W/ActivityThread: ClassLoader.getResources: The class loader returned by Thread.getContextClassLoader() may fail for processes that host multiple applications. You should explicitly specify a context class loader. For example: Thread.setContextClassLoader(getClass().getClassLoader());

Unfortunately, there is no context given as to this warning, so I don't know what causes this problem and how I can resolve it.

caw
  • 30,999
  • 61
  • 181
  • 291

2 Answers2

35

Background information

The message means that Android has setup a dummy ClassLoader with Thread.currentThread().setContextClassLoader(), and something tries to use that dummy class loader. The something can be a lot of things, it's hard to tell exactly what from the information given. There is a trick you can try though, see below. Anyway, Android sets up the dummy class loader when there is a risk that the process might contain code from more than one APK. More specifically, Android looks in your manifest if you have used android:sharedUserId:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:sharedUserId="triggers.dummy.loader" >

or if you run in a non-standard android:process

    <application android:process="triggers.dummy.loader">

How to get rid of the warning

There are two things you can do to get rid of the warning:

  1. Don't use android:sharedUserId or android:process
  2. Explicitly set what APK ClassLoader to use before running any other code

To go with solution 2, there are some key insights you need. First, for any class AnyClass in an APK, AnyClass.class.getClassLoader() will return the same ClassLoader. Second,

AnyClass obj = new AnyClass();
Thread.currentThread().setContextClassLoader(obj.getClass().getClassLoader())

is the same as

Thread.currentThread().setContextClassLoader(AnyClass.class.getClassLoader())

Third, you need to call Thread.currentThread().setContextClassLoader(getClass().getClassLoader()) before the code that calls Thread.currentThread().getContextClassLoader(). Fourth, When many APKs are involved, you need to call Thread.setContextClassLoader(getClass().getClassLoader()) after the last APK has been loaded (otherwise loading the last APK will overwrite what you have set manually). Because of that, it would be a good idea to find out who is using the context class loader in you case by using the below debug trick. Then, right before that, you call Thread.setContextClassLoader(getClass().getClassLoader()) for a class from the desired APK, typically the APK that is loaded first (or, in the case when only one APK is involved, that APK ;). Fifth, the context class loader is per thread, which you need to keep in mind if your application is multi-threaded.

Debug trick

If you want to find out what code that calls ClassLoader.getResources(), this should work:

Thread.currentThread().setContextClassLoader(new ClassLoader() {
    @Override
    public Enumeration<URL> getResources(String resName) throws IOException {
        Log.i("Debug", "Stack trace of who uses " +
                "Thread.currentThread().getContextClassLoader()." +
                "getResources(String resName):", new Exception());
        return super.getResources(resName);
    }
});

if you do this early enough, you should see in the logcat a stack trace that goes back to whoever calls getResources() on the dummy class loader.

Martin Nordholts
  • 10,338
  • 2
  • 39
  • 43
  • Thank you very much! I've set `sharedUserId`, so we've already found the problem :) But can you say that, as long as I don't really use the `sharedUserId`, everything is fine? I've set it already because you cannot set it after you have released versions without it. – caw Nov 19 '12 at 14:57
  • Right, as long as you don't need `sharedUserId`, you should not set it. If you set it after you've made releases, you might bump into application data migration issues, but you are likely to bump into application data migration issues anyway if you set it in advance since you don't know what value to set. – Martin Nordholts Nov 19 '12 at 15:44
  • Of course, you can just set it to your package name ;) Then, when you need it in another apk, just set it to that package name as well. This way, you won't have any data migration issues. The problem is: You have not only some migration issues but serious exceptions (app cannot find its own data anymore). I was just asking: If I set it yet but don't use it, can I ignore the class loader warning? – caw Nov 19 '12 at 17:11
  • Aha you meant like that. Well, you get the warning because something is using `getContextClassLoader()`. If that something needs access to your APK specific classes and resources, it currently can't access those. Even if nothing _currently_ needs access to your APK specific classes and resources, that need might arise in the future. So if you want to keep using `sharedUserId`, I would recommend you to go for solution 2 even though it might not be needed now, because it might be needed in the future (when you have forgot about this warning). – Martin Nordholts Nov 20 '12 at 07:27
  • Thank you! But if external access to my apk is not needed yet, how can I use `setContextClassLoader`? I can only use this as soon as I have a second APK with the same `sharedUserId` that wants access to my first APK, right? And how to use it? Just call `Thread.setContextClassLoader(getClass().getClassLoader())` before the code that accesses the other APK's methods? – caw Nov 20 '12 at 16:53
  • Quite the contrary, it is when a second APK is involved that it becomes tricky to use `setContextClassLoader` properly, because then you have two class loaders to choose from. With one APK, there is only one class loader you can set. I have updated my answer with some sample code and comments. I hope it helps more than it confuses... – Martin Nordholts Nov 21 '12 at 08:07
  • Additional info: If you are using JSON-IO on Android, you have to write `Thread.currentThread().setContextClassLoader(getClass().getClassLoader());` before JsonReader.jsonToJava invocation. – Bevor Dec 08 '14 at 11:04
0

I received this warning without either android:sharedUserId or android:process in my Manifest...

Found it only presented on an emulator...

Devices did not present the warning message. Tested on Smartphone KitKat 4.4 API 19, and Tablet 5.0.1 API 21.

WM1
  • 451
  • 7
  • 14