- Зона кода
- Новичкам
- Новичкам и не только
- Как Android запускает MainActivity
- Что происходит при запуске приложения
- 1 Схема запуска приложения
- 2. Класс ActivityThread
- 2.1 Подготовка main looper (Process 2–3)
- 2.2 Вызов Handler’a (Process 4-5)
- 2.3 Вызов метод loop() у Looper’а (Process 6–7)
- 3. Бесконечный loop() в Looper’е (Process 7,8,9)
- 4. Запуск MainActivity (Process 10 to 15)
Зона кода
Эту статью я написал несколько лет назад для другого сайта, но она так и не была опубликована. Тогда 7-я версия Java только-только появилась на свет, а 6-я была всё ещё актуальна. Статья адресована, в первую очередь тем, кто начинает знакомиться с языком Java. Я решил стряхнуть с неё пыль и опубликовать: пусть будет!
Здравствуйте, уважаемый читатель! Эта статья состоит из двух частей.
Первая из них адресована новичкам, которые только-только приступают к изучению Java и, возможно, не написали ещё ни одной программы на этом языке.
А вторая часть может быть интересна не только новичкам, но и тем, кто уже имеет некоторый опыт программирования на Java. В ней будут описаны несколько любопытных фактов, связанных с main() . Признаться, о некоторых из них я и сам узнал, уже имея за плечами 3 года работы с Java-технологиями. Это, кстати, говорит о том, что прекрасно можно обходиться и без знания этих фактов. Их ценность состоит лишь в том, что они действительно забавны.
Новичкам
Метод main() отличается от всех остальных методов тем, что является, как правило, точкой входа в программу. Этот метод вызывается виртуальной машиной Java. Как только заканчивается выполнение метода main() , так сразу же завершается, тем самым, работа самой программы.
Метод main() , так и любой другой метод, должен быть обязательно вложен в класс. После компиляции класс, содержащий метод main() , запускается на выполнение командой
Эта команда приводит к выполнению метода main() , вложенного в данный класс. Оговоримся, что после имени класса могут следовать параметры командной строки, которые будут обсуждаться ниже.
Метод main() должен объявляться в классе следующим образом:
Ключевое слово public означает, что метод main() доступен везде, где доступен содержащий его класс. Ключевое слово static означает, что метод является статическим, т. е. не требует для своего вызова наличие экземпляра класса. Ключевое слово void означает, что метод не возвращает никакого значения. Все эти три слова обязательно должны присутствовать в описании метода.
Запись ( String[] args ) после имени метода представляет собой список его параметров. Имеется лишь один параметр args , содержащий ссылку на массив строк. Этот массив формируется из строки, переданной классу, содержащему метод main() , при запуске этого класса на выполнение командой java.
Если строка передана не была, то массив args содержит 0 элементов. В противном случае строка разбивается на части, которые в строке отделёны друг от друга пробельными символами. Эти части будем в дальнейшем называть аргументами командной строки. Данными аргументами и заполняется массив args .
Вместо имени массива args можно использовать любое другое имя. Например, следующее описание main() вполне корректно.
Что касается типа и количества параметров метода main() , то они изменению не подлежат.
Заметим, что все требования к методу main() , приведённые выше, необходимо соблюдать только в случае, если мы хотим, чтобы данный метод мог вызываться виртуальной машиной Java.
Рассмотрим пример, в котором на консоль построчно выводятся все аргументы командной строки, переданные программе при её запуске. Под программой в данном случае мы будем понимать класс, содержащий метод main() . Этот класс мы назовём MainTest . Вот его код:
Здесь в методе main() содержится цикл, поочерёдно перебирающий элементы массива args . Ссылки на элементы массива поочерёдно присваиваются переменной s и выводятся на консоль методом println() объекта System.out . Этот метод после каждого вывода осуществляет перевод строки.
Сохраним код класса MainTest в файле с именем MainTest.java и скомпилируем этот файл командой
В результате компиляции получаем файл MainTest.class . Запускаем его на выполнение с несколькими аргументами командной строки:
java MainTest Это всего лишь проверка!
Если нет проблем с отображением кириллицы, то в результате выполнения команды на консоль будет выведено:
Это
всего
лишь
проверка!
Метод main() может вызываться не только виртуальной машиной Java, но и любым другим методом. Например, main() может быть рекурсивным, т. е. может вызывать сам себя. В этом отношении main() ничем не отличается от остальных методов.
В следующем примере (весьма вычурном) метод main() рекурсивен. При каждом вызове метода, за исключением последнего, на печать выводится один аргумент командной строки.
Здесь в методе main() выясняется, не является ли пустым массив args . Если нет, то на печать выводится первый его элемент. После этого метод main() вызывается рекурсивно. В качестве параметра ему передаётся новый массив, отличающийся от старого отсутствием первого элемента. Если же массив args пуст, то работа main() на этом завершается.
Результат выполнение класса RexMain совпадает с результатом выполнения класса MainTest .
Метод main() , так же как и любой другой метод, можно перегружать, т. е. создавать в том же классе одноимённые методы, отличающиеся от исходного списком параметров. При запуске на выполнение класса, содержащего несколько методов main() , виртуальная машина Java выбирает нужный (если он имеется), ориентируясь на список параметров.
Несколько разных классов, входящих в одну программу, могут содержать методы main() . Ничто не мешает запускать на выполнение любой из этих классов.
Новичкам и не только
В самом начале статьи было сказано, что метод main() является точкой входа в программу с оговоркой “как правило”. В этом разделе мы установим, что, вообще говоря, выполнение программы необязательно начинается с вызова метода main() .
В Интернете на форумах, посвящённых Java, я нередко встречал любопытную задачку: написать класс, метод main() которого имеет пустое тело, выводящий на консоль надпись “Hello world!”. Полагаю, что эта задача известна многим из тех, кто интересуется Java.
В основе решения задачи лежит использование статического блока, представляющего собой программный код, заключённый в фигурные скобки, которому предшествует ключевое слово static . Статический блок вкладывается в класс и исполняется при первом же обращении к классу, до выполнения любых методов класса, в том числе, и метода main() .
Статический блок имеет некоторое сходство со статическим методом. Отличается он от последнего тем, что не имеет имени, не принимает параметров, не возвращает значения (а значит, не может содержать инструкции return ) и не вызывается явно. Так же как и статический метод, статический блок может содержать обращения к статическим полям и методам класса.
Статические блоки используются редко. Как правило, в их задачи входит инициализация статических полей класса.
Ну а мы поместим в статический блок инструкцию вывода на консоль строки “Hello world!”:
Можно скомпилировать класс, запустить на выполнение и удостовериться в том, что с поставленной задачей он вполне успешно справляется.
Тот факт, что статический блок выполняется до метода main() , легко проверить, скомпилировав и выполнив следующий класс:
На консоль будет выведено:
Привет от static-блока!
Привет от метода main()!
Заметим, что задачу, поставленную в начале статьи, можно решить, не прибегая к использованию статического блока. Для этого достаточно написать статический метод, печатающий строку “Hello world!” и возвращающий значение какого-либо типа, например, типа int , после чего создать статическое поле того же типа, инициализирующееся посредством вызова данного метода.
Дело в том, что инициализация статических полей происходит до выполнения любых статических методов, (и даже до выполнения статического блока). Исключением из этого правила является вызов статических методов в процессе инициализации. Поэтому метод, участвующий в инициализации, выполнится раньше main() .
Вот код, демонстрирующий данный подход:
Выполнение класса WithoutStatic приводит к тому же выводу на консоль, что и выполнение класса HelloWorld .
Резонно задаться вопросом: а можно ли вообще обойтись без метода main() , пусть даже имеющего пустое тело? Для ответа на вопрос изменим класс HelloWorld , удалив из него main() :
И вот тут начинается самое интересное! Я компилировал и запускал этот класс с использованием комплектов разработки на языке Java (JDK) трёх разных версий: jdk1.6_021, jdk1.6_024 и jdk1.7.0_01. Во всех трёх случаях код компилировался без проблем. А вот с запуском класса проблемы возникали.
Для начала, оговорюсь, что каждый файл с расширением class запускался под управлением “своей” виртуальной машины Java, т. е. входящей в тот JDK, посредством которого этот файл был получен в результате компиляции.
Итак, в последних двух случаях на экран выводилась надпись:
Error: Main method not found in class WithoutMain, please define the main method as: public static void main(String[] args)
Таким образом, не выполнялся даже код, входящий в статический блок. А вот в первом случае на консоль выводилось следующее:
HelloWorld!
Exception in thread «main» java.lang.NoSuchMethodError: main
На этот раз виртуальная машина, всё же, выполняет статический блок и только после этого, не обнаружив метода main() , спохватывается и выдаёт по данному поводу ругательство. Можно ли избежать сообщения об исключении? Оказывается, можно!
Для этого достаточно после инструкции печати вызвать статический метод exit() класса System . Данный метод прерывает работу виртуальной машины и возвращает родительскому процессу (как правило, операционной системе) целое значение, переданное методу в качестве аргумента. Считается, что ненулевое значение свидетельствует об аварийном прерывании.
Итак, ниже приведён код работоспособной программы, не содержащей метода main() .
Выполнение программы приводит к следующему выводу на консоль:
Как мы выяснили, программа вполне может обходиться без метода main() , правда, с оговоркой, что запущена на выполнение она будет под управлением виртуальной машины достаточно старой версии. На этом всё. Спасибо за внимание!
Источник
Как Android запускает MainActivity
Недавно я провел исследование о main() методе в Java и то, как он служит точкой входа для любого приложения Java. Это заставило меня задуматься, а как насчет Android-приложений? Есть ли у них основной метод? Как они загружаются? Что происходит за кулисами до выполнения onCreate()? Майкл Бэйли очень подробно рассказал о том, как работает Main Thread, так что это быстрый обзор его доклада плюс дополнительная информация из Android Open Source Project (AOSP).
В этой статье мы рассмотрим:
- Что происходит от нажатия на иконку приложения до запуска MainActivity
- Найдем основной метод приложения и узнаем, как основной поток (он же UI, он же Main Thread) получает свое назначение.
- Рассмотрим роль, которую играют Looper & Handler в передаче сообщений, которые в конечном итоге приводят к созданию вашей Activity.
Что происходит при запуске приложения
1 Схема запуска приложения
Между вызовом метода main() и onCreate() в нашем MainActivity примерно 15 шагов, и в этой статье мы пройдем по ним. На рисунке 1 изображена общая схема запуска приложения, показывающая различные классы взаимодействия сверху и соответствующую цепочку методов. Шаги пронумерованы, и когда я обращаюсь к ним, я буду использовать следующие обозначения Process3 или Process14
Рисунок 1: Схема запуска приложения по шагам от вызова main() до onCreate() в MainActivity
2. Класс ActivityThread
В классе ActivityThread чуть более 6500 строк. Для краткости я определил самые важные для нас части. Давайте рассмотрим, что делает этот класс и связанный с ним основной метод, чтобы запустить нашу Activity
Рисунок 2: Метод main() в ActivityThread, который служит точкой входа для запуска вашего приложения.
Как видно в коде: метод main() выполняет три важных дела:
1. Подготавливает основной Looper (MainLooper) (Process 2)
2. Настройка Handler’a (Process 4)
3. Вызов метода Looper.loop() в главном потоке (MainThread) (Process 6)
2.1 Подготовка main looper (Process 2–3)
Основной Looper задается вызовом Looper.prepareMainLooper() (см. Строку 8 в коде). Это отмечает текущий случайный поток, который выполняет всю работу по вызову метода main() в качестве основного потока приложений. Именно так и именно здесь определяется знаменитый главный поток для приложения в Android!
2.2 Вызов Handler’a (Process 4-5)
Внутри класса ActivityThread существует приватный внутренний класс H, да-да, все верно, просто H, который наследуется от класса Handler (см. рис. 4 и 7). В 12й строке экземпляр H-обработчика устанавливается как главный Handler потока. Что очень интересно знать о классе H, как вы сами увидите позже, это то, что он содержит более 50 определений состояния/событий, в которых может находиться ваше приложение, например LAUNCH_ACTIVITY, PAUSE_ACTIVITY, BIND_SERVICE и т.д.
2.3 Вызов метод loop() у Looper’а (Process 6–7)
После назначения главного потока в этом же главном потоке, для того чтоб мы могли в нем что-то выполнять, вызывается метод Looper.loop() (см. Строку 20). Это начинает выполнение сообщений в очереди сообщений Loopers. Теперь главный поток запущен и может начать обработку задач из очереди.
Обратите внимание, что в строке 18, если выполнение кода пойдет дальше чем Looper.loop() в 17 строке вдруг и приложение выйдет из цикла, то будет брошено исключение RuntimeException. Это говорит о том, что метод loop() в идеале никогда преждевременно не заканчивается. Мы увидим как это в следущем разделе.
3. Бесконечный loop() в Looper’е (Process 7,8,9)
Рисунок 3: Код внутри метода loop() в классе Looper’e
Как мы видим в коде, в методе Looper.loop() есть очередь сообщений (строка 10) и внутри цикла вызывается queue.next(). MessageQueue заполняется Handler-‘ом, о котором мы говорили в предыдущем разделе (см. Process 8). Обратите внимание на интересное описание условия в цикле for — здесь нет аргументов, только две точки с запятой говорят что это бесконечный цикл. Поэтому Looper в идеале никогда не заканчивается, если данное сообщение не null.
Итак, теперь мы определили главный поток, выполняемый благодаря Looper, мы также видели, что Handler добавляет сообщения в цикл Looper.loops() и обрабатывает сообщения. Давайте посмотрим, как они вместе вызывают нашу Activity.
4. Запуск MainActivity (Process 10 to 15)
Важно помнить, что этот бесконечный цикл и обработка сообщений выполнялись в main() методе класса ActivityThread, потому что именно там они были вызваны (см. в коде строки с 12 по 17). Мы поверхностно просмотрели Loopers, MessageQueues и Handlers, чтобы вникнуть в контекст. Итак, давайте вернемся к классу ActivityThread, в частности, к внутреннему классу H, о котором мы говорили ранее, который действует как основной Handler главного потока.
Итак, у нас есть Looper, передающий сообщения нашему Handler’у, давайте узнаем, как эти сообщения обрабатываются. Это делается внутри класса H. Этот класс содержит метод handleMessage(Message msg). Помните, что все классы, которые наследуются от Handler, должны переопределить этот метод.
Рисунок 4: Приватный внутренний класс H и его handleMessage() метод
Как видно в коде, в 8й строке есть оператор switch, в котором определяется обработка входящего сообщения по его содержимому.
Один из случаев (cases) включает в себя запуск активности (строка 11), что интересно, так это то, что этот метод предназначен для обработки около 50 случаев, которые варьируются от возобновления, приостановки, запуска Activity, привязки Service’ов, обработки Receiver’ов, предоставления предупреждений lowMemory или trimMemory, когда память устройства заполняется и т. д.
В case LAUNCH_ACTIVITY вызывается метод handleLaunchActivity(), как показано в строке 13, см Process11 на схеме. Затем этот метод вызывает другой метод, называемый performLaunchActivity(), который возвращает объект Activity (см. Рис. 5, строка 7).
Рисунок 5: Метод handleLaunchActivity() в котором создается Activity
Метод performLaunchActivity() добавляет в Activity важную информацию, такую как Instrumentation, Context, Component, а также Intent; а также задает Application. Затем этот метод вызывает Instrumentation.callActivityOnCreate() (Process 13), который является последним этапом перед вызовом метода onCreate() в Activity (Process 14-15, см. Рисунок 5 (код), строки 8-10).
Рисунок 6: Класс Instrumentation наконец запускает Activity
На данный момент ваша Activity загружена c множеством полезных переменных и методов, которые можно использовать для создания вашего нового удивительного приложения для Android! Все это благодаря ActivityThread, умной работе Handler’a и Looper’a, и огромному классу Activity в 7600 строк кода, который позволяет аттачить фрагменты, получить контекст и легко управлять View’s — и много еще чего.
Источник