Android: "Загрузчик классов может завершиться ошибкой для процессов, в которых размещено несколько приложений"

Что означает это сообщение в Eclipse LogCat для Android?

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());

К сожалению, нет никакого контекста относительно этого предупреждения, поэтому я не знаю, что вызывает эту проблему и как я могу решить ее.

2 ответа

Решение

Исходная информация

Сообщение означает, что Android настроил пустышку ClassLoader с Thread.currentThread().setContextClassLoader()и что-то пытается использовать этот фиктивный загрузчик классов. Что- то может быть много вещей, трудно сказать точно, что из предоставленной информации. Есть трюк, который вы можете попробовать, см. Ниже. В любом случае, Android настраивает загрузчик фиктивных классов, когда есть риск, что процесс может содержать код из более чем одного APK. В частности, Android выглядит в вашем манифесте, если вы использовали android:sharedUserId:

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

или если вы работаете в нестандартном android:process

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

Как избавиться от предупреждения

Есть две вещи, которые вы можете сделать, чтобы избавиться от предупреждения:

  1. Не использовать android:sharedUserId или же android:process
  2. Явно установить, что APK ClassLoader использовать перед запуском любого другого кода

Чтобы перейти к решению 2, вам понадобятся некоторые ключевые идеи. Во-первых, для любого класса AnyClass в APK, AnyClass.class.getClassLoader() вернет то же самое ClassLoader, Во-вторых,

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

такой же как

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

В-третьих, вам нужно позвонить Thread.currentThread().setContextClassLoader(getClass().getClassLoader()) перед кодом, который вызывает Thread.currentThread().getContextClassLoader(), В-четвертых, когда участвует много APK, вам нужно позвонить Thread.setContextClassLoader(getClass().getClassLoader()) после загрузки последнего APK (иначе загрузка последнего APK перезапишет то, что вы установили вручную). Из-за этого было бы неплохо выяснить, кто использует загрузчик класса контекста в вашем случае, используя приведенный ниже прием отладки. Затем, прямо перед этим, вы звоните Thread.setContextClassLoader(getClass().getClassLoader()) для класса из требуемого APK, как правило, APK, который загружается первым (или, в случае, когда задействован только один APK, этот APK;). В-пятых, загрузчик класса контекста для каждого потока, который вы должны иметь в виду, если ваше приложение является многопоточным.

Трюк отладки

Если вы хотите узнать, какой код вызывает ClassLoader.getResources(), это должно сработать:

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);
    }
});

если вы делаете это достаточно рано, вы должны увидеть в logcat трассировку стека, которая возвращается к тому, кто вызывает getResources() на манекене загрузчик класса.

Я получил это предупреждение без android:sharedUserId или android:process в моем манифесте...

Нашел только представленный на эмуляторе...

Устройства не представили предупреждающее сообщение. Протестировано на Smartphone KitKat 4.4 API 19 и Tablet 5.0.1 API 21.

Другие вопросы по тегам