- Jake Wharton
- Android’s Java 9, 10, 11, and 12 Support
- Java 9
- Concise Try With Resources
- Anonymous Diamond
- Private Interface Methods
- String Concat
- Java 10
- Java 11
- Nestmates
- Java 12
- Пишем, собираем и запускаем HelloWorld для Android в блокноте. Java 8 и Android N
- Вступление
- Подготовка
- Написание проекта
- Сборка
- Подготовка путей
- Подготовка к компиляции
- Запуск
- Заключение
- Мои параметры
Jake Wharton
Android’s Java 9, 10, 11, and 12 Support
27 November 2018
Note: This post is part of a series on D8 and R8, Android’s new dexer and optimizer, respectively. For an intro to D8 read “Android’s Java 8 support”.
The first post in this series explored Android’s Java 8 support. Having support for the language features and APIs of Java 8 is table stakes at this point. We’re not quite there with the APIs yet, sadly, but D8 has us covered with the language features. There’s a future promise for the APIs which is essential for the health of the ecosystem.
A lot of the reaction to the previous post echoed that Java 8 is quite old. The rest of the Java ecosystem is starting to move to Java 11 (being the first long-term supported release after 8) after having toyed with Java 9 and 10. I was hoping for that reaction because I mostly wrote that post so that I could set up this one.
With Java releases happening more frequently, Android’s yearly release schedule and delayed uptake of newer language features and APIs feels more painful. But is it actually the case that we’re stuck with those of Java 8? Let’s take a look at the Java releases beyond 8 and see how the Android toolchain fares.
Java 9
The last release on the 2 — 3 year schedule, Java 9 contains a few new language features. None of them are major like lambdas were. Instead, this release focused on cleaning up some of the sharp edges on existing features.
Concise Try With Resources
Prior to this release the try-with-resources construct required that you define a local variable (such as try (Closeable bar = foo.bar()) ). But if you already have a Closeable , defining a new variable is redundant. As such, this release allows you to omit declaring a new variable if you already have an effectively-final reference.
This feature is implemented entirely in the Java compiler so D8 is able to dex it for Android.
Unlike the lambdas or static interface methods of Java 8 which required special desugaring, this Java 9 feature becomes available to all API levels for free.
Anonymous Diamond
Java 7 introduced the diamond operator which allowed omitting a generic type from the initializer if it could be inferred from the variable type.
This cut down on redundant declarations, but it wasn’t available for use on anonymous classes. With Java 9 that is now supported.
Once again this is entirely implemented in the Java compiler so the resulting bytecode is as if String was explicitly specified.
Because there is nothing interesting in the bytecode, D8 handles this without issue.
Yet another language feature available to all API levels for free.
Private Interface Methods
Interfaces with multiple static or default methods can often lead to duplicated code in their bodies. If these methods were part of a class and not an interface private helper functions could be extracted. Java 9 adds the ability for interfaces to contain private methods which are only accessible to its static and default methods.
This is the first language feature that requires some kind of support. Prior to this release, the private modifier was not allowed on an interface member. Since D8 is already responsible for desugaring default and static methods, private methods were straightforward to include using the same technique.
Static and default methods are supported natively in ART as of API 24. When you pass —min-api 24 for this example, the static method is not desugared. Curiously, though, the private static method is also not desugared.
We can see that the getHey() method’s access flags still contain both PRIVATE and STATIC . If you add a main method which calls hey() and push this to a device it will actually work. Despite being a feature added in Java 9, ART allows private interface members since API 24!
Those are all the language features of Java 9 and they all already work on Android. How about that.
The APIs of Java 9, though, are not yet included in the Android SDK. A new process API, var handles, a version of the Reactive Streams interfaces, and collection factories are just some of those which were added. Since libcore (which contains implementation of java.* ) and ART are developed in AOSP, we can peek and see that work is already underway towards supporting Java 9. Once included included in the SDK, some of its APIs will be candidates for desugaring to all API levels.
String Concat
The new language features and APIs of a Java release tend to be what we talk about most. But each release is also an opportunity to optimize the bytecode which is used to implement a feature. Java 9 brought an optimization to a ubiquitous language feature: string concatenation.
If we take this fairly innocuous piece of code and compile it with Java 8 the resulting bytecode will use a StringBuilder .
The bytecode contains the code we otherwise would have written if the language didn’t allow simple concatenation.
If we change the compiler to Java 9, however, the result is very different.
The entire StringBuilder usage has been replaced with a single invokedynamic bytecode! The behavior here is similar to how native lambdas work on the JVM which was discussed in the last post.
At runtime, on the JVM, the JDK class StringConcatFactory is responsible for returning a block of code which can efficiently concatenate the arguments and constants together. This allows the implementation to change over time without the code having to be recompiled. It also means that the StringBuilder can be pre-sized more accurately since the argument’s lengths can be queried.
If you want to learn more about why this change was made, Aleksey ShipilГ«v gave a great presentation on the motivations, implementation, and resulting benchmarks of the change.
Since the Android APIs don’t yet include anything from Java 9, there is no StringConcatFactory available at runtime. Thankfully, just like it did for LambdaMetafactory and lambdas, D8 is able to desugar StringConcatFactory for concatenations.
This means that all of the language features of Java 9 can be used on all API levels of Android despite changes in the bytecode that the Java compiler emits.
But Java is now on a six-month release schedule making Java 9 actually two versions old. Can we keep it going with newer versions?
Java 10
The only language feature of Java 10 was called local-variable type inference. This allows you to omit the type of local variable by replacing it with var when that type can be inferred.
This is another feature implemented entirely in the Java compiler.
No new bytecodes or runtime APIs are required for this feature to work and so it can be used for Android just fine.
Of course, like the versions of Java before it, there are new APIs in this release such as Optional.orElseThrow , List.copyOf , and Collectors.toUnmodifiableList . Once added to the Android SDK in a future API level, these APIs can be trivially desugared to run on all API levels.
Java 11
Local-variable type inference was enhanced in Java 11 to support its use on lambda variables. You don’t see types used in lambda parameters often so a lot of people don’t even know this syntax exists. This is useful when you need to provide an explicit type to help type inference or when you want to use a type-annotation on the parameter.
Just like Java 10’s local-variable type inference this feature is implemented entirely in the Java compiler allowing it to work on Android.
New APIs in Java 11 include a bunch of new helpers on String , Predicate.not , and null factories for Reader , Writer , InputSteam , and OutputStream . Nearly all of the API additions in this release could be trivially desugared once available.
A major API addition to Java 11 is the new HTTP client, java.net.http . This client was previously available experimentally in the jdk.incubator.http package since Java 9. This is a very large API surface and implementation which leverages CompletableFuture extensively. It will be interesting to see whether or not this even lands in the Android SDK let alone is available via desugaring.
Nestmates
Like Java 9 and its string concatenation bytecode optimization, Java 11 took the opportunity to fix a long-standing disparity between Java’s source code and its class files and the JVM: nested classes.
In Java 1.1, nested classes were added to the language but not the class specification or JVM. In order to work around the lack of support in class file, nesting classes in a source file instead creates sibling classes which use a naming convention to convey nesting.
Compiling this with Java 10 or earlier will produce two class files from a single source file.
As far as the JVM is concerned, these classes have no relationship except that they exist in the same package.
This illusion mostly works. Where it starts to break down is when one of the classes needs to access something that is private in the other.
When these classes are made siblings, Outer$Inner.sayHi() is unable to access Outer.name because it is private to another class.
In order to work around this problem and maintain the nesting illusion, the Java compiler adds a package-private synthetic accessor method for any member accessed across this boundary.
This is visible in the compiled class file for Outer .
Historically this has been at most a small annoyance on the JVM. For Android, though, these synthetic accessor methods contribute to the method count in our dex files, increase APK size, slow down class loading and verification, and degrade performance by turning a field lookup into a method call!
In Java 11, the class file format was updated to introduce the concept of nests to describe these nesting relationships.
The output here has been trimmed significantly, but the two class files are still produced except without an access$000 in Outer and with new NestMembers and NestHost attributes. These allow the VM to enforce a level of access control between package-private and private called nestmates. As a result, Inner can directly access Outer ’s name field.
ART does not understand the concept of nestmates so it needs to be desugared back into synthetic accessor methods.
Unfortunately, at the time of writing, this does not work. The version of ASM, the library used to read Java class files, predates the final implementation of nestmates. Beyond that, though, D8 does not support desugaring of nest mates. You can star the D8 feature request on the Android issue tracker to convey your support for this feature.
Without support for desugaring nestmates it is currently impossible to use Java 11 for Android. Even if you avoid accessing things across the nested boundary, the mere presence of nesting will fail to compile.
Without the APIs from Java 11 in the Android SDK, its single language feature of lambda parameter type inference isn’t compelling. For now, Android developers are not missing anything by being stuck on Java 10. That is, until we start looking forward…
Java 12
With a release date of March 2019, Java 12 is quickly approaching. The language features and APIs of this release have been in development for a few months already. Through early-access builds, we can download and experiment with these today.
In the current EA build, number 20, there are two new language features available: expression switch and string literals.
Once again, both of these features are implemented entirely as part of the Java compiler without any new bytecodes or APIs.
We can push this to a device to ensure that it actually works at runtime.
This works because the bytecode for expression switch is the same as the “regular” switch we would otherwise write with an uninitialized local, case blocks with break , and a separate return statement. And a multi-line string literal is just a string with newlines in it, something we’ve been able to do with escape characters forever.
As with all the other releases covered, there will be new APIs in Java 12 and it’s the same story as before. They’ll need added to the Android SDK and evaluated for desugaring capability.
Hopefully by the time Java 12 is actually released D8 will have implemented desugaring for Java 11’s nestmates. Otherwise the pain of being stuck on Java 10 will go up quite a bit!
Java 8 language features are here and desugaring of its APIs are coming (star the issue!). As the larger Java ecosystem moves forward to newer versions, it’s reassuring that every language feature between 8 and 12 is already available on Android.
With Java 9 work seemingly happening in AOSP (cross your fingers for Android P+1), hopefully we’ll have a new batch of APIs in the summer as candidates for desugaring. Once that lands, the smaller releases of Java will hopefully yield faster integration into the Android SDK.
Despite this, the end advice remains the same as in the last post. It’s vitally important to maintain pressure on Android for supporting the new APIs and VM features from newer versions of Java. Without APIs being integrated into the SDK they can’t (easily) be made available for use via desugaring. Without VM features being integrated into ART D8 bears a desugaring burden for all API levels instead of only to provide backwards compatibility.
Before these posts move on to talk about R8, the optimizing version of D8, the next one will cover how D8 works around version-specific and vendor-specific bugs in the VM.
(This post was adapted from a part of my Digging into D8 and R8 talk that was never presented. Watch the video and look out for future blog posts for more content like this.)
Источник
Пишем, собираем и запускаем HelloWorld для Android в блокноте. Java 8 и Android N
Два с половиной года назад я опубликовал статью Пишем, собираем и запускаем HelloWorld для Android в блокноте. Она стала пользоваться огромной популярностью и набрала около 80 000 просмотров. С появлением новых инструментов, таких как Jack ToolChain, возникла необходимость переиздания и обновления статьи.
Когда я начал изучать Android, захотелось полностью написать и скомпилировать Android-приложение вручную — без использования IDE. Однако эта задача оказалась непростой и заняла у меня довольно много времени. Но как оказалось — такой подход принёс большую пользу и прояснил многие тонкости, которые скрывают IDE.
Используя только блокнот, мы напишем совсем маленькое учебное Android-приложение. А затем скомпилируем его, соберём и запустим на устройстве — и всё через командную строку. Заинтересовало? Тогда прошу.
Вступление
Я был поражён, насколько сложным и запутанным является шаблонное приложение в Android Studio. Оно просто нагромождено ресурсами. И в меньшей степени — кодом и скриптами. Хотя всё что оно должно делать — это выводить на экран HelloWorld! Кроме того, в книгах и руководствах, которые я просмотрел, объясняется, как с помощью диалоговых окон создать IDEA-шный или эклипсовый HelloWorld — и от него уже идёт дальнейшее повествование. А что происходит «под капотом» — остаётся только гадать.
Мы создадим свой шаблонный проект, который идеально использовать для учебных целей. Там не будет ничего лишнего, только всё самое необходимое. А потом детально разберём, как его собрать и запустить на вашем Android-устройстве. В конце статьи будет ссылка на скачивание архива с итоговым проектом — если возникнут какие-то вопросы — можете свериться с ним.
Таким образом, вы будете на 100% знать и понимать состав вашего проекта и процесс его сборки. Хотя этот тестовый проект предназначен для обучения, при небольшой доработке его можно будет использовать как прочный фундамент для ваших реальных проектов.
Подготовка
Для начала нам нужно скачать и установить инструменты командной строки (command line tools). Ссылка на их скачивание находится внизу страницы, посвящённой Android Studio (https://developer.android.com/studio/index.html).
Android SDK 24 это как раз Android N (Nougat / 7). Принимаем условия, скачиваем установщик, запускаем его. Оставим всё по умолчанию. Он установится в директорию вида C:\Users\kciray\AppData\Local\Android\android-sdk. Запомните этот путь, там будут находится наши основные инструменты.
Далее, запускаете SDK Manager (из папки android-sdk) и тоже устанавливаете набор по-умолчанию. Там есть всё необходимое, включая новый Jack-компилятор. Также вам понадобится JDK 8.
Главное требование перед прочтением этой статьи — кроме установленного софта вы должны уже уметь запускать на вашем девайсе тот Helloworld, который поставляется вместе с Eclipse или Android Studio. Т.е. у вас должен быть настроен драйвер usb, включена отладка по usb на вашем девайсе и т.д… Или же создан и настроен эмулятор. Это совсем элементарные вещи, и их рассмотрение выходит за рамки данной статьи — в сети достаточно информации. Кстати прочитать пару глав из книг тоже будет не лишним — хотя бы понимать, как устроен манифест, ресурсы, да и вообще основы языка Java. А в этой статье я опишу то, о чём книги молчат.
Написание проекта
Для начала, создайте некоторую папку, где будет ваш проект. Назовём её testapp. В ней создайте ещё 3 папки — bin, res и src.
Создайте в testapp пустой текстовый файл и измените его имя на AndroidManifest.xml.
Добавьте в него следующее:
Тут всё просто. Мы намерены сделать приложение с именем TestApp, которое при старте запускает класс MainActivity. Осталось только написать этот небольшой класс — и приложение готово. Если нужно — отредактируйте в теге uses-sdk свойство android:targetSdkVersion — поставьте ту версию, которая у вас.
Далее — создадим простейший ресурс — строку Hello test app. Вообще-то мы могли обойтись и без ресурса, вставив эту строку прямо в Java код. Но некоторые шаги сборки работают с ресурсами, и чтобы увидеть интересные моменты — мы всё-таки поработаем с ними.
Давайте создадим в папке res папку values. Все ресурсы следует разбивать по папкам. Далее — в ней создадим пустой файл strings.xml, а в нём напишем:
Вот и все ресурсы, нам необходимые. Просто, не так ли? Далее создадим внутри src папку com, в ней папку example, потом ещё ниже по иерархии папку testapp — а там уже наш класс MainActivity.java. Добавим туда код:
Это простейшая Activity, которая содержит одну кнопку на весь экран. При нажатии на эту кнопку вызывается диалоговое окно, которое показывает строку из ресурсов. Обратите внимание на лямбду (конструкция v -> ). Jack ToolChain позволяет нам использовать многие возможности Java 8 под андроидом. Более подробно можете почитать на developer.android.com и source.android.com.
Структура каталогов должна получится такая
И это собственно всё, что нам было нужно для простейшего проекта. Для сравнения —
Собственно, автоматизация через gradle, работа с git и IDE — вещи очень важные, однако на этапе изучения Android мне бы очень хотелось от них абстрагироваться.
Сборка
Теперь же подходим к самому важному и сложному этапу. Мы будем много работать с командной строкой, поэтому рекомендую вам все команды, данные здесь, записывать в один файл и назвать его Compile.bat. В конце файла после команд можете добавить pause, чтобы был виден результат и ошибки — если таковые возникнут.
Подготовка путей
Первое, что мы сделаем для удобства и краткости — создадим специальные переменные, в которых будем хранить пути. Для начала — определим наши основные директории. Вам нужно заменить пути к JDK и Android SDK на те, которые у вас.
Далее — пути непосредственно к программам. Я рекомендую вам просмотреть каталоги ваших SDK и убедится в том, что всё на месте. Также подкорректировать версии, которые присутствуют в путях.
Между прочим, в более старых версиях утилита aapt находилась в platform-tools — и я не исключаю что она и\или другие могут слинять куда-нибудь ещё. Так что будьте внимательны. Если вы всё правильно сверите сейчас — то остальная часть статьи должна пройти гладко.
И ещё — в пару переменных забьём наши пакеты и классы. Если заходите их сменить — вам не придётся бегать по коду — все настройки вначале.
Подготовка к компиляции
Для начала спрошу — а вы никогда не задумывались, как работает загадочный класс R? Собственно меня он сперва смутил из-за его сверхъестественных возможностей. Как на этапе компиляции можно через поля класса обращаться к XML-файлам в других каталогах? Я предположил, что тут орудует прекомпилятор — так оно и оказалось.
Собственно, есть специальная утилита AAPT — она проходится по каталогам ваших ресурсов и создаёт тот самый R.java. Оказывается, всё очень даже просто — это просто класс, в составе которого другие статические вложенные классы с целочисленными константами. И всё! Он выглядит примерно так:
Теперь давайте создадим его у вас. Для этого используем следующие команды:
Давайте разберёмся, что к чему. AAPT — Android Asset Packaging Tool — буквально «упаковщик андроид-имущества». Его опции:
- package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
- -f — перезапись существующего R.java, если таковой имеется
- -m — разместить R.java в надлежащих пакетах, а не в корне указанного в -J пути
- -S — после этой опции мы указываем каталог с ресурсами
- -J — после этой опции мы указываем куда сохранить получившийся R.java
- -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar
После его выполнения в каталоге src должен появится тот самый файл R.java. Проверьте.
Теперь в нашем проекте нет никакой магии и он полностью синтаксически корректен в рамках Java. А теперь самое интересное. Помните, как классические Java-программы компилируются через javac? Раньше он также входил в последовательность сборки Android-приложений. Мы брали наши исходники (*.java), получали из них байт-код JVM (*.class) и уже потом из него получали байт-код для Dalvic (*.dex). С появлением Jack ToolChain мы сократили нашу последовательность сборки на один шаг. Из исходников (*.java) мы сразу же получаем байт-код для Dalvic (*.dex).
Где же взять Джека? Он находится в папке build-tools в виде jack.jar и запускается как обычный Java-архив.
- -jar — Стандартная опция для JVM, указывающая на то, что нужно запустить Java-архив. Не имеет никакого отношения к Джеку
- —output-dex — Папка, в которую нужно поместить итоговый dex-файл. Пускай он будет в bin
- -D jack.java.source.version=1.8 — «D» указывает на то, что мы задаём свойство. jack.java.source.version позволяет нам указать, что мы используем Java 8. Без неё лямбды не будут работать и будут ошибки. Полный список свойств можете посмотреть по команде %JAVAVM% -jar %JACK_JAR% —help-properties
- [Список из *.java — файлов] — Ваши исходники. У нас всего 2 файла — R.java и MainActivity.java
Полный список опций для Джека можете посмотреть по команде %JAVAVM% -jar %JACK_JAR% —help
Убедитесь в том, что в папке bin находится наш classes.dex. Теперь осталось только упаковать его вместе с ресурсами в APK-файл. Сделаем это:
Здесь опции аналогичны тем, которые мы использовали при создании R.java:
- package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
- -f — перезапись существующего AndroidTest.unsigned.apk, если таковой имеется
- -M — после этой опции мы указываем путь к файлу манифеста
- -S — после этой опции мы указываем каталог с ресурсами
- -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar
- -F — после этой опции мы указываем куда сохранить получившийся AndroidTest.unsigned.apk
- последний аргумент — путь к папке с dex — файлами
В папке bin теперь должен появится AndroidTest.unsigned.apk. И мы назвали его не просто так! У него нет цифровой подписи. Андроид запрещает устанавливать и запускать приложения без подписи. Но создать её не так-то трудно, как может показаться на первый взгляд
Собственно, эти строчки запускают 2 Java-утилиты, которые не имеют никакого отношения к Android SDK — но они необходимы. Первая создаёт файл AndroidTest.keystore (проверьте его наличие), а вторая — этот файл соединяет с AndroidTest.unsigned.apk. Получается файл AndroidTest.signed.apk. Вот такой дикий крафт файлов. Но однажды создав bat-скрипт запускайте его — и он будет делать всё это в автоматическом режиме.
Я думаю, не стоит тратить время на детальный разбор опций этих утилит в пределах данной статьи. Просто нужно уловить суть — они берут AndroidTest.unsigned.apk, подписывают его файлом AndroidTest.keystore и сохраняют в AndroidTest.signed.apk. Если есть желание, можете почитать в документации.
У вас, скорее всего, будет предупреждение «Warning: No -tsa or -tsacert is provided and this jar. «, но не обращайте внимание.
Запуск
Теперь, когда мы наконец собрали наш apk-файл — можем его запустить. Подключите по usb ваше устройство, или же запустите эмулятор. А затем выполните
Собственно, первая строчка удаляет программку, если она уже там есть. Для повторных запусков пригодится. Вторая — устанавливает APK на ваш девайс или эмулятор. Третья же — запускает. Давайте более подробно разберём её аргументы:
- shell — мы хотим выполнить некоторые команды на нашем девайсе
- am — используем для выполнения команд activity manager
- start — мы хотим запустить некоторое Activity
- имя пакета и полное имя класса (включая пакет), которые мы стартуем
Внимание — во время установки на устройстве может появится диалоговое окно с подтверждением. Если вовремя его не одобрить, то установка произойдёт с ошибкой [INSTALL_FAILED_USER_RESTRICTED]. Также у вас может возникнуть вопрос — зачем делать uninstall/install вместо install -r. Я сделал так для чистоты эксперимента. Скрипт полностью удаляет все продукты своей деятельности и создаёт их с нуля при каждом запуске. Даже ключи. Вы можете использовать install -r, но тогда следует убрать код, который отвечает за пересоздание ключей. Иначе вы столкнётесь с ошибкой [INSTALL_FAILED_UPDATE_INCOMPATIBLE].
Если всё прошло удачно, вы увидите что-то вроде этого:
Заключение
После сборки всех файлов дерево каталогов должно быть примерно таким.
Теперь вы можете наглядно увидеть и понять, как происходит сборка андроид-приложения на более низком уровне. Когда будете использовать IDE — если сборка вдруг пойдёт не так (а такое часто бывает) — сможете вырулить ситуацию как надо. Также обратите внимание на то, что итоговый apk-файл занимает всего около 4 килобайт.
Выкладываю архив проекта. Обратите внимание, что я добавил туда ещё один маленький скрипт — Clear.bat. Он удаляет все созданные при сборке файлы. И поставил его запуск на начало Compile.bat. Также добавил комментарии с помощью Rem — по шагам.
Таким образом, скрипт производит полную очистку и пересборку проекта, включая подпись, а также удаление его на устройстве, установку и запуск.
Мои параметры
ОC: Windows 10 Pro x64
JDK: 1.8.0_73
Android SDK: 24
Модель: Meizu MX4
Android: 5.1
ОС: Flyme 5.6.8.9 beta
Источник