Android coding how to

Как устроен билд APK файла внутри

Процесс создания APK и компиляции кода

Рассматриваемые темы

  • Архитектура процессоров и необходимость для виртуальной машины
  • Понимание Java виртуальной машины
  • Компиляция исходного кода
  • Виртуальная машина Андроид
  • Процесс компиляции в .dex файл
  • ART против Dalvik
  • Описание каждой части билд процесса
  • Исходный код
  • Файлы ресурсов
  • AIDL файлы
  • Модули библиотек
  • AAR библиотеки
  • JAR библиотеки
  • Android Asset Packaging Tool
  • resources.arsc
  • D8 и R8
  • Dex и Multidex
  • Подписывание APK файла
  • Ссылки

Понимание флоу процесса билда APK файла, среда исполнения и компиляция кода
Этот пост нацелен быть отправной точкой для разработчиков, чтобы они ближе познакомились с билд процессом и созданием APK файла.

Архитектура процессоров и зачем нужна виртуальная машина

Андроид после того как вышел в 2007 году претерпел множество изменений связанный с билд процессом, средой исполнения и улучшениями производительности.

У андроида много удивительных характеристик и одна из них разные архитектуры процессоров такие как ARM64 и x86

Невозможно скомпилировать код, который поддерживает каждую архитектуру. Вот именно поэтому используется Java виртуальная машина.

Понимание Java виртуальной машины

JVM это виртуальная машина, позволяющая устройству запускать код, который скомпилирован в Java байткод

Используя JVM, вы избавляетесь от проблемы с разной архитектурой процессоров.

JVM предоставляет переносимость и она позволяет запускать Java код в виртуальной среде, вместо того, чтобы запускать его сразу «на железе»

Но JVM была создана для систем с большими мощностями по ресурсам, а наш андроид имеет сравнительно мало памяти и заряда батареи.

По этой причине Google создал адаптированную под андроид виртуальную машину, которая называется Dalvik.

Компилируем исходный код

Наш исходный Java код для андроида компилируется в класс файл .class с байткодом с помощью javac компилятора и запускается на JVM

Для котлина есть kotlinc компилятор, который делает совместимый с Java байткод.

Байткод — это набор инструкций, который выполняется на целевом устройстве.

Java байткод — это набор инструкций для Java виртуальной машины.

Андроид виртуальная машина

Каждое андроид приложение работает на своей виртуальной машине. С версий 1.0 до 4.4, это был Dalvik. В андроид 4.4, вместе с Dalvik, Google представил в качестве эксперимента новый андроид runtime, который назывался ART

Сгенерированный класс файл .class содержит JVM Java байткод.

Но у андроида есть свой собственный оптимизированный формат байткода, который называется Dalvik bytecode — это просто инструкции машинного кода для процессора также как и JVM байткод.

Комплияция в .dex файл

Во время компиляции происходит конвертация .class класс файл и .jar библиотеки в один classes.dex файл, который содержит Dalvik байткод.

Команда dx превращает все .class и .jar файлы в один classes.dex файл, который написан с форматом Dalvik байткода.

Dex — это аббревиатура с английского — Dalvik Executable.

ART против Dalvik

C версии 4.4 андроид мигрировал на ART. ART также работает с .dex файлом.

Преимущество ART над Dalvik проявляется в том, что приложения запускаются быстрее, потому что весь DEX байткод транслируется в машинный код во время установки, не нужно дополнительного времени на компиляцию в рантайме.

ART и Dalvik совместимы, так что приложения разработанные для Dalvik должны работать и на ART.

Компиляция Dalvik (JIT- just in time) имела такие минусы как — быстрая трата батареи, лаги в приложениях и плохой перформанс. В Dalvik трансляция происходит только когда это нужно. Мы открываем новый экран и только в этот момент происходит трансляция, за счет этого установка происходит быстрее, но при этом проседает перформанс.

Это причина по которой Google сделал Android Runtime (ART).

ART — основан на AOT (ahead of time) компиляции, она происходит до того как приложение запустится.

В ART компиляция происходит во время установки приложения. Это ведет к более долгому времени установки, но уменьшает трату батареи и избавляет от лагов, которые были на Dalvik.

Несмотря на то, что Dalvik был заменен на ART, .dex формат файлов еще используется

В андроид 7.0 JIT вернулся. Гибридная среда сочетает фичи как от JIT компиляции так и
от ART

Читайте также:  Мимимишки андроид полная версия все открыто

Среда запуска байткода это очень важная часть андроида и она вовлечена в процесс запуска и установки приложения

Каждый этап описанного процесса

Source Code (Исходный код)

Это Java и Kotlin файлы в src пакете.

Resource Files

Файлы находящиеся в директории с ресурсами

AIDL Files

AIDL — аббревиатура Android Interface Definition Language, позволяет вам описать интерфейс межпроцессорного взаимодействия.

AIDL — может использоваться между любыми процессами в андроиде.

Library Modules

Модули библиотек содержат Java или Kotlin классы, компоненты андроида и ресурсы.

Код и ресурсы бибилотеки компилируются и пакуются вместе с приложением.

Поэтому модуль библиотеки может считаться компайл тайм артефактом.

AAR Libraries

Андроид библиотеки компилируются в AAR — android archive файл, который вы можете использовать как зависимость для вашего android app модуля.

AAR файлы могут содержать андроид ресурсы и файл манифеста, что позволяет вам упаковать туда общие ресурсы такие как layouts и drawables в дополнение к Java или Kotlin классам и методам.

JAR Libraries

JAR это Java библиотека и в отличие от AAR она не может содержать андроид ресурсы и манифесты.

Android Asset Packaging Tool

AAPT2 — аббревиатура (Android Asset Packaging Tool) — компилирует манифест и файлы ресурсов в один APK.

Этот процесс разделен на два шага компиляцию и линковку Это улучшает производительность так как если вы поменяете один файл, вам нужно компилировать только его и прилинковать к остальным файлам командой ‘link’

AAPT2 может компилировать все типы андроид ресурсов, таких как drawables и XML файлы.

При вызове AAPT2 для компиляции, туда передается по одному ресурсному файлу на каждый вызов

Затем APPT2 парсит файл и генерирует промежуточный бинарный файл с расширением .flat

Фаза линковки склеивает все промежуточные файлы сгенерированные в фазе компиляции и дает нам на выход один .apk файл. Вы также можете сгенерировать R.java файл и правила для proguard в это же время.

resources.arsc

Полученный на выходе .apk файл не включает в себя DEX файл, APK не подписан и не может быть запущен на устройстве.

APK содержит AndroidManifest, бинарные XML файлы и resources.arsc

resource.arsc содержит всю мета информацию о ресурсах, такую как индексы всех ресурсов в пакете

Это бинарный файл и APK который может быть запущен. APK который вы обычно создаете и запускаете не сжат и может быть использован просто посредством размещения в памяти.

R.java файл это выходной файл вместе с APK ему назначен уникальный id, который позволяет Java коду использовать ресурсы во время компиляции.

arsc это индекс ресурса который используется во время запуска приложения

D8 и R8

Начиная с андроид студии 3.1 и далее, D8 был сделан дефолтным компилятором.

D8 производит более маленькие dex файлы с лучшей производительностью, если сравнивать со старым dx.

R8 используется для компиляции кода. R8 это оптимизированная версия D8

D8 играет роль конвертера класс файлов в Dex файлы, а также производит дешугаринг функций из Java 8 в байткод, который может быть запущен на андроиде

R8 оптимизирует dex байткод. Он предоставляет такие фичи как оптимизация, обфускация, удаление ненужных классов.

Обфускация уменьшает размер вашего приложения укорачивая названия классов, методов и полей.

Обфускация имеет и другие преимущества для предотвращения реверс инжиниринга, но основная цель уменьшить размер.

Оптимизация уменьшает размер Dex файла путем переписывания ненужных частей кода и инлайнинга.

С помощью дешугаринга мы можем использовать удобные фичи языка Java 8 на андроиде.

Dex and Multidex

R8 дает на выходе один DEX файл, который называется classes.dex

Если количество методов приложения переваливает за 65,536, включая подключенные библиотеки, то произойдет ошибка при билде

Источник

Редактор кода на Android: часть 1

Перед тем как закончить работу над своим редактором кода я много раз наступал на грабли, наверное декомпилировал десятки похожих приложений, и в данной серии статей я расскажу о том чему научился, каких ошибок можно избежать и много других интересных вещей.

Вступление

Привет всем! Судя из названия вполне понятно о чем будет идти речь, но всё же я должен вставить свои пару слов перед тем как перейти к коду.

Я решил разделить статью на 2 части, в первой мы поэтапно напишем оптимизированную подсветку синтаксиса и нумерацию строк, а во второй добавим автодополнение кода и подсветку ошибок.

Для начала составим список того, что наш редактор должен уметь:

  • Подсвечивать синтаксис
  • Отображать нумерацию строк
  • Показывать варианты автодополнения (расскажу во второй части)
  • Подсвечивать синтаксические ошибки (расскажу во второй части)

Это далеко не весь список того, какими свойствами должен обладать современный редактор кода, но именно об этом я хочу рассказать в этой небольшой серии статей.

MVP — простой текстовый редактор

На данном этапе проблем возникнуть не должно — растягиваем EditText на весь экран, указываем gravity , прозрачный background чтобы убрать полосу снизу, размер шрифта, цвет текста и т.д. Я люблю начинать с визуальной части, так мне становится проще понять чего не хватает в приложении, и над какими деталями ещё стоит поработать.

Читайте также:  Домен имя пользователя как заполнить outlook андроид

На этом этапе я так же сделал загрузку/сохранение файлов в память. Код приводить не буду, в интернете переизбыток примеров работы с файлами.

Подсветка синтаксиса

Как только мы ознакомились с требованиями к редактору, пора переходить к самому интересному.

Очевидно, чтобы контролировать весь процесс — реагировать на ввод, отрисовывать номера строк, нам придется писать CustomView наследуясь от EditText . Накидываем TextWatcher чтобы слушать изменения в тексте и переопределяем метод afterTextChanged , в котором и будем вызывать метод отвечающий за подсветку:

Q: Почему мы используем TextWatcher как переменную, ведь можно реализовать интерфейс прямо в классе?
A: Так уж получилось, что у TextWatcher есть метод который конфликтует c уже существующим методом у TextView :

Оба этих метода имеют одинаковое название и одинаковые аргументы, да и смысл вроде у них тот же, но проблема в том что метод onTextChanged у TextView вызовется вместе с onTextChanged у TextWatcher . Если проставить логи в тело метода, то увидим что onTextChanged вызовется дважды:

Это очень критично если мы планируем добавлять функционал Undo/Redo. Также нам может понадобится момент, в котором не будут работать слушатели, в котором мы сможем очищать стэк с изменениями текста. Мы ведь не хотим, чтобы после открытия нового файла можно было нажать Undo и получить совершенно другой текст. Хоть об Undo/Redo в этой статье говориться не будет, важно учитывать этот момент.

Соответственно, чтобы избежать такой ситуации можно использовать свой метод установки текста вместо стандартного setText :

Но вернёмся к подсветке.

Во многих языках программирования есть такая замечательная штука как RegEx, это инструмент позволяющий искать совпадения текста в строке. Рекомендую как минимум ознакомится с его базовыми возможностями, потому что рано или поздно любому программисту может понадобится «вытащить» какой-либо кусочек информации из текста.

Сейчас нам важно знать только две вещи:

  1. Pattern определяет что конкретно нам нужно найти в тексте
  2. Matcher будет пробегать по всему тексту в попытках найти то, что мы указали в Pattern

Может не совсем корректно описал, но принцип работы такой.

Т.к я пишу редактор для JavaScript, вот небольшой паттерн с ключевыми словами языка:

Конечно, слов тут должно быть гораздо больше, а ещё нужны паттерны для комментариев, строк, чисел и т.д. но моя задача заключается в демонстрации принципа, по которому можно найти нужный контент в тексте.

Далее с помощью Matcher мы пройдёмся по всему тексту и установим спаны:

Поясню: мы получаем объект Matcher у Pattern, и указываем ему область для поиска в символах (Соответственно с 0 по text.length это весь текст). Далее вызов matcher.find() вернёт true если в тексте было найдено совпадение, а с помощью вызовов matcher.start() и matcher.end() мы получим позиции начала и конца совпадения в тексте. Зная эти данные, мы можем использовать метод setSpan для раскраски определённых участков текста.

Существует много видов спанов, но для перекраски текста обычно используется ForegroundColorSpan .

Итак, запускаем!

Дело в том что метод setSpan работает медленно, сильно нагружая UI Thread, а учитывая что метод afterTextChanged вызывается после каждого введенного символа, писать код становится одним мучением.

Поиск решения

Первое что приходит в голову — вынести тяжелую операцию в фоновый поток. Но тяжелая операция тут это setSpan по всему тексту, а не регулярки. (Думаю, можно не объяснять почему нельзя вызывать setSpan из фонового потока).

Немного поискав тематических статей узнаем, что если мы хотим добиться плавности, придётся подсвечивать только видимую часть текста.

Точно! Так и сделаем! Вот только… как?

Оптимизация

Хоть я и упомянул что нас заботит только производительность метода setSpan , всё же рекомендую выносить работу RegEx в фоновой поток чтобы добиться максимальной плавности.

Нам нужен класс, который будет в фоне обрабатывать весь текст и возвращать список спанов.
Конкретной реализации приводить не буду, но если кому интересно то я использую AsyncTask работающий на ThreadPoolExecutor . (Да-да, AsyncTask в 2020)

Нам главное, чтобы выполнялась такая логика:

  1. В beforeTextChanged останавливаем Task который парсит текст
  2. В afterTextChanged запускаем Task который парсит текст
  3. По окончанию своей работы, Task должен вернуть список спанов в TextProcessor , который в свою очередь подсветит только видимую часть

И да, спаны тоже будем писать свои собственные:

Таким образом, код редактора превращается в нечто подобное:

Т.к конкретной реализации обработки в фоне я не показал, представим что мы написали некий JavaScriptStyler , который в фоне будет делать всё тоже самое что мы делали до этого в UI Thread — пробегать по всему тексту в поисках совпадений и заполнять список спанов, а в конце своей работы вернёт результат в setSpansCallback . В этот момент запустится метод updateSyntaxHighlighting , который пройдётся по списку спанов и отобразит только те, что видны в данный момент на экране.

Читайте также:  Fps шутеры для андроид

Как понять, какой текст попадает в видимую область?

Буду ссылаться на эту статью, там автор предлагает использовать примерно такой способ:

И он работает! Теперь вынесем topVisibleLine и bottomVisibleLine в отдельные методы и добавим пару дополнительных проверок, на случай если что-то пойдёт не так:

Последнее что остаётся сделать — пройтись по полученному списку спанов и раскрасить текст:

Не пугайтесь страшного if ‘а, он всего лишь проверяет попадает ли спан из списка в видимую область.

Ну что, работает?

Работает, вот только при редактировании текста спаны не обновляются, исправить ситуацию можно очистив текст от всех спанов перед наложением новых:

Ещё один косяк — после закрытия клавиатуры кусок текста остаётся неподсвеченным, исправляем:

Главное не забыть указать adjustResize в манифесте.

Скроллинг

Говоря про скроллинг снова буду ссылаться на эту статью. Автор предлагает ждать 500 мс после окончания скроллинга, что противоречит моему чувству прекрасного. Я не хочу дожидаться пока прогрузится подсветка, я хочу видеть результат моментально.

Так же автор приводит аргумент что запускать парсер после каждого «проскроленного» пикселя затратно, и я полностью с этим согласен (вообще рекомендую полностью ознакомится с его статьей, она небольшая, но там много интересного). Но дело в том, что у нас уже есть готовый список спанов, и нам не нужно запускать парсер.

Достаточно вызывать метод отвечающий за обновление подсветки:

Нумерация строк

Если мы добавим в разметку ещё один TextView то будет проблематично их между собой связать (например, синхронно обновлять размер текста), да и если у нас большой файл то придется полностью обновлять текст с номерами после каждой введенной буквы, что не очень круто. Поэтому будем использовать стандартные средства любой CustomView — рисование на Canvas в onDraw , это и быстро, и не сложно.

Для начала определим что будем рисовать:

  • Номера строк
  • Вертикальную линию, отделяющую поле ввода от номеров строк

Предварительно необходимо вычислить и установить padding слева от редактора, чтобы не было конфликтов с напечатанным текстом.

Для этого напишем функцию, которая будет обновлять отступ перед отрисовкой:

Для начала мы узнаем кол-во строк в EditText (не путать с кол-вом » \n » в тексте), и берем кол-во символов от этого числа. Например, если у нас 100 строк, то переменная gutterDigitCount будет равна 3, потому что в числе 100 ровно 3 символа. Но допустим, у нас всего 1 строка — а значит отступ в 1 символ будет визуально казаться маленьким, и для этого мы используем переменную count , чтобы задать минимально отображаемый отступ в 3 символа, даже если у нас меньше 100 строк кода.

Эта часть была самая запутанная из всех, но если вдумчиво прочитать несколько раз (поглядывая на код), то всё станет понятно.

Далее устанавливаем отступ предварительно вычислив widestNumber и widestWidth .

Приступим к рисованию

К сожалению, если мы хотим использовать стандартный андройдовский перенос текста на новую строку то придется поколдовать, что займет у нас много времени и ещё больше кода, которого хватит на целую статью, поэтому дабы сократить ваше время (и время модератора хабра), мы включим горизонтальный скроллинг, чтобы все строки шли одна за другой:

Ну а теперь можно приступать к рисованию, объявим переменные с типом Paint :

Где-нибудь в init блоке установим цвет текста и цвет разделителя. Важно помнить, что если вы поменяйте шрифт текста, то шрифт Paint ‘а придется применять вручную, для этого советую переопределить метод setTypeface . Аналогично и с размером текста.

После чего переопределяем метод onDraw :

Смотрим на результат

Выглядит круто.

Что же мы сделали в onDraw ? Перед вызовом super -метода мы обновили отступ, после чего отрисовали номера только в видимой области, ну и под конец провели вертикальную линию, визуально отделяющую нумерацию строк от редактора кода.

Для красоты можно ещё перекрасить отступ в другой цвет, визуально выделить строку на которой находится курсор, но это я уже оставлю на ваше усмотрение.

Заключение

В этой статье мы написали отзывчивый редактор кода с подсветкой синтаксиса и нумерацией строк, а в следующей части добавим удобное автодополнение кода и подсветку синтаксических ошибок прямо во время редактирования.

Также оставлю ссылку на исходники моего редактора кода на GitHub, там вы найдёте не только те фичи о которых я рассказал в этой статье, но и много других которые остались без внимания.

Задавайте вопросы и предлагайте темы для обсуждения, ведь я вполне мог что-то упустить.

Источник

Оцените статью