Для взаимодействия с 32-разрядной COM библиотекой через jni4net понадобилось, чтобы приложение запускалось с помощью 32-разрядной JDK. Скачал, поставил, и напоролся на то, что установленная 32-битная версия 1.7_25 полностью заменила все имеющиеся JDK, закинув exe-файлы java.exe, javaw.exe прямо в Windows\System32. А мне хотелось, чтобы по умолчанию работала 64-разрядная версия. И только в определённых приложениях запуск производился с помощью 32-битной JVM. Нашел следующее решение:
- Удаляем из System32 три exe файла, появившихся после инсталляции 32-битной JDK
- Устанавливаем JAVA_HOME на 64-разрядную JDK. В Path прописываем путь к директории bin этой же 64-разрядной JDK. Это будет JDK по умолчанию.
- В приложениях, которые требуют запуска с использованием недефолтной JDK, прописываем следующие строки в скрипт запуска:
set JAVA_HOME=путь к JDK
set PATH=%JAVA_HOME%\bin;%PATH%
Вот и всё! Теперь у нас в приложениях, где этих строк нет, будет запускаться дефолтная версия JVM, а в приложениях, которые мы сконфигурировали явно, будет стартовать именно та версия, которую мы прописали.
Озаботился поиском библиотеки для связывания данных в java, чтобы использовать в программировании UI компонентов, наподобие того, как это можно делать в WPF. Нашел библиотеку http://databinding.tornado.no/, попробовал и нашел множество проблем, хотя на первый взгляд она показалась мне вполне подходящей. Проблем оказалось настолько много, что я специально решил их все выписать, чтобы потом при разработке библиотек сверяться с этим чеклистом и проверять свои проекты на соответствие желаемому уровню качества. Итак, поехали.
- Нет комментариев.
С этим всё понятно. Если код библиотеки, пусть даже очень небольшой, не содержит комментариев хотя бы к классам и паблик методам, то сразу возникает вопрос о возможности применять эту библиотеку. - Нет репозитория с кодом на одном из хостингов кода
Нет единой точки входа для того, чтобы скачать последнюю версию исходников, форкнуть или предложить патч. - Нет лицензии
Не понятно, можно ли использовать код в своих приложениях. - Нет unit тестов
Нет гарантии того, что код в основных сценариях работает корректно. - Ошибка при вызове StatusMonitor’a в валидации – clearStatus никогда не вызывается. Вместо него вызывается setStatus(null);
Это уже первая реальная ошибка. Были бы юнит тесты – этой ошибки бы не было. - Поддерживается только связывание с UI компонентами, нельзя связать 2 произвольных объекта.
Это чисто моя хотелка насчет байндинга. - UI bridges поддерживают только 1 проперти, что делать, если хочется связаться с UI компонентом, но по другому свойству?
Тоже хотелка. - Проглатывание исключений в вызове валидатора
Подозрительный код, здесь могут возникнуть трудноотлаживаемые проблемы. - Не поддерживаются свойства с примитивными типами
Печаль. - Неочевидные комбинации режимов ONCHANGE, ONBLUR итд (можно установить отдельно режимы для байндинга и для валидации/конвертации, например)
Проблема плохо продуманного АПИ. - Невозможно получить результат валидации и конвертации отдельно, приходит просто строка
То же самое. - Неудобный интерфейс StatusMonitor
Аналогично. Необходимо реализовывать 2 метода, хотя вполне достаточно и одного. - При связывании происходит связь только из source в UI. То есть состояние валидации не инициализируется. Нужно явно вызывать flushUiToModel().
Ещё одно следствие отсутствия юнит тестов. - Установка model.prop = null не всегда приводит к очистке UI. Бага связана с наличием валидатора и с тем, проходит ли текущее значение валидацию. Хотя по идее от этого не должно зависеть вообще никак.
И еще одно. - Нет возможности отладить какой-то байндинг (включить логирование например).
- Непонятные зависимости от commons-beans и транзитивно еще от commons-logging и commons-collections.
Зачем мне дополнительные 3 джарника для маленькой либы в 40 килобайт кода ? - Байндинг в сторону модели ВООБЩЕ не работает если задан валидатор ! Просто блокер бага.
Без комментариев. - Нет поддержки локализации сообщений об ошибках в конверторах и валидаторах.
Это можно было бы пофиксить патчем, но форкнуть код невозможно. А создавать свой отдельный репозиторий не хочется. - Непонятно, как связывать списки.
Ещё одна очевидная хотелка.
.. если завершено оно было операционной системой по причине недостатка памяти. В этом случае приложение стартует с последней бывшей видимой activity. Зачем это нужно ? Чтобы убийство процессов было прозрачно для пользователя. Рассмотрим типичную ситуацию: у вас открыто приложение, и в этот момент вам позвонили. Если вы после ответа на звонок обнаружите своё приложение заново запускающимся, вас это не обрадует. В общем, нам надо научиться поддерживать это поведение, так чтобы можно было стартануть приложение с любой Activity. А для того, чтобы адаптироваться к таким вещам, нужно эти вещи уметь воспроизводить. В моём случае сценарий воспроизведения такой:
- Открываем наше приложение, переходим со стартовой Activity на тестируемую
- Переходим в окно с уведомлениями (которое выползает сверху) и нажимаем на значок настроек
Этим мы переходим в системное приложение настроек, которое закрывает наше приложение - Убиваем процесс командой
ps | grep su.elwood | awk '{print $2}' | xargs kill
( su.elwood – имя пакета вашего приложения )
Это можно сделать либо в консоли adb -d shell, либо по ssh, если он у вас, конечно, установлен
Также понадобится root (в adb shell перед выполнением команды нужно выполнить команду su). - Нажимаем кнопку Back на телефоне
Обязательно нужно, чтобы какое-то другое приложение закрывало наше, иначе ОС подумает, что программа упала сама, и при перезапуске стартанёт приложение с основной Activity.
Теперь мы научились воспроизводить это поведение, и можем протестировать наше приложение, пробуя запускать его со всех доступных Activities. Осталось только решить, как обрабатывать такие ситуации программно. Здесь я могу отметить, что помимо введения флагов в некоторое глобальное состояние, можно еще абузить особенность андроида, состоящую в том, что при таком перезапуске ОС запомнит стек Activities, который был на момент остановки процесса. И если при перезапуске с “неверной” Activity вы сделаете finish(), то будет создан экземпляр Activity, которая лежала под ней в исходном процессе (если таковая, конечно, была).
1