Ответ 1
TL; DR: Исключения являются исключительными. Не удается поймать исключение, тип которого неизвестен.
Следующее - это большинство предположений, основанных на моих ограниченных знаниях о Java/Dalvik и здравом смысле. Возьмите его с солью.
Я нашел метод, который выплевывает неудачную строку журнала и подтвердил большинство моих упомянутых предположений, см. Дополнительные ссылки ниже.
Кажется, ваша проблема заключается в том, что классы загружаются сразу, либо весь класс загружен, либо ни один из них. Проверка выполняется сначала, я предполагаю, что для предотвращения некоторых проверок времени выполнения (помните, что Android ограничен ресурсами).
Я использовал следующий код:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21(@NonNull final String language, @NonNull final String region) {
try {
// try some API 21 stuff
new Locale.Builder().build().getDisplayVariant();
} catch (final IllformedLocaleException ex) {
ex.printStackTrace();
}
return 0;
}
Когда система пыталась создать экземпляр класса, содержащего этот метод, произошло следующее:
E/dalvikvm: Не удалось найти класс 'java.util.Locale $Builder', на который ссылается метод com.test.TestFragment.setVoice21
Загрузка класса Locale.Builder
будет ClassNotFoundException
.
W/dalvikvm: VFY: невозможно разрешить новый экземпляр 5241 (Ljava/util/Locale $Builder;) в Lcom/test/TestFragment;
D/dalvikvm: VFY: замена кода операции 0x22 на 0x0000
Затем в этом несуществующем классе он попытается вызвать метод <init>
, который предотвращается путем замены OP_NEW_INSTANCE
с помощью OP_NOP
. Я думаю, что это было бы выживаемо, поскольку я все время вижу это при использовании библиотеки поддержки. Я считаю, что предположение состоит в том, что если класс не найден, он должен быть защищен с помощью проверки SDK_INT
. Кроме того, если он прошел через dexing/proguard и другие вещи, он, должно быть, был преднамеренным, а ClassNotFoundException
допустим во время выполнения.
W/dalvikvm: VFY: невозможно разрешить класс исключения 5234 (Ljava/util/IllformedLocaleException;)
Еще один проблемный класс, обратите внимание на этот раз "класс исключения", который должен быть особенным. Если вы проверите байт-код Java для этого метода с помощью:
javap -verbose -l -private -c -s TestFragment.class > TestFragment.dis
public int setVoice21(java.lang.String, java.lang.String);
...
Exception table:
from to target type
0 14 17 Class java/util/IllformedLocaleException
LocalVariableTable:
Start Length Slot Name Signature
18 11 3 ex Ljava/util/IllformedLocaleException;
0 31 0 this Lcom/test/TestFragment;
0 31 1 language Ljava/lang/String;
0 31 2 region Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 81 /* same_locals_1_stack_item */
stack = [ class java/util/IllformedLocaleException ]
frame_type = 11 /* same */
Вы действительно можете видеть, что все Exception table
и StackMapTable
и LocalVariableTable
содержат проблемный класс, но не Locale$Builder
. Это может быть связано с тем, что строитель не хранится в переменной, но точка, которую нужно извлечь, заключается в том, что исключения обрабатываются специально и получают больше контроля, чем обычные строки кода.
Использование BakSmali на APK через:
apktool.bat d -r -f -o .\disassembled "app-debug.apk"
.method public setVoice21(Ljava/lang/String;Ljava/lang/String;)I
.prologue
:try_start_0
new-instance v1, Ljava/util/Locale$Builder;
invoke-direct {v1}, Ljava/util/Locale$Builder;-><init>()V
...
:try_end_0
.catch Ljava/util/IllformedLocaleException; {:try_start_0 .. :try_end_0} :catch_0
...
:catch_0
move-exception v0
.local v0, "ex":Ljava/util/IllformedLocaleException;
invoke-virtual {v0}, Ljava/util/IllformedLocaleException;->printStackTrace()V
Похоже, что обнаруживает аналогичную структуру, здесь мы действительно можем увидеть op-коды, упомянутые в журнале. Обратите внимание, что .catch
представляется специальной инструкцией, а не операцией, поскольку ей предшествует точка. Я думаю, что это усиливает внимание, упомянутое выше: это не операция времени выполнения, но требуется, чтобы класс загружал код, содержащийся в методах.
W/dalvikvm: VFY: невозможно найти обработчик исключений в addr 0xe
W/dalvikvm: VFY: отклонено Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String; Ljava/lang/String;) I
Я предполагаю, что это означает, что он не смог восстановить, когда вызывать блок catch
из Exception table
и StackMapTable
, потому что он не мог найти класс для определения родительских классов. Это подтверждено в getCaughtExceptionType
, где "неспособность разрешить класс исключений" напрямую приводит к "неспособности найти обработчик исключений", потому что не находит общего супер класс для несуществующего исключения, что-то вроде } catch (? ex) {
, поэтому он не знает, что поймать.
W/dalvikvm: VFY: отклонение кода операции 0x0d на 0x000e
W/dalvikvm: VFY: отклонено Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String; Ljava/lang/String;) I
Я думаю, что в этот момент верификатор просто сдался, потому что он не мог понять OP_MOVE_EXCEPTION
. Это подтверждается тем, что метод getCaughtExceptionType
используется только в одном месте, переключатель. Из-за этого мы получаем "отказ от кода операции" , а затем goto bail
до стека вызовов до "отклоненный класс" . После исправления кода ошибки был VERIFY_ERROR_GENERIC
, который отображается на VerifyError
. Не удалось найти, где вызывается фактическое исключение JNI, если оно даже работает таким образом.
W/dalvikvm: Verifier отклонил класс Lcom/test/TestFragment;
Множественные отклонения были поданы против метода setVoice21
, и, следовательно, весь класс должен быть отклонен (это кажется мне суровым, возможно, в этом отношении АРТ отличается).
W/dalvikvm: сбой класса init в вызове newInstance (Lcom/test/TestFragment;)
D/AndroidRuntime: выключение виртуальной машины
W/dalvikvm: threadid = 1: выход с отключенным исключением (группа = 0x41869da0)
E/AndroidRuntime: FATAL EXCEPTION: главная
Процесс: com.bumptech.glide.supportapp.v3, PID: 27649
java.lang.VerifyError: com/test/TestFragment
Я предполагаю, что это похоже на ExceptionInInitializerError
в настольном Java, который вызывается, когда тело static { }
в классе класса или инициализатор статического поля генерирует RuntimeException
/Error
.
Почему instanceof
работает
Использование обходного пути razzledazzle изменяет эти таблицы на java/lang/Exception
и перемещает зависимость до IllformedLocaleException
в код, который будет выполняться во время выполнения:
0 14 17 Class java/lang/Exception
19: instanceof #34 // class java/util/IllformedLocaleException
и аналогичным образом Smali:
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;
E/dalvikvm: Не удалось найти класс 'java.util.IllformedLocaleException', на который ссылается метод com.test.TestFragment.setVoice21
W/dalvikvm: VFY: невозможно разрешить экземпляр 5234 (Ljava/util/IllformedLocaleException;) в Lcom/test/TestFragment;
Теперь, та же самая жалоба, что и для Locale$Builder
выше
D/dalvikvm: VFY: замена кода операции 0x20 на 0x000f
Замена OP_INSTANCE_OF
с? что-то?, он не говорит:)
Еще одно возможное обходное решение
Если вы посмотрите на классы android.support.v4.view.ViewCompat*
, вы заметите, что не все эти классы используются во всех версиях. Правильный выбирается во время выполнения (поиск static final ViewCompatImpl IMPL
в ViewCompat.java
), и только это загружается. Это гарантирует, что даже во время загрузки класса не будет какой-либо странности из-за отсутствия классов и будет выполняться. Вы можете создать аналогичную архитектуру, чтобы предотвратить загрузку этого метода на более ранних уровнях API.