Кто чем занимался 31 декабря, а я разбирал класс Chronometer из стандартной библиотеки Андроида. Как раз в 11:30 pm закончил. И вот хочу поделиться, что нового я узнал, плюс порассуждать о своем прогрессе в обучении программированию в целом.
Я уже писал, что в прошлом году, наконец, решил серьезно учить программирование. Вначале цель была – “выпускать собственные приложения”, но постепенно, осознав размах задачи, я поменял ее на более реалистичную – “понимать код, который пишет мой брат”. Искать работу разработчиком я не собираюсь, что сильно ограничивает набор тем и инструментов для изучения.
Моему брату надоели баги в кросс-платформенном движке cocos2d-x, на котором написаны наши приложения, поэтому он переписывает код на нативную для Андроида Java. Я установил Android Studio и принялся тоже учить Java и Android по разнообразным туториалам и официальной документации, чередуя теорию с практикой, то есть с написанием собственных небольших проектов.
Во всех наших приложениях есть “Игра на время”, где надо дать как можно больше правильных ответов за одну минуту. Поэтому я счел необходимым научиться вставлять в свои проекты отсчет времени. Разумеется, такие штуки не придумать самому, а надо искать в интернете готовые решения и подгонять их под свои задачи. Решений оказалось много разных. Я скопировал некий код в свой математический квиз, потратил пару часов, чтобы заставить его правильно работать, и еще пару часов, чтобы разобраться, как именно он работает:

Цифры “9.9” в верхнем правом углу и есть обратный отсчет времени: у вас осталось меньше десяти секунд, чтобы перемножить 27 на 22.
Для другого своего проектика “Отгадай цифры” я попробовал другое решение, которое подсмотрел в одном из чужих проектов на github, – готовый андроидный виджит Chronometer (в toolbar справа):

Брат посмотрел на мои поделки и сказал: “А вот теперь разберись, как этот хронометр работает?”. “Как же я могу это сделать?” – спросил я. Я уже знал, что в Android Studio можно щелкнуть на любой класс, зажав Cmd, и он откроет определение этого класса, но для стандартных андроидных классов он открывал мне только заголовки методов, без их внутренностей.
Брат покачал головой, насколько можно быть тупым, чтобы полгода учить Android, и даже не установить его исходный код, который, как оказалось, полностью открыт, и может быть легко дозагружен из интернета. После чего на меня напало воодушевление: я же могу сейчас почитать код, который настоящие гуглеры пишут, а не какие-то чудики с гитхаба. И 31 декабря я сел разбирать Chronometer.
В моем ученическом представлении разобрать класс – это прочитать его код, понять все слова и что какой метод делает. Код у Chronometer – всего 400 строчек с пробелами и комментариями.
Тут мне пришла аналогия из химии – разбор полного синтеза. Понять все стадии, что на них происходит, какие реагенты используются и почему. И как понимание отдельных стадий не приведет к пониманию стратегии, почему именно таким путем синтезировали молекулу, и тем более не позволит самому придумывать такие синтезы, так и мой дилетантский разбор класса не позволит писать такой же код. Но без знания реакций и базовых химических концепций, тем более не удастся ничего сделать. На то она и учеба.
Первым делом я увидел, что Chronometer наследует от TextView. Год назад я не понял бы, что это значит. Поэтому я постараюсь писать как можно проще, а если вставлять куски кода, то только для иллюстративных целей, понимать его необязательно. В общем, Chronometer – это обыкновенный кусок текста на экране, но еще с несколькими дополнительными методами, которые и определяют, что в этом тексте будет написано.
И более того, сам механизм обновления текста через определенные промежутки времени такой же, как и в моем математическом квизе. Те самые Runnable, которые я два часа пытался понять:
А вот время они берут разное. У меня было System.currentTimeMilles() – число миллисекунд между текущим временем и 1 января 1970 года по Гринвичу. А в Chronometer оказалась другая функция SystemClock.elapsedRealTime(), которая возвращает число миллисекунд с последней загрузки устройства, что намного логичнее, так как я не хочу, чтобы автоматическая смена часового пояса или перевод часов самим пользователем сбил мой хронометр на пару часов.
Я стал читать про разные системы времени, встретил незнакомый мне модификатор native у elapsedRealTime(), но брат, с которым я делился своими разысканиями, сказал мне не лезть в такие дебри. Native – обращение к коду, написанному не на Java. Android как операционная система является разновидностью Linux, поэтому такие фундаментальные вещи, как системное время достается через код на C.
То же самое он сказал мне о словечке synchronized, которое используется как модификатор метода updateText(long now) в Chronometer. Что это нечто, связанное с многопоточностью и что мне о таком рано знать. И вообще непонятно, что оно в хронометре делает, потому что вывод текста на экран – это основной поток. Брат решил, что это баг.
А вот следующая функция isTheFinalCountDown() багом не была:
Из официальной документации класса Chronometer невозможно догадаться, что она делает. Проверят финальный ли это отсчет – что это значит?
Ответ есть в исходном коде. Моих познаний в Android уже было достаточно, чтобы понять, что при вызове этот метод попытается открыть в браузере YouTube, а там песню группы Europe “The Final Countdown” 1986 года. Популярную, судя по 500m+ просмотрам.
Зачем? Этим вопросом задавался не только я. Поэтому нашел обсуждение этого метода на Reddit, где все решили, что это так называемое “пасхальное яйцо” от разработчиков Google, добавленное, кстати, весьма недавно, в API 26 Android Oreo, вышедшей в 2017 году. Именно, чтобы заставить недоумевать, а потом улыбнуться тех, кто будет читать исходный код класса Chronometer.
Пользы от такого знания никакой, но почему-то именно в этот момент я решил, что не зря читал этот код. Намного полезнее мне было узнать, почему комментарии там написаны в таком странном формате с аннотациями типа @return. Официальная документация автоматически генерится из них с помощью программы Javadoc. То есть к исполняемому коду этот @return отношение не имеет, но позволяет писать комментарии к каждому классу и методу один раз в коде, а html для сайта со всеми гиперссылками получится автоматически.
А глобальный вывод в том, что в классе Chronometer нет ничего магического, чего я не смог бы сам воссоздать через TextView или собственный класс. Тем более оказалось, что в Chronometer нельзя задать формат, который показывал бы десятые доли секунды, как мы делаем в своих эппах. А когда знаешь, как он работает, то можешь и такую функциональность добавить и многое другое. У меня есть желание еще какой-нибудь несложный класс разобрать. Это как читать настоящие статьи вместо учебника по химии.
Но если продолжать сравнения, я со своими недоприложениями, которые нельзя в таком виде публиковать, и знанием, что каждое слово значит в Java, сейчас нахожусь на той же стадии обучения, где находится химик, который знает кучу химических реакций, умеет ставить их в лабе, но не умеет делать хроматографию и разбирать ЯМР. Он может побеждать на олимпиадах, сдавать экзамены и пройти практикум по органике на третьем курсе СПбГУ, но в реальном синтезе он не уйдет дальше первой стадии. Ни продукта выделить не сможет, ни понять, какая его структура и насколько он чистый.
Можно заявить, что это уже не органика, а аналитика. Настоящая химия – это смешивать реагенты, а не гонять колонки. Но это не так. Вот и в программировании настоящая работа для меня еще и не начиналась. Я все еще барахтаюсь на учебном уровне, где все работает. А дальше надо заниматься скучными, сложными вещами: архитектурой, дизайном, тестированием.
Научиться переворачивать свое приложение из portrait в landscape, чтобы все сохранилось и не поплыло, кажется такой мелочью по сравнению с логикой игры. Но мой брат эту логику может написать за пять минут, а сидит и думает он как раз над такими вещами, как все лучше сделать, чтобы все работало быстро на всех устройствах.
Помучившись, я переворачивание сделал, но я сам знаю, что у меня есть баг: эпп вылетает, если перевернуть во время “экрана победы”. И мне под силу этот баг исправить, но так не хочется. Я же не собираюсь этот проект публиковать.

Зато сегодня с утра за полчаса добавил в toolbar сердечки-жизни, которые гаснут с заполнением каждого ряда. Вот такое я уже умею. Научусь и с багами воевать. Когда-нибудь. Главное не грузить себя до потери всякой мотивации учиться.
Я уже писал, что в прошлом году, наконец, решил серьезно учить программирование. Вначале цель была – “выпускать собственные приложения”, но постепенно, осознав размах задачи, я поменял ее на более реалистичную – “понимать код, который пишет мой брат”. Искать работу разработчиком я не собираюсь, что сильно ограничивает набор тем и инструментов для изучения.
Моему брату надоели баги в кросс-платформенном движке cocos2d-x, на котором написаны наши приложения, поэтому он переписывает код на нативную для Андроида Java. Я установил Android Studio и принялся тоже учить Java и Android по разнообразным туториалам и официальной документации, чередуя теорию с практикой, то есть с написанием собственных небольших проектов.
Во всех наших приложениях есть “Игра на время”, где надо дать как можно больше правильных ответов за одну минуту. Поэтому я счел необходимым научиться вставлять в свои проекты отсчет времени. Разумеется, такие штуки не придумать самому, а надо искать в интернете готовые решения и подгонять их под свои задачи. Решений оказалось много разных. Я скопировал некий код в свой математический квиз, потратил пару часов, чтобы заставить его правильно работать, и еще пару часов, чтобы разобраться, как именно он работает:

Цифры “9.9” в верхнем правом углу и есть обратный отсчет времени: у вас осталось меньше десяти секунд, чтобы перемножить 27 на 22.
Для другого своего проектика “Отгадай цифры” я попробовал другое решение, которое подсмотрел в одном из чужих проектов на github, – готовый андроидный виджит Chronometer (в toolbar справа):

Брат посмотрел на мои поделки и сказал: “А вот теперь разберись, как этот хронометр работает?”. “Как же я могу это сделать?” – спросил я. Я уже знал, что в Android Studio можно щелкнуть на любой класс, зажав Cmd, и он откроет определение этого класса, но для стандартных андроидных классов он открывал мне только заголовки методов, без их внутренностей.
Брат покачал головой, насколько можно быть тупым, чтобы полгода учить Android, и даже не установить его исходный код, который, как оказалось, полностью открыт, и может быть легко дозагружен из интернета. После чего на меня напало воодушевление: я же могу сейчас почитать код, который настоящие гуглеры пишут, а не какие-то чудики с гитхаба. И 31 декабря я сел разбирать Chronometer.
В моем ученическом представлении разобрать класс – это прочитать его код, понять все слова и что какой метод делает. Код у Chronometer – всего 400 строчек с пробелами и комментариями.
Тут мне пришла аналогия из химии – разбор полного синтеза. Понять все стадии, что на них происходит, какие реагенты используются и почему. И как понимание отдельных стадий не приведет к пониманию стратегии, почему именно таким путем синтезировали молекулу, и тем более не позволит самому придумывать такие синтезы, так и мой дилетантский разбор класса не позволит писать такой же код. Но без знания реакций и базовых химических концепций, тем более не удастся ничего сделать. На то она и учеба.
Первым делом я увидел, что Chronometer наследует от TextView. Год назад я не понял бы, что это значит. Поэтому я постараюсь писать как можно проще, а если вставлять куски кода, то только для иллюстративных целей, понимать его необязательно. В общем, Chronometer – это обыкновенный кусок текста на экране, но еще с несколькими дополнительными методами, которые и определяют, что в этом тексте будет написано.
public class Chronometer extends TextView {
И более того, сам механизм обновления текста через определенные промежутки времени такой же, как и в моем математическом квизе. Те самые Runnable, которые я два часа пытался понять:
private final Runnable mTickRunnable = new Runnable() { @Override public void run() { if (mRunning) { updateText(SystemClock.elapsedRealtime()); dispatchChronometerTick(); postDelayed(mTickRunnable, 1000); } } };
А вот время они берут разное. У меня было System.currentTimeMilles() – число миллисекунд между текущим временем и 1 января 1970 года по Гринвичу. А в Chronometer оказалась другая функция SystemClock.elapsedRealTime(), которая возвращает число миллисекунд с последней загрузки устройства, что намного логичнее, так как я не хочу, чтобы автоматическая смена часового пояса или перевод часов самим пользователем сбил мой хронометр на пару часов.
Я стал читать про разные системы времени, встретил незнакомый мне модификатор native у elapsedRealTime(), но брат, с которым я делился своими разысканиями, сказал мне не лезть в такие дебри. Native – обращение к коду, написанному не на Java. Android как операционная система является разновидностью Linux, поэтому такие фундаментальные вещи, как системное время достается через код на C.
То же самое он сказал мне о словечке synchronized, которое используется как модификатор метода updateText(long now) в Chronometer. Что это нечто, связанное с многопоточностью и что мне о таком рано знать. И вообще непонятно, что оно в хронометре делает, потому что вывод текста на экран – это основной поток. Брат решил, что это баг.
А вот следующая функция isTheFinalCountDown() багом не была:
/** * @return whether this is the final countdown */ public boolean isTheFinalCountDown() { try { getContext().startActivity( new Intent(Intent.ACTION_VIEW, Uri.parse("https://youtu.be/9jK-NcRmVcw")) .addCategory(Intent.CATEGORY_BROWSABLE) .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)); return true; } catch (Exception e) { return false; } }
Из официальной документации класса Chronometer невозможно догадаться, что она делает. Проверят финальный ли это отсчет – что это значит?
Ответ есть в исходном коде. Моих познаний в Android уже было достаточно, чтобы понять, что при вызове этот метод попытается открыть в браузере YouTube, а там песню группы Europe “The Final Countdown” 1986 года. Популярную, судя по 500m+ просмотрам.
Зачем? Этим вопросом задавался не только я. Поэтому нашел обсуждение этого метода на Reddit, где все решили, что это так называемое “пасхальное яйцо” от разработчиков Google, добавленное, кстати, весьма недавно, в API 26 Android Oreo, вышедшей в 2017 году. Именно, чтобы заставить недоумевать, а потом улыбнуться тех, кто будет читать исходный код класса Chronometer.
Пользы от такого знания никакой, но почему-то именно в этот момент я решил, что не зря читал этот код. Намного полезнее мне было узнать, почему комментарии там написаны в таком странном формате с аннотациями типа @return. Официальная документация автоматически генерится из них с помощью программы Javadoc. То есть к исполняемому коду этот @return отношение не имеет, но позволяет писать комментарии к каждому классу и методу один раз в коде, а html для сайта со всеми гиперссылками получится автоматически.
А глобальный вывод в том, что в классе Chronometer нет ничего магического, чего я не смог бы сам воссоздать через TextView или собственный класс. Тем более оказалось, что в Chronometer нельзя задать формат, который показывал бы десятые доли секунды, как мы делаем в своих эппах. А когда знаешь, как он работает, то можешь и такую функциональность добавить и многое другое. У меня есть желание еще какой-нибудь несложный класс разобрать. Это как читать настоящие статьи вместо учебника по химии.
Но если продолжать сравнения, я со своими недоприложениями, которые нельзя в таком виде публиковать, и знанием, что каждое слово значит в Java, сейчас нахожусь на той же стадии обучения, где находится химик, который знает кучу химических реакций, умеет ставить их в лабе, но не умеет делать хроматографию и разбирать ЯМР. Он может побеждать на олимпиадах, сдавать экзамены и пройти практикум по органике на третьем курсе СПбГУ, но в реальном синтезе он не уйдет дальше первой стадии. Ни продукта выделить не сможет, ни понять, какая его структура и насколько он чистый.
Можно заявить, что это уже не органика, а аналитика. Настоящая химия – это смешивать реагенты, а не гонять колонки. Но это не так. Вот и в программировании настоящая работа для меня еще и не начиналась. Я все еще барахтаюсь на учебном уровне, где все работает. А дальше надо заниматься скучными, сложными вещами: архитектурой, дизайном, тестированием.
Научиться переворачивать свое приложение из portrait в landscape, чтобы все сохранилось и не поплыло, кажется такой мелочью по сравнению с логикой игры. Но мой брат эту логику может написать за пять минут, а сидит и думает он как раз над такими вещами, как все лучше сделать, чтобы все работало быстро на всех устройствах.
Помучившись, я переворачивание сделал, но я сам знаю, что у меня есть баг: эпп вылетает, если перевернуть во время “экрана победы”. И мне под силу этот баг исправить, но так не хочется. Я же не собираюсь этот проект публиковать.

Зато сегодня с утра за полчаса добавил в toolbar сердечки-жизни, которые гаснут с заполнением каждого ряда. Вот такое я уже умею. Научусь и с багами воевать. Когда-нибудь. Главное не грузить себя до потери всякой мотивации учиться.
no subject
Date: 2019-01-06 04:03 am (UTC)В чём проблема? Наверное же mTickRunnable - член класса, производного от Handler, а экземпляр этого класса работает в том потоке, в котором создан, а создается в штатном режиме, повидимому, в основном потоке.
Если баг - то каким же образом вообще текст обновляется? Хотя я сталкивался с тем, что в основном потоке запускается то, что должно было жапускаться в отдельном.
no subject
Date: 2019-01-06 04:28 am (UTC)Я о потоках не знаю почти ничего, но мой брат сказал:
"Раз внутри этого метода вызывается setText(text);, то бессмысленно отмечать его как synchronized. Нельзя вызывать этот метод ни с какого потока, кроме как с основного. Пометка synchronized только жрет ресурсы, поэтому это баг, а не особенность стиля."
no subject
Date: 2019-01-06 06:04 pm (UTC)no subject
Date: 2019-01-06 04:16 am (UTC)В химии тоже нередко бывает, что самой сложной и неподдающейся оказывается не какое-нибудь сложное не знаю что, а снятие какой-нибудь примитивной защитной группы в самом конце. Ничего интересного и впечатляющего, а уйти на это может больше всего усилий. И никто не оценит - ну сняли группу и сняли, что тут такого. Вот и дизайны и прочая косметика это такая длительная затяжная морока, сплошная головная боль для идеалиста-перфекциониста, да хотя бы на примере слайдов в поверпоинте каком-нибудь. Как люди вырисовывают огромные игры или фильмы, я не могу себе вообще представить, у нас в компании друзей есть один чел, который программирует мультики в Диснее, например. И ведь все привыкли к такому уровню, если ты что-то простое нарисуешь - никто не впечатлится, и не поверит, что на это могли уйти недели. Хотя это, вероятно, не совсем тот дизайн, о котором ты говоришь ;-)
no subject
Date: 2019-01-06 04:36 am (UTC)Класс TextView, от которого Chronometer наследует, - это код на 12500 строчек. Не потому что текст так сложно вывести, а потому что ему можно поменять очень много атрибутов.
Да, аналогия правильная. Человеку далекому от программирования, например, пользователю приложения, почти невозможно догадаться, какая была самая сложная программистская проблема. Я сам пока себя программистом назвать не могу, но по мере изучения обнаруживаю вещи, о которых раньше даже не догадывался. И в научном проекте то же самое, и в мультфильмах.
no subject
Date: 2019-01-07 09:34 am (UTC)я знаю примеры игр в 30 символов на javascript (на 30lines.info), но это предельно конкретная реализация с предельно конкретными возможности и условиями запуска (текстовый вывод в окне браузера). Начнешь переписывать и расширять - получится уже гораздо больше, даже если делаешь для себя.
А когда сознательно пишешь класс для повторного использования в будущем - без некоторой многословности не обойтись.
no subject
Date: 2019-01-07 08:15 pm (UTC)no subject
Date: 2019-01-08 05:56 am (UTC)no subject
Date: 2019-01-07 04:31 pm (UTC)no subject
Date: 2019-01-07 06:06 pm (UTC)no subject
Date: 2019-01-08 03:17 am (UTC)no subject
Date: 2019-01-08 03:24 am (UTC)