Внезапно пропадает множество классов на Android 4 - PullRequest
0 голосов
/ 28 сентября 2018

Мое приложение работает нормально на Android 4.0.3 и выше в течение многих лет.С недавним небольшим обновлением он внезапно начал падать на устройствах Android 4.x.Когда я отлаживаю его на Android 4, он прекрасно собирается и устанавливается, но консоль получает множество ошибок, начиная с

W/dalvikvm: VFY: unable to find class referenced in signature (Landroid/media/midi/MidiDeviceInfo;)
I/dalvikvm: Failed resolving Lcom/arlomedia/myapp/App$4; interface 113 'Landroid/media/midi/MidiManager$OnDeviceOpenedListener;'
W/dalvikvm: Link of class 'Lcom/arlomedia/myapp/App$4;' failed
E/dalvikvm: Could not find class 'com.arlomedia.myapp.App$4', referenced from method com.arlomedia.myapp.App.addMidiPorts
W/dalvikvm: VFY: unable to resolve new-instance 447 (Lcom/arlomedia/myapp/App$4;) in Lcom/arlomedia/myapp/App;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0007

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

Я прочитал несколько постов с похожими проблемами, связанными с ограничением метода 64K в Android 4, и вполне возможно, что в последнее времяОбновление, я случайно перешагнул порог 64К.Поэтому я попытался настроить мультидекс в соответствии с этими инструкциями , но это не имеет значения, и я не знаю, как определить, работает ли он.Когда я собираю APK-файл и проверяю его содержимое, я вижу только один файл classes.dex.Это может означать, что моя мультидексная установка не работает, или у меня не более 64К методов, и моя проблема в другом.

Моя вторая теория заключается в том, что было использовано использование класса MidiDeviceInfo, который был добавленв Android 6 вызывает проблемы на Android 4. Однако я использую проверки версий и аннотации @TargetApi(24), чтобы игнорировать код MIDI на Android 4-5.И эта проблема не возникает в Android 5. И все же любопытно, что это первая ошибка, которая появляется каждый раз.

Правильно ли звучит одна из этих теорий или она напоминает какую-то другую проблему?

Кстати, что именно означает «неспособен найти класс, на который есть ссылка в сигнатуре» - сигнатура метода?У меня были некоторые методы, которые использовали аргументы типа MidiDeviceInfo, но я аннотировал все эти аргументы @TargetApi(24), и у меня нет проблем с компиляцией.

Обновление

Благодаря подсказке Гомермана, я вижу, что количество методов не является проблемой.Это возвращает меня к совместимости с MIDI, но я смотрю на различия в предыдущей версии и не вижу никаких изменений в коде MIDI.Что еще мне нужно искать?

Обновление 2

Я не смог найти ничего, что могло бы показаться релевантным в diff-файлах со времени последней версии, поэтому я проверилпоследняя версия из моего VCS и запустил его, и он работал нормально.Но я заметил в logcat для этой версии все те же ошибки для классов MIDI.Так что, похоже, это не является частью проблемы.Я думаю, это то, что делает Android, когда работает в новых классах.Я продолжал сравнивать logcats между последней хорошей версией и текущей версией, и затем я увидел разницу: недавно я добавил OnScrollChangeListener в один из моих классов.Это породило еще одну группу ошибок отсутствующих классов, которых не было в последней версии приложения.И когда я удаляю эту новую функциональность, приложение снова работает нормально на Android 4.

Так вот в чем проблема: у меня есть определение класса, подобное этому:

public class DocumentViewer extends RelativeLayout implements View.OnScrollChangeListener {

    if (Build.VERSION.SDK_INT >= 23) {
        textView.setOnScrollChangeListener(this);
    }

    @Override
    public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        Log.d("onScrollChange", "scroll changed: " + scrollY);
    }

}

Видимо, наличиеView.OnScrollChangeListener в определении класса - это то, что все портит.Есть ли способ объявить это только для поддерживаемых версий Android?

И, очевидно, Android 5 изящно игнорирует эту неподдерживаемую ссылку, а Android 4 - нет.

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Ответ Бен П поставил меня на правильный путь, но я запутался в преобразовании своего класса в интерфейс, поэтому вот что я придумал:

// this was the original class and I added scrollViewDidScroll() to keep all my related code in one class
public class DocumentViewer extends RelativeLayout {

    if (Build.VERSION.SDK_INT >= 23) {
        if (this instanceof DocumentViewerExtras) {
            // assign the listener on supported devices
            DocumentViewerExtras thisExtras = (DocumentViewerExtras)this;
            textView.setOnScrollChangeListener(thisExtras);
        }
    }

    public void scrollViewDidScroll(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        // do something with the scroll results
    }

}

// this is a new class that adds OnScrollChangeListener on supported devices
@RequiresApi(23)
public class DocumentViewerExtras extends DocumentViewer implements View.OnScrollChangeListener {

    public DocumentViewerExtras(Fragment fragment) {
        super(fragment);
    }

    @Override
    public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        this.scrollViewDidScroll(scrollView, scrollX, scrollY, oldScrollX, oldScrollY);
    }

}

// this method instantiates the best available class
public DocumentViewer getDocumentViewer(Fragment fragment) {
    if (Build.VERSION.SDK_INT >= 23) {
        return new DocumentViewerExtras(fragment);
    } else {
        return new DocumentViewer(fragment);
    }
}

// this is how I get a new instance of the class
DocumentViewer documentViewer = getDocumentViewer(this);
0 голосов
/ 28 сентября 2018

Интерфейс View.OnScrollChangeListener был добавлен на уровне API 23. Это означает, что определение вашего класса уже является проблемой;если этот класс будет загружен до 23, то произойдет сбой.

Решение состоит в том, чтобы создать общего родителя (возможно, интерфейс) и две конкретные реализации этого родителя: одну для pre-23 и одну для23+.Затем используйте фабрику, чтобы получить соответствующую реализацию, основанную на работающем в данный момент устройстве.

public interface DocumentViewer {
    // interface methods
}
public class DocumentViewPre23 extends RelativeLayout implements DocumentViewer {
    // interface methods
}
public class DocumentViewer23 extends RelativeLayout implements DocumentViewer, View.OnScrollChangeListener {
    // interface methods
}

И тогда у вас может быть что-то вроде

public static DocumentViewer getDocumentViewer() {
    if (Build.VERSION.SDK_INT >= 23) {
        return new DocumentViewer23();
    } else {
        return new DocumentViewerPre23();
    }
}

Androidбиблиотека поддержки делает это довольно часто, для чего это стоит.Вот пример из ActionBarDrawerToggle, который переключается между двумя реализациями на основе версии API:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
    mActivityImpl = new JellybeanMr2Delegate(activity);
} else {
    mActivityImpl = new IcsDelegate(activity);
}
public interface Delegate {
    ...
}
@RequiresApi(18)
private static class JellybeanMr2Delegate implements Delegate {
    ...
}
private static class IcsDelegate implements Delegate {
    ...
}
...