andresol: (Default)
[personal profile] andresol
Кто чем занимался 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 – это обыкновенный кусок текста на экране, но еще с несколькими дополнительными методами, которые и определяют, что в этом тексте будет написано.


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 сердечки-жизни, которые гаснут с заполнением каждого ряда. Вот такое я уже умею. Научусь и с багами воевать. Когда-нибудь. Главное не грузить себя до потери всякой мотивации учиться.

Date: 2019-01-06 04:03 am (UTC)
From: [identity profile] levgilman.livejournal.com
"И вообще непонятно, что оно в хронометре делает, потому что вывод текста на экран – это основной поток. Брат решил, что это баг."

В чём проблема? Наверное же mTickRunnable - член класса, производного от Handler, а экземпляр этого класса работает в том потоке, в котором создан, а создается в штатном режиме, повидимому, в основном потоке.

Если баг - то каким же образом вообще текст обновляется? Хотя я сталкивался с тем, что в основном потоке запускается то, что должно было жапускаться в отдельном.

Date: 2019-01-06 04:28 am (UTC)
From: [identity profile] andresol.livejournal.com
Я не приводил код метода, который отмечен как synchronized, потому что он относительно длинный. Это private synchronized void updateText(long now).

Я о потоках не знаю почти ничего, но мой брат сказал:
"Раз внутри этого метода вызывается setText(text);, то бессмысленно отмечать его как synchronized. Нельзя вызывать этот метод ни с какого потока, кроме как с основного. Пометка synchronized только жрет ресурсы, поэтому это баг, а не особенность стиля."

Date: 2019-01-06 06:04 pm (UTC)
From: [identity profile] levgilman.livejournal.com
А, я думал, что "непонятно, что оно в хронометре делает" - это про updateText, и что это системная функция, которая должна вызываться из основного потока. Ну "системная" может быть неправильно, имею в виду то, что "не сами программисты пишут".

Date: 2019-01-06 04:16 am (UTC)
From: [identity profile] saint-dragon.livejournal.com
Я, конечно, ничего не знаю про Java, но 400 строчек для какого-то элементарного таймера (или он умеет еще три миллиона разных вещей делать?) это впечатляет.

В химии тоже нередко бывает, что самой сложной и неподдающейся оказывается не какое-нибудь сложное не знаю что, а снятие какой-нибудь примитивной защитной группы в самом конце. Ничего интересного и впечатляющего, а уйти на это может больше всего усилий. И никто не оценит - ну сняли группу и сняли, что тут такого. Вот и дизайны и прочая косметика это такая длительная затяжная морока, сплошная головная боль для идеалиста-перфекциониста, да хотя бы на примере слайдов в поверпоинте каком-нибудь. Как люди вырисовывают огромные игры или фильмы, я не могу себе вообще представить, у нас в компании друзей есть один чел, который программирует мультики в Диснее, например. И ведь все привыкли к такому уровню, если ты что-то простое нарисуешь - никто не впечатлится, и не поверит, что на это могли уйти недели. Хотя это, вероятно, не совсем тот дизайн, о котором ты говоришь ;-)
Edited Date: 2019-01-06 04:17 am (UTC)

Date: 2019-01-06 04:36 am (UTC)
From: [identity profile] andresol.livejournal.com
У меня весь математический квиз 300 строчек, которые включают самопальный хронометр и все остальное. То есть минимальный функционал - это строчек десять. А все остальное игра в форматирование и довольно обширные комментарии.
Класс TextView, от которого Chronometer наследует, - это код на 12500 строчек. Не потому что текст так сложно вывести, а потому что ему можно поменять очень много атрибутов.

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

Date: 2019-01-07 09:34 am (UTC)
From: [identity profile] kvisaz.livejournal.com
универсальный код требует много места, потому что он как бы несет в себе реализацию под разные случаи, а не под один, где можно обойтись компактным хаком )

я знаю примеры игр в 30 символов на javascript (на 30lines.info), но это предельно конкретная реализация с предельно конкретными возможности и условиями запуска (текстовый вывод в окне браузера). Начнешь переписывать и расширять - получится уже гораздо больше, даже если делаешь для себя.

А когда сознательно пишешь класс для повторного использования в будущем - без некоторой многословности не обойтись.

Date: 2019-01-07 08:15 pm (UTC)
From: [identity profile] kvisaz.livejournal.com
Не 30 символов, а 30 строк, конечно.

Date: 2019-01-08 05:56 am (UTC)
From: [identity profile] saint-dragon.livejournal.com
Хорошо, что не из 6 символов ;-)

Date: 2019-01-07 04:31 pm (UTC)
From: [identity profile] nervo4ka.livejournal.com
Мне, как всегда, очень интересно!

Date: 2019-01-07 06:06 pm (UTC)
From: [identity profile] andresol.livejournal.com
Спасибо, что читаешь! Мне тоже нравятся, когда люди про свою работу пишут, как ты про микроскопы.

Date: 2019-01-08 03:17 am (UTC)
From: [identity profile] drbernat.livejournal.com
Принцип Парето в действии. Та же история у нас с разработкой глюкозных сенсоров. Казалось бы, самое сложное - это фундаментальное открытие нового вещества, но на деле куда больше времени уходит на решение проблем вроде "как сделать чтобы маленький сенсор не вываливался из иглы при транспортировке и хранении".

Date: 2019-01-08 03:24 am (UTC)
From: [identity profile] andresol.livejournal.com
Да, и пока в эту область не погрузишься, ни за что не догадаешься о таких мелочах. Я после полугода изучения Андроида пересмотрел свои взгляды на то, насколько легко с нуля сделать мобильное приложение.

April 2026

S M T W T F S
   123 4
5678910 11
12131415161718
1920 2122232425
2627282930  

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Apr. 25th, 2026 04:38 pm
Powered by Dreamwidth Studios