Снятся ли андроидам электро-грабли?
2014-09-08 11:57 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Я тут потратил несколько вечеров, чтобы написать програмку под андроид. Очень простую (3 активности, пять кнопочек, два calendar view, одна база sqlite). Остаток поста - о том, какие грабли мне попались по пути, и кто, интересно, их там разложил?
Кто пишет под андроид - может почитать и поржать. Кто не пишет, но собирается - может почитать и подумать, так ли оно ему надо? Кто пишет под iOS - может почитать и позлорадствовать, что с Apple все по-другому.
Full disclosure: последний раз я что-то делал на Java четыре года тому назад, и каждый раз, когда у меня возникали какие-то проблемы, я шел прямиком в google и искал по характерным ключевым словам. Я намерено не собирался читать толстых фолиантов типа "The Absolute Definite Android Programming Guide and Reference", т.к. книг много, а хороших книг - мало, да и те быстро устаревают. Так что только гугл + stackoverflow + developer.android.com - такой себе "тяп-ляп и в продакшн".
Первый звоночек был сразу после установки Android Studio. Оказывается, сразу после установки надо сделать определенные действия - открыть SDK manager и скачать все, что оно посчитает нужным. Сама студия об этом молчит - нет, чтобы показать что-то такое при первом запуске. Информация об этом показывается на странице, с которой ты скачиваешь студию, но появляется она там через сколько-то секунд после начала скачивания. Я к этому времени эту страницу уже благополучно закрыл :)
В результате ты рисуешь интерфейс своего первого приложения в графическом дизайнере гуя, и все прекрасно и замечательно, пока ты не добавляешь туда тривиальное поле для ввода текста. И тут твой красивый гуй пропадает, а вместо него появляется загадочная надпись "java.lang.system.arraycopy(ci cii)v". Можно легко проверить, что интернет полон страдальцев, бьющихся головой об эту надпись, а ларчик открывается просто - пока вы не запустите SDK manager, и не накачаете себе всякого разного добра, которое вам предложат по умолчанию, у вашей студии будет только один вариант SDK, который она и будет использовать. Это SDK для Android Wear, то бишь для часов. И куча элементов интерфейса для них просто "не бывает", и вот это самый exception - это способ сообщить пользователю об этом. Я было думал, что это в новой студии такие косяки, а в старом добром эклипсе все ок, но разведчики доносят, что в эклипсе - точно такая же фигня. Ладно, запустил SDK Manager, скачал все что надо, поехали дальше. (ссылка на StackOverflow)
Пару простеньких примеров с developer.android.com собрались, но при попытке запустить их в эмуляторе я обнаружил, что эмулятор запускается через раз. Опытным путем выяснилось, что если попросить "use snapshot", то эмулятор работает, а без этой опции - нет. При помощи strace и такой-то матери было выяснено, что опция "use snapshot" несовместимо с использованием опции "use opengl rendering", и включая-выключая "snapshot" я фактически включал-выключал opengl. А с ним проблемы, если у тебя JDK 7 или выше, linux, используется emulator-arm и луна - в первой четверти. У меня был именно такой JDK, linux, и луной судя по всему тоже повезло, т.к. эмулятор с opengl у меня так и не завелся. Ладно, буду запускать с отключенным, поехали дальше (ссылка на отдаленно имеющих отношение к делу баг, который помог понять, что это в принципе может быть).
Для разминки я решил написать приложение, которое трекает даты. Знаете такие таблички типа "Уже X дней работаем без происшествий"?. Ну вот, чтобы можно было туда вводить даты, оно показывало, сколько дней прошло с последней введенной даты, и можно было посмотреть историю - какие даты вводились раньше и сколько дней между ними прошло. Как раз для разминки - не очень просто, но и не очень сложно.
И вот я делаю в своем приложении MainActivity, у которого в layout есть кнопка, давишь на нее - открывается CalendarActivity, в котором CalendarView, чтобы можно было выбрать дату. И тут у меня начинаются открытия - одно за другим, только успевай записывать.
Во-первых, у CalendarView рекомендуется повесить обработчик на событие onSelectedDateChange, но вот незадача - текущий день уже selected, и сделать так, чтобы никакой день не был selected - нельзя. Но чтобы разработчик не скучал, сделано вот что - если взять и поскроллить календарик (не меняя выбранную дату), то ВНЕЗАПНО выстрелит событие selectedDateChange, возможно даже несколько раз. Интернеты предлагают в обработчике события "изменилась дата" проверять, РЕАЛЬНО ли изменилась дата, и таким образом понимать, изменил пользователь дату или просто поскроллил календарь. Я спасовал против этой логики, выкинул обработчик onSelectedDateChange, и добавил позорную кнопку "Ok", на которую пользователь должен нажать, чтобы подтвердить выбор даты (ссылка на StackOverflow). Тут активность с календарем стала открываться по 10 секунд, но добрая студия подсказала мне, что не надо делать календарю layoutHeight=wrap_content, если другие атрибуты говорят "отдай календарю все, что осталось от других элементов интерфейса".
Далее выяснилось, что внешний вид календаря более-менее прибит гвоздями - текущая неделя всегда выделяется другим цветом фона, а у текущей даты слева-справа от числа будут две "палочки", но это и все. Как сделать у выбранной даты другой цвет фона я так и не нашел, и таких страдальцев, опять же, полон интернет, и всем им советуют - "просто возьмите другой third-party календарь". Теперь я по крайней мере понял, почему во всех приложениях с календарями эти календари разные.
Ладно, я решил, что буду жить со стандартным CalendarView - по крайней мере, пока. Скомпилировал свой пример, поставил на свой телефон, открыл и увидел календарик с мааааахонькими циферками - намного меньшими, чем остальной текст. Как оказалось, у меня на телефоне Android 4.1, а в нем CalendarView поломали - отрисовка дат происходит без использования задаваемого пользователем (или темой) размера шрифтам. В 4.2 уже починили, в 4.0 еще не поломали, а у меня - вот так. Стало еще более понятно, почему все любят кастомные календари. (ссылка на StackOverflow - там видно, как это выглядит).
Ура, теперь у меня работает выбор даты. Дальше я добавил класс для работы с базой SQLite, создал там табличку для хранения дат - все "по учебникам". Даты выбираются, даты сохраняются - красота.
Пришло время считать интервалы между датами. Тут у меня был хитрый план - есть база, sqlite умеет нормально исполнять достаточно сложные запросы, поэтому почему бы не посчитать почти все, что нужно, силами SQL-запроса:
Засовываю я этот запрос в db.rawQuery("..."), и получают ошибку во время исполнения - "column not found: the_date". Как же как unknown, вот же она! Неа, говорит мне какая-то библиотека из дебрей андроидного SDK, ты мужик меня не обманешь - раз я сказала "нету", значит нету. Как назло, текст ошибки такой, что в гугле находится куча всего постороннего, и 100500 несчастных, которые реально указали не то имя колонки. Но у меня-то в sqlite3 все работает, дело точно в чем-то другом. Попробовав и так и сяк я плюнул и решил, что просто сделаю view, и буду запрашивать данные уже оттуда.
Добавил в метод создания базы вызов db.executeSql("CREATE VIEW AS ..."), и ... получил ту же самую ошибку! Оказалось, что библиотека для работы с sqlite в Android SDK пытается парсить все(!) запросы, которые ты собираешься выполнять. И когда запрос слишком сложный для нее, она валится с вот такой вот диагностикой. Я как-то могу понять, зачем это делать в rawQuery (чтобы получить имена колонок для Cursor-а), но зачем это делать в executeSql - я понять не могу.
Ладно, фиг с ним, посчитаем разницу вручную. Для работы с датами предлагается java.util.Calendar - что в нем есть для подсчета количества дней между датами? Быстрый просмотр доступных методов ничего не дал, и я пошел в гугл. И нашел вот такой ответ на StackOverflow. Настоятельно рекомендую сходить и почитать, памятуя о том, что на дворе у нас 21 век, эра победившего tzinfo и все такое прочее. Вот вам для затравки один из ответов оттуда:
Я, каюсь, был насколько впечатлен увиденным, что в результате тоже использовал позорное:
Короче говоря, приложение я написал, но первое впечатление об андроиде и его SDK у меня сложилось далеко не самое благоприятное. Рассказывайте теперь, как надо было делать правильно :)
Кто пишет под андроид - может почитать и поржать. Кто не пишет, но собирается - может почитать и подумать, так ли оно ему надо? Кто пишет под iOS - может почитать и позлорадствовать, что с Apple все по-другому.
Full disclosure: последний раз я что-то делал на Java четыре года тому назад, и каждый раз, когда у меня возникали какие-то проблемы, я шел прямиком в google и искал по характерным ключевым словам. Я намерено не собирался читать толстых фолиантов типа "The Absolute Definite Android Programming Guide and Reference", т.к. книг много, а хороших книг - мало, да и те быстро устаревают. Так что только гугл + stackoverflow + developer.android.com - такой себе "тяп-ляп и в продакшн".
Первый звоночек был сразу после установки Android Studio. Оказывается, сразу после установки надо сделать определенные действия - открыть SDK manager и скачать все, что оно посчитает нужным. Сама студия об этом молчит - нет, чтобы показать что-то такое при первом запуске. Информация об этом показывается на странице, с которой ты скачиваешь студию, но появляется она там через сколько-то секунд после начала скачивания. Я к этому времени эту страницу уже благополучно закрыл :)
В результате ты рисуешь интерфейс своего первого приложения в графическом дизайнере гуя, и все прекрасно и замечательно, пока ты не добавляешь туда тривиальное поле для ввода текста. И тут твой красивый гуй пропадает, а вместо него появляется загадочная надпись "java.lang.system.arraycopy(ci cii)v". Можно легко проверить, что интернет полон страдальцев, бьющихся головой об эту надпись, а ларчик открывается просто - пока вы не запустите SDK manager, и не накачаете себе всякого разного добра, которое вам предложат по умолчанию, у вашей студии будет только один вариант SDK, который она и будет использовать. Это SDK для Android Wear, то бишь для часов. И куча элементов интерфейса для них просто "не бывает", и вот это самый exception - это способ сообщить пользователю об этом. Я было думал, что это в новой студии такие косяки, а в старом добром эклипсе все ок, но разведчики доносят, что в эклипсе - точно такая же фигня. Ладно, запустил SDK Manager, скачал все что надо, поехали дальше. (ссылка на StackOverflow)
Пару простеньких примеров с developer.android.com собрались, но при попытке запустить их в эмуляторе я обнаружил, что эмулятор запускается через раз. Опытным путем выяснилось, что если попросить "use snapshot", то эмулятор работает, а без этой опции - нет. При помощи strace и такой-то матери было выяснено, что опция "use snapshot" несовместимо с использованием опции "use opengl rendering", и включая-выключая "snapshot" я фактически включал-выключал opengl. А с ним проблемы, если у тебя JDK 7 или выше, linux, используется emulator-arm и луна - в первой четверти. У меня был именно такой JDK, linux, и луной судя по всему тоже повезло, т.к. эмулятор с opengl у меня так и не завелся. Ладно, буду запускать с отключенным, поехали дальше (ссылка на отдаленно имеющих отношение к делу баг, который помог понять, что это в принципе может быть).
Для разминки я решил написать приложение, которое трекает даты. Знаете такие таблички типа "Уже X дней работаем без происшествий"?. Ну вот, чтобы можно было туда вводить даты, оно показывало, сколько дней прошло с последней введенной даты, и можно было посмотреть историю - какие даты вводились раньше и сколько дней между ними прошло. Как раз для разминки - не очень просто, но и не очень сложно.
И вот я делаю в своем приложении MainActivity, у которого в layout есть кнопка, давишь на нее - открывается CalendarActivity, в котором CalendarView, чтобы можно было выбрать дату. И тут у меня начинаются открытия - одно за другим, только успевай записывать.
Во-первых, у CalendarView рекомендуется повесить обработчик на событие onSelectedDateChange, но вот незадача - текущий день уже selected, и сделать так, чтобы никакой день не был selected - нельзя. Но чтобы разработчик не скучал, сделано вот что - если взять и поскроллить календарик (не меняя выбранную дату), то ВНЕЗАПНО выстрелит событие selectedDateChange, возможно даже несколько раз. Интернеты предлагают в обработчике события "изменилась дата" проверять, РЕАЛЬНО ли изменилась дата, и таким образом понимать, изменил пользователь дату или просто поскроллил календарь. Я спасовал против этой логики, выкинул обработчик onSelectedDateChange, и добавил позорную кнопку "Ok", на которую пользователь должен нажать, чтобы подтвердить выбор даты (ссылка на StackOverflow). Тут активность с календарем стала открываться по 10 секунд, но добрая студия подсказала мне, что не надо делать календарю layoutHeight=wrap_content, если другие атрибуты говорят "отдай календарю все, что осталось от других элементов интерфейса".
Далее выяснилось, что внешний вид календаря более-менее прибит гвоздями - текущая неделя всегда выделяется другим цветом фона, а у текущей даты слева-справа от числа будут две "палочки", но это и все. Как сделать у выбранной даты другой цвет фона я так и не нашел, и таких страдальцев, опять же, полон интернет, и всем им советуют - "просто возьмите другой third-party календарь". Теперь я по крайней мере понял, почему во всех приложениях с календарями эти календари разные.
Ладно, я решил, что буду жить со стандартным CalendarView - по крайней мере, пока. Скомпилировал свой пример, поставил на свой телефон, открыл и увидел календарик с мааааахонькими циферками - намного меньшими, чем остальной текст. Как оказалось, у меня на телефоне Android 4.1, а в нем CalendarView поломали - отрисовка дат происходит без использования задаваемого пользователем (или темой) размера шрифтам. В 4.2 уже починили, в 4.0 еще не поломали, а у меня - вот так. Стало еще более понятно, почему все любят кастомные календари. (ссылка на StackOverflow - там видно, как это выглядит).
Ура, теперь у меня работает выбор даты. Дальше я добавил класс для работы с базой SQLite, создал там табличку для хранения дат - все "по учебникам". Даты выбираются, даты сохраняются - красота.
Пришло время считать интервалы между датами. Тут у меня был хитрый план - есть база, sqlite умеет нормально исполнять достаточно сложные запросы, поэтому почему бы не посчитать почти все, что нужно, силами SQL-запроса:
select _id, the_date, prev_date, julianday(the_date)-julianday(prev_date) as duration
from (select _id,the_date,
coalesce((select max(the_date)
from dates
where the_date < d.the_date),
the_date) as prev_date
from dates d) foo;
1|2014-09-01|2014-09-01|0.0
2|2014-09-10|2014-09-01|9.0
3|2014-09-20|2014-09-10|10.0
Засовываю я этот запрос в db.rawQuery("..."), и получают ошибку во время исполнения - "column not found: the_date". Как же как unknown, вот же она! Неа, говорит мне какая-то библиотека из дебрей андроидного SDK, ты мужик меня не обманешь - раз я сказала "нету", значит нету. Как назло, текст ошибки такой, что в гугле находится куча всего постороннего, и 100500 несчастных, которые реально указали не то имя колонки. Но у меня-то в sqlite3 все работает, дело точно в чем-то другом. Попробовав и так и сяк я плюнул и решил, что просто сделаю view, и буду запрашивать данные уже оттуда.
Добавил в метод создания базы вызов db.executeSql("CREATE VIEW AS ..."), и ... получил ту же самую ошибку! Оказалось, что библиотека для работы с sqlite в Android SDK пытается парсить все(!) запросы, которые ты собираешься выполнять. И когда запрос слишком сложный для нее, она валится с вот такой вот диагностикой. Я как-то могу понять, зачем это делать в rawQuery (чтобы получить имена колонок для Cursor-а), но зачем это делать в executeSql - я понять не могу.
Ладно, фиг с ним, посчитаем разницу вручную. Для работы с датами предлагается java.util.Calendar - что в нем есть для подсчета количества дней между датами? Быстрый просмотр доступных методов ничего не дал, и я пошел в гугл. И нашел вот такой ответ на StackOverflow. Настоятельно рекомендую сходить и почитать, памятуя о том, что на дворе у нас 21 век, эра победившего tzinfo и все такое прочее. Вот вам для затравки один из ответов оттуда:
int difference=
((int)((startDate.getTime()/(24*60*60*1000))
-(int)(endDate.getTime()/(24*60*60*1000))));
Я, каюсь, был насколько впечатлен увиденным, что в результате тоже использовал позорное:
TimeUnit.MILLISECONDS.toDays(curr.getTimeInMillis()-prev.getTimeInMillis());
Короче говоря, приложение я написал, но первое впечатление об андроиде и его SDK у меня сложилось далеко не самое благоприятное. Рассказывайте теперь, как надо было делать правильно :)
(no subject)
Date: 2014-09-09 12:16 am (UTC)(no subject)
Date: 2014-09-09 06:55 am (UTC)(no subject)
Date: 2014-09-09 09:59 am (UTC)Собственно, когда я начал смотреть примеры (Delphi XE5), у меня уже был телефон с андроидом, и оказалось намного проще запускать и отлаживать приложение прямо на телефоне, чем на эмуляторе.
Тем более, что эмулятор не даст никакого представления об удобстве пользования приложением "пальцами".
(no subject)
Date: 2014-09-09 12:20 am (UTC)1. В ведроиде есть встроенный sip-клиент начиная от версии 2.3. Однако часто он отключен вендорами при сборке и не умеет делать эхоподавление, от чего бесполезен полностью. Есть специальная функция установить эходав только она не работает вообще.
2. Ведроид типа штатно умеет играть mp4-потоки, однако все настройки сети там прибиты гвоздями внутри отчего ведроид пытается запустить поток по udp, и если не получилось то по tcp, что приводит к лагу в 5-10 секунд который ничем не убирается.
3. Более-менее приличные библиотеки требуют native-компиляции, и это отдельный ад.
(no subject)
Date: 2014-09-09 06:59 am (UTC)(no subject)
From:(no subject)
Date: 2014-09-09 08:50 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:"А какая разница?"
From:(no subject)
Date: 2014-09-09 05:36 am (UTC)(no subject)
Date: 2014-09-26 10:31 am (UTC)(no subject)
Date: 2014-09-09 06:12 am (UTC)Но тут, конечно, во многом нужно сказать "спасибо" выбранному базовому языку, то есть жабе.
Заборы, коровники.
(no subject)
Date: 2014-09-09 07:00 am (UTC)(no subject)
Date: 2014-09-09 06:54 am (UTC)(no subject)
Date: 2014-09-10 08:46 pm (UTC)(no subject)
Date: 2014-09-09 06:54 am (UTC)и не стоит уповать на яблоко - там своего добра хватает, только на языке эльфов и без фёрд-пати библиотек. То есть копируй, юзер, вот эту простыню с солюшном и меняй под себя.
а листенер, который не слушает дефолт - проблема многих систем, я еще с лохматого дельфи-2003 приучил себя проверять две весёлые вещи: листенер дефолта и листенер программного чекинга. Первые ты столкнулся, а второе - это когда чекаешь чекбокс в коде. В 50% случаев элемент срабатывает с криком "о, господин юзер меня нажал!"
(no subject)
Date: 2014-09-09 09:18 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
Date: 2014-09-09 11:13 am (UTC)Прекратите разводить дезинформацию, в CocoaPods в данный момент 3rd-party библиотек — 5,236 (но в это число входит небольшое количество OS X-эксклюзивов), устанавливаются все одной коммандой в терминале, автоматически интегрируются в проект с созданием всех нужных таргетов. Это не говоря уж про библиотеки, которые туда пока что не добавили и которые тихонько на гитхабе живут.
Про языки тоже уточняйте, их сейчас как минимум два, если не считать опенсорсные поделия и RubyMotion.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
Date: 2014-09-09 08:53 am (UTC)(no subject)
Date: 2014-09-09 09:30 am (UTC)Но даже продвинутые разработчики часто делают полную чернягу вида "сообщение об ошибке никогда не скажет, в чем именно была ошибка", в основном - из-за checked exceptions, которые всем лень нормально обрабатывать.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
Date: 2014-09-09 09:37 am (UTC)ЗЫ если же вы имеете в виду строгость, работу с памятью и прочие фундаментальные вещи (не алгоритмические), то плз оставим это за скобками :)
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2014-09-09 01:59 pm (UTC) - Expand(no subject)
Date: 2014-09-09 10:50 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
Date: 2014-09-09 10:48 am (UTC)(no subject)
Date: 2014-09-09 12:26 pm (UTC)(no subject)
Date: 2014-09-11 06:18 am (UTC)A можно примеров?
(no subject)
Date: 2014-09-09 05:56 pm (UTC)https://code.google.com/p/android/
https://issues.apache.org/jira/browse/
Там в коде прототип метода
RandomAccessFile#seek(long)
но внутри чуть ли не на первой строчке этот лонг урезается до инта.
"С быстрым компилятором workflow намного более гладкий"
Date: 2014-09-10 07:17 pm (UTC)(no subject)
Date: 2014-09-11 09:00 am (UTC)В общем у меня сложилось впечатление, что разработка под андроид это такие помои, что ни за какие деньги (сам под айось пишу, там только интерфейс xcode кривой в усмерть, с остальным жить можно).
(no subject)
Date: 2015-12-21 03:19 pm (UTC)