Android dalvik java version

Как Java 8 поддерживается в Android

Привет, Хабр! Предлагаю вашему вниманию перевод замечательной статьи из цикла статей небезызвестного Джейка Вортона о том, как происходит поддержка Андроидом Java 8.

Оригинал статьи лежит тут

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

Это довольно сложная тема. Для начала нужно определиться, что мы вообще подразумеваем под «поддержкой Java в Android», ведь в одной версии языка может быть много всего: фичи (лямбды, например), байткод, тулзы, APIs, JVM и так далее.

Когда говорят о поддержке Java 8 в Android, обычно подразумевают поддержку фичей языка. Итак, начнем с них.

Лямбды

Одним из главных нововведений Java 8 были лямбды.
Код стал более лаконичным и простым, лямбды избавили нас от необходимости писать громоздкие анонимные классы, используя интерфейс с единственным методом внутри.

После компиляции этого, используя javac и легаси dx tool , мы получим следующую ошибку:

Эта ошибка происходит из-за того, что лямбды используют новую инструкцию в байткоде — invokedynamic , которая была добавлена в Java 7. Из текста ошибки можно увидеть, что Android поддерживает ее только начиная с 26 API (Android 8).

Звучит не очень, ведь вряд ли кто-то будет выпускать приложение с 26 minApi. Чтобы это обойти, используется так называемый процесс десахаризации (desugaring), который делает возможным поддержку лямбд на всех версиях API.

История десахаризации

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

Изначально, например, для поддержки лямбд в Android разработчики подключали плагин Retrolambda. Он использовал тот же встроенный механизм, что и JVM, конвертируя лямбды в классы, но делал это в рантайме, а не во время компиляции. Сгенерированные классы были очень дорогими с точки зрения количества методов, но со временем, после доработок и улучшений, этот показатель снизился до чего-то более-менее разумного.

Затем команда Android анонсировала новый компилятор, который поддерживал все фичи Java 8 и был более производительным. Он был построен поверх Eclipse Java компилятора, но вместо генерации Java-байткода генерировал Dalvik-байткод. Однако его производительность все равно оставляла желать лучшего.

Когда новый компилятор (к счастью) забросили, трансформатор Java байткода в Java байткод, который и выполнял дешугаринг, был интегрирован в Android Gradle Plugin из Bazel — системы сборки Google. И его производительность все равно была невелика, поэтому параллельно продолжался поиск более хорошего решения.

И вот нам представили новый dexer — D8, который должен был заменить dx tool . Десахаризация теперь выполнялась во время конвертации скомпилированных JAR-файлов в .dex (dexing). D8 сильно выигрывает в производительности по сравнению с dx , и, начиная с Android Gradle Plugin 3.1 он стал dexer’ом по умолчанию.

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

Чтобы посмотреть, как D8 преобразовал лямбду, можно использовать dexdump tool , который входит в Android SDK. Она выведет довольно много всего, но мы заострим внимание только на этом:

Если вы до этого еще не читали байткод, не волнуйтесь: многое из того, что здесь написано, можно понять интуитивно.

В первом блоке наш main метод с индексом 0000 получает ссылку от поля INSTANCE на класс Java8$1 . Этот класс был сгенерирован во время десахаризации . Байткод метода main тоже нигде не содержит упоминаний о теле нашей лямбды, поэтому, скорее всего, она связана с классом Java8$1 . Индекс 0002 затем вызывает static-метод sayHi , используя ссылку на INSTANCE . Методу sayHi требуется Java8$Logger , поэтому, похоже, Java8$1 имплементирует этот интерфейс. Мы можем убедиться в этом тут:

Флаг SYNTHETIC означает, что класс Java8$1 был сгенерирован и список интерфейсов, которые он включает, содержит Java8$Logger .
Этот класс и представляет собой нашу лямбду. Если вы посмотрите на реализацию метода log , то не увидите тело лямбды.

Вместо этого внутри вызывается static метод класса Java8 — lambda$main$0 . Повторюсь, этот метод представлен только в байткоде.

Флаг SYNTHETIC снова говорит нам, что этот метод был сгенерирован, и его байткод как раз содержит тело лямбды: вызов System.out.println . Причина, по которой тело лямбды находится внутри Java8.class, простая — ей может понадобиться доступ к private членам класса, к которым сгенерированный класс иметь доступа не будет.

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

Преобразование исходников — Source Transformation

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

Возьмем за основу тот же класс с лямбдой:

Сначала тело лямбды перемещается в package private метод.

Затем генерируется класс, имплементирующий интерфейс Logger , внутри которого выполняется блок кода из тела лямбды.

Далее создается синглтон инстанс Java8$1 , который хранится в static переменной INSTANCE .

Вот итоговый задешугаренный класс, который может использоваться на всех версиях API:

Если вы посмотрите на сгенерированный класс в байткоде Dalvik, то не найдете имен по типу Java8$1 — там будет что-то вроде -$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY . Причина, по которой для класса генерируется такой нейминг, и в чем его плюсы, тянет на отдельную статью.

Нативная поддержка лямбд

Когда мы использовали dx tool , чтобы скомпилировать класс, содержащий лямбды, сообщение об ошибке говорило, что это будет работать только с 26 API.

Поэтому кажется логичным, что если мы попробуем скомпилировать это с флагом —min-api 26 , то десахаризации происходить не будет.

Однако если мы сдампим .dex файл, то в нем все равно можно будет обнаружить -$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY . Почему так? Это баг D8?

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

Внутри метода main мы снова видим invokedynamic по индексу 0 . Второй аргумент в вызове — 0 — индекс ассоциируемого с ним bootstrap метода.

Вот список bootstrap методов:

Здесь bootstrap метод назван metafactory в классе java.lang.invoke.LambdaMetafactory . Он живет в JDK и занимается созданием анонимных классов налету (on-the-fly) в рантайме для лямбд так же, как и D8 генерит их в компайлтайме.

Если взглянуть на документацию Android к java.lang.invoke
или на исходники AOSP к java.lang.invoke , увидим, что в рантайме этого класса нет. Вот поэтому дешугаринг всегда происходит во время компиляции, независимо от того, какой у вас minApi. VM поддерживает байткод инструкцию, похожую на invokedynamic , но встроенный в JDK LambdaMetafactory недоступен для использования.

Читайте также:  Не помню пароль от андроида после перезагрузки

Method References

Вместе с лямбдами в Java 8 добавили ссылки на методы — это эффективный способ создать лямбду, тело которой ссылается на уже существующий метод.

Наш интерфейс Logger как раз является таким примером. Тело лямбды ссылалось на System.out.println . Давайте превратим лямбду в метод референc:

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

Вместо вызова сгенерированного Java8.lambda$main$0 , который содержит вызов System.out.println , теперь System.out.println вызывается напрямую.

Класс с лямбдой больше не static синглтон, а по индексу 0000 в байткоде видно, что мы получаем ссылку на PrintStream — System.out , который затем используется для того, чтобы вызвать на нем println .

В итоге наш класс превратился в это:

Default и static методы в интерфейсах

Еще одним важным и серьезным изменением, которое принесла Java 8, стала возможность объявлять default и static методы в интерфейсах.

Все это тоже поддерживается D8. Используя те же инструменты, что и ранее, несложно увидеть задешугаренную версию Logger’a с default и static методами. Одно из различий с лямбдами и method references в том, что дефолтные и статик методы реализованы в Android VM и, начиная с 24 API, D8 не будет дешугарить их.

Может, просто использовать Kotlin?

Читая статью, большинство из вас, наверное, подумали о Kotlin. Да, он поддерживает все фичи Java 8, но реализованы они kotlinc точно так же, как и D8, за исключением некоторых деталей.

Поэтому поддержка Андроидом новых версий Java до сих пор очень важна, даже если ваш проект на 100% написан на Kotlin.

Не исключено, что в будущем Kotlin перестанет поддерживать байткод Java 6 и Java 7. IntelliJ IDEA, Gradle 5.0 перешли на Java 8. Количество платформ, работающих на более старых JVM, сокращается.

Desugaring APIs

Все это время я рассказывал про фичи Java 8, но ничего не говорил о новых API — стримы, CompletableFuture , date/time и так далее.

Возвращаясь к примеру с Logger’ом, мы можем использовать новый API даты/времени, чтобы узнать, когда сообщения были отправлены.

Снова компилируем это с помощью javac и преобразуем его в байткод Dalvik с D8, который дешугарит его для поддержки на всех версиях API.

Можете даже запушить это на свой девайс, чтобы убедиться, что оно работает.

Если на этом устройстве API 26 и выше, появится месседж Hello. Если нет — увидим следующее:

D8 справился с лямбдами, метод референсами, но не сделал ничего для работы с LocalDateTime , и это очень печально.

Разработчикам приходится использовать свои собственные реализации или обертки над date/time api, либо использовать библиотеки по типу ThreeTenBP для работы со временем, но почему то, что ты можешь написать руками, не может сделать D8?

Эпилог

Отсутствие поддержки всех новых API Java 8 остается большой проблемой в экосистеме Android. Ведь вряд ли каждый из нас может позволить указать 26 min API в своем проекте. Библиотеки, поддерживающие и Android и JVM, не могут позволить себе использовать API, представленный нам 5 лет назад!

И даже несмотря на то, что саппорт Java 8 теперь является частью D8, каждый разработчик все равно должен явно указывать source и target compatibility на Java 8. Если вы пишете собственные библиотеки, то можете усилить эту тенденцию, выкладывая библиотеки, которые используют Java 8 байткод (даже если вы не используете новые фичи языка).

Над D8 ведется очень много работ, поэтому, кажется, в будущем с поддержкой фичей языка все будет ок. Даже если вы пишете только на Kotlin, очень важно заставлять команду разработки Android поддерживать все новые версии Java, улучшать байткод и новые API.

Этот пост — письменная версия моего выступления Digging into D8 and R8.

Источник

Как работает Android, часть 2

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

Говоря про Unix- и Linux-корни Android, нужно вспомнить и о других проектах операционных систем, влияние которых можно проследить в Android, хотя они и не являются его прямыми предками.

Я уже упомянул про BeOS, в наследство от которой Android достался Binder.

Plan 9 from Bell Labs

Plan 9 — потомок Unix, логическое продолжение, развитие его идей и доведение их до совершенства. Plan 9 был разработан в Bell Labs той же командой, которая создала Unix и C — над ним работали такие люди, как Ken Thompson, Rob Pike, Dennis Ritchie, Brian Kernighan, Tom Duff, Doug McIlroy, Bjarne Stroustrup, Bruce Ellis и другие.

В Plan 9 взаимодействие процессов между собой и с ядром системы реализовано не через многочисленные системные вызовы и механизмы IPC, а через виртуальные текстовые файлы и файловые системы (развитие принципа Unix «всё — это файл»). При этом каждая группа процессов «видит» файловую систему по-своему (пространства имён, namespaces), что позволяет запускать разные части системы в разном окружении.

Например, чтобы получить позицию курсора мыши, приложения читают текстовый файл /dev/mouse . Оконная система rio предоставляет каждому приложению свою версию этого файла, в которой видны только события, относящиеся к окну этого приложения, и используются локальные по отношению к окну координаты. Сама rio читает события «настоящей» мыши через такой же файл /dev/mouse — в том виде, в котором его видит она. Если она запущена напрямую, этот файл предоставляется ядром и действительно описывает движения настоящей мыши, но она может быть совершенно прозрачно запущена в качестве приложения под другой копией rio, без какой-то специальной поддержки с её стороны.

Plan 9 полностью поддерживает доступ к удалённым файловым системам (используется собственный протокол 9P, кроме того, поддерживаются FTP и SFTP), что позволяет программам совершенно прозрачно получать доступ к удалённым файлам, интерфейсам и ресурсам. Такая «родная» сетевая прозрачность превращает Plan 9 в распределённую операционную систему — пользователь может физически находиться за одним компьютером, на котором запущена rio, запускать приложения на нескольких других, использовать в них файлы, хранящиеся на файловом сервере и выполнять вычисления на CPU-сервере — всё это полностью прозрачно и без специальной поддержки со стороны каждой из частей системы.

За счёт красиво спроектированной архитектуры Plan 9 значительно проще и меньше, чем Unix — на самом деле ядро Plan 9 даже в несколько раз меньше известного микроядра Mach.

Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away.

Несмотря на техническое превосходство и наличие слоя совместимости с Unix, Plan 9 не получил широкого распространения. Тем не менее, многие идеи и технологии из Plan 9 получили распространение и были реализованы в других системах. Самая известная из них — кодировка , которая была разработана в Plan 9 для обеспечения полной поддержки Unicode при сохранении обратной совместимости с ASCII — стала общепринятым стандартом.

Читайте также:  Отличие mvp от mvvm android

Больше всего идей и технологий из Plan 9 реализовано в Linux:

  • файловая система /proc (procfs)
  • системный вызов clone (аналог rfork из Plan 9)
  • поддержка пространств имён монтирования (mount namespaces)
  • поддержка файловых систем, реализованных в пользовательском пространстве (filesystem in userspace, FUSE)
  • поддержка протокола 9P

Многое из этого используется, в том числе, и в Android. Кроме того, в Android реализован механизм intent’ов, похожий на plumber из Plan 9; о нём я расскажу в следующей статье.

Про Plan 9 можно узнать подробнее на сайте plan9.bell-labs.com (сохранённая копия в Wayback Machine), или его зеркале 9p.io

Inferno

Plan 9 получил продолжение в виде проекта Inferno, тоже разработанного в Bell Labs. К таким свойствам Plan 9, как простота и распределённость, Inferno добавляет переносимость. Программы для Inferno пишутся на высокоуровневом языке Limbo и выполняются — с использованием just-in-time компиляции — встроенной в ядро Inferno виртуальной машиной.

Inferno настолько переносим, что может запускаться

  • на процессорах разных архитектур: ARM, x86, IBM PowerPC, Sun SPARC, 6SGI MIPS и HP ,
  • как самостоятельная операционная система или как программа под Plan 9, Unix, Windows 95 и Windows NT.

При этом приложениям, запущенным внутри Inferno, предоставляется совершенно одинаковое окружение.

Inferno получил ещё меньше распространения и известности, чем Plan 9. С другой стороны, Inferno во многом предвосхитил Android, самую популярную операционную систему на свете.

Danger

Компания Danger Research Inc. была сооснована Энди Рубином (Andy Rubin) в 1999 году, за 4 года до сооснования им же Android Inc. в 2004 году.

В 2002 году Danger выпустили свой смартфон Danger Hiptop. Многие из разработчиков Danger впоследствии работали над Android, поэтому неудивительно, что его операционная система была во многом похожа на Android. Например, в ней были реализованы:

  • «всегда запущенные» приложения, написанные на Java,
  • полноценный веб-браузер,
  • веб-приложения,
  • мессенджер,
  • email client,
  • облачная синхронизация,
  • магазин сторонних приложений.

Подробнее о Danger можно прочитать в статье Chris DeSalvo, одного из разработчиков, под названием The future that everyone forgot.

Хотя использование высокоуровневых языков для серьёзной разработки сейчас уже никого не удивляет, из популярных операционных систем только у Android «родной» язык — высокоуровневая Java (с другой стороны, здесь можно вспомнить веб с его JavaScript, .NET для Windows и относительно высокоуровневый — но полностью компилируемый в нативный код и не использующий сборку мусора — Swift).

Несмотря на кажущиеся недостатки («Java сочетает в себе красоту синтаксиса C++ со скоростью выполнения питона»), Java обладает множеством преимуществ.

Во-первых, Java — самый популярный (с большим отрывом) язык программирования. У Java огромная экосистема библиотек и инструментов разработки (в том числе систем сборки и IDE). Про Java написано множество статей, книг и документации. Наконец, существует множество квалифицированных Java-разработчиков.

Программы на Java, как и на многих других высокоуровневых языках, переносимы между операционными системами и архитектурами процессора («Write once, run anywhere»). Практически это проявляется, например, в том, что приложения для Android работают без перекомпиляции на устройствах любой архитектуры (Android поддерживает ARM, ARM64, x86, x86–64 и MIPS).

В отличие от низкоуровневых языков вроде C и C++, использующих ручное управление памятью, в Java память автоматически управляется средой времени выполнения (runtime environment). Программа на Java даже не имеет прямого доступа к памяти, что автоматически предотвращает несколько классов ошибок, часто приводящих к падениям и уязвимостям в программах, написанных на низкоуровневых языках — невозможны «висячие ссылки» (из-за которых происходит ), разыменование нулевого указателя (при попытке это сделать выбрасывается NullPointerException ), чтение неинициализированной памяти и выход за границы массива.

Использование полноценной сборки мусора (по сравнению с automatic reference counting) избавляет программиста от всех проблем и сложностей с циклическими ссылками и позволяет реализовывать ещё более продвинутые (advanced) зависимости между объектами.

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

Running Java is ART

В отличие от большинства других высокоуровневых языков, программы на Java не распространяются в виде исходного кода, а компилируются в промежуточный формат (байткод, bytecode), который представляет собой исполняемый бинарный код для специального процессора.

Хотя делаются попытки создать физический процессор, который исполнял бы Java-байткод напрямую, в подавляющем большинстве случаев в качестве такого процессора используется эмулятор — Java virtual machine (JVM). Обычно используется реализация от Oracle/OpenJDK под названием HotSpot.

В Android используется собственная реализация под названием Android Runtime (ART), специально оптимизированная для работы на мобильных устройствах. В старых версиях Android (до 5.0 Lollipop) вместо ART использовалась другая реализация под названием Dalvik.

И в Dalvik, и в ART используется собственный формат байткода и собственный формат файлов, в которых хранится байткод — DEX (Dalvik executable). В отличие от в «обычной джаве», весь Java-код приложения обычно компилируется в один classes.dex . При сборке Android-приложения Java-код сначала компилируется обычным компилятором Java в , а потом конвертируется в специальной утилитой (возможно и обратное преобразование).

И HotSpot, и Dalvik, и ART дополнительно оптимизируют выполняемый код. Все три используют just-in-time compilation (JIT), то есть во время выполнения компилируют байткод в куски полностью нативного кода, который выполняется напрямую. Кроме очевидного выигрыша в скорости, это позволяет оптимизировать код для выполнения на конкретном процессоре, не отказываясь от полной переносимости байткода.

Кроме того, ART может компилировать байткод в нативный код заранее, а не во время выполнения (ahead-of-time compilation) — причём система автоматически планирует эту компиляцию на то время, когда устройство не используется и подключено к зарядке (например, ночью). При этом ART учитывает данные, собранные профилировщиком во время предыдущих запусков этого кода (profile-guided optimization). Такой подход позволяет дополнительно оптимизировать код под специфику работы конкретного приложения и даже под особенности использования этого приложения именно этим пользователем.

В результате всех этих оптимизаций производительность Java-кода на Android не сильно уступает производительности низкоуровневого кода (на C/C++), а в некоторых случаях и превосходит её.

Java-байткод, в отличие от обычного исполняемого кода, использует объектную модель Java — то есть в байткоде явно записываются такие вещи, как классы, методы и сигнатуры. Это делает возможной компиляцию других языков в Java-байткод, что позволяет написанным на них программам исполняться на виртуальной машине Java и быть в той или иной степени совместимыми (interoperable) с Java.

Читайте также:  Vit registry для андроид

Существуют как JVM-реализации независимых языков — например, Jython для Python, JRuby для Ruby, Rhino для JavaScript и диалект Lisp Clojure — так и языки, исходно разработанные для компиляции в Java-байткод и выполнения на JVM, самые известные из которых — Groovy, Scala и Kotlin.

Самый новый из них, Kotlin, специально разработанный для идеальной совместимости с Java и обладающий гораздо более приятным синтаксисом (похожим на Swift), поддерживается Google как официальный язык разработки под Android наравне с Java.

Несмотря на все преимущества Java, в некоторых случаях всё-таки желательно использовать низкоуровневый язык — например, для реализации критичного по производительности компонента, такого как браузерный движок, или чтобы использовать существующую нативную библиотеку. Java позволяет вызывать нативный код через Java Native Interface (JNI), и Android предоставляет специальные средства для нативной разработки — Native Development Kit (NDK), в который входят в том числе заголовочные файлы, компилятор (Clang), отладчик (LLDB) и система сборки.

Хотя NDK в основном ориентирован на использование C/C++, с его помощью можно писать под Android и на других языках — в том числе Rust, Swift, Python, JavaScript и даже Haskell. Больше того, есть даже возможность портировать iOS-приложения (написанные на или Swift) на Android практически без изменений.

О безопасности

Классический Unix

Модель безопасности в классическом Unix основана на системе UID/GID — специальных номеров, которые ядро хранит для каждого процесса. Процессам с одинаковым UID разрешён доступ друг к другу, процессы с разным UID защищены друг от друга. Аналогично ограничивается доступ к файлам.

По смыслу каждый UID (user ID) соответствует своему пользователю — во времена создания Unix была нормальной ситуация, когда один компьютер одновременно использовался множеством людей. Таким образом, в Unix процессы и файлы разных людей были защищены друг от друга. Чтобы разрешить общий доступ к некоторым файлам, пользователи объединялись в группы, которым и соответствовал GID (group ID).

При этом всем программам, запускаемым пользователем, даётся полный доступ ко всему, к чему есть доступ у этого пользователя. Собственно, поскольку пользователь не может общаться с ядром напрямую, а взаимодействует с компьютером через shell и другие процессы — права пользователя и есть права программ, запущенных от его имени.

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

В Unix есть и исключение из ограничений доступа — UID 0, который принято называть root. У него есть доступ ко всему в системе, и никакие ограничения на него не распространяются. Этот аккаунт использовался системным администратором; кроме того, под UID 0 запускаются многие системные сервисы.

В современном Linux эта модель была значительно расширена и обобщена, в том числе появились capabilities, позволяющие «получить часть root-прав», и реализующая мандатное управление доступом (mandatory access control, MAC) подсистема SELinux, которая позволяет дополнительно ограничить права (в том числе права root-процессов).

Всё изменилось

За несколько десятков лет, прошедших с создания Unix до создания Android, практика использования компьютеров («вычислителей») значительно изменилась.

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

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

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

Android

Хотя часть Android-приложений поставляется с системой — например, такие стандартные приложения, как Калькулятор, Часы и Камера — большинство приложений пользователи устанавливают из сторонних источников. Самый известный из них — Google Play Store, но есть и другие, например, F-Droid, Amazon Appstore, Яндекс.Store, китайские Baidu App Store, Xiaomi App Store, Huawei App Store и т.д. Кроме того, Android позволяет вручную устанавливать произвольные приложения из APK-файлов (это называют sideloading).

Как и другие Unix-подобные системы, Android использует для ограничения доступа существующий механизм UID/GID. При этом — в отличие от традиционного использования, когда UID соответствуют пользователям — в Android разные UID соответствуют разным приложениям. Поскольку процессы разных приложений запускаются с разными UID, уже на уровне ядра приложения защищены и изолированы друг от друга и не имеют доступа к системе и данным пользователя. Это образует песочницу (Application Sandbox) и позволяет пользователю устанавливать любые приложения без необходимости доверять им.

Чтобы всё-таки получить доступ к пользовательским данным, камере, совершению звонков и т.п., приложение должно получить от пользователя разрешение (permission). Некоторые из разрешений существуют в виде GID, в которые приложение добавляется, когда получает это разрешение — например, получение разрешения ACCESS_FM_RADIO помещает приложение в группу media , что позволяет ему получить доступ к файлу /dev/fm . Остальные существуют только на более высоком уровне (в виде записей в файле packages.xml ) и проверяются другими компонентами системы при обращении к высокоуровневому API через Binder.

Небольшая часть системных сервисов в Android запускается под UID 0, то есть root, но большинство используют специально выделенные номера UID, повышая при необходимости свои права с помощью Linux capabilities. Кроме того, Android использует SELinux — использование SELinux в Android называют SEAndroid — для ещё большего ограничения того, какие действия разрешено выполнять приложениям и системным сервисам.

Обычно Android не предоставляет пользователю прямой доступ к root-аккаунту, но в некоторых случаях у него есть возможность этот доступ получить. Как это происходит, зачем это нужно и какими опасностями это грозит я расскажу позднее.

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

Источник

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