Android: "Class loader may fail for processes that host multiple applications"

android, classloader, multithreading

Solution

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:

- Don't use `android:sharedUserId` or `android:process`

- 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.

Problem

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.

Original source