- Creating libraries for Android applications — Tutorial
- 1. Android library projects and Java libraries
- 2. Custom Android library modules
- 2.1. Using custom library modules
- 2.2. Creating custom Android library modules
- 3. Prerequisite
- 4. Exercise: Create an Android library module
- 4.1. Create library module
- 4.2. Remove generated dependency from build.gradle of the library project
- 4.3. Create the model class
- 4.4. Create instances
- 4.5. Define dependency to the library project
- 4.6. Use library project to update detailed fragments
- 4.7. Validate implementation
- 5. Exercise: Deploy a library project
- Как собрать нативную библиотеку для Android
- Собираем Opus из исходников
- Добавляем собранные библиотеки в проект
Creating libraries for Android applications — Tutorial
This tutorial describes how to create and use library projects in Android.
1. Android library projects and Java libraries
Android project can use code contained in JAR files (Java libraries)
If you want to use libraries, these must only use API available in Android. For example, the java.awt and javax.swing packages are not available on Android.
In addition to JAR files, the Android uses a binary distribution format called Android ARchive(AAR). The .aar bundle is the binary distribution of an Android Library Project.
An AAR is similar to a JAR file, but it can contain resources as well as compiled byte-code. A AAR file can be included in the build process of an Android application similar to a JAR file.
It is possible to create libraries modules which can be used as dependencies in Android projects. These modules allow you to store source code and Android resources which can be shared between several other Android projects.
To use a Java library (JAR file) inside your Android project, you can simple copy the JAR file into the folder called libs in your application. *.jar files in this folder are included into the compile classpath via the default build.gradle file.
2. Custom Android library modules
2.1. Using custom library modules
An Android library module can contain Java classes, Android components and resources. Only assets are not supported.
The code and resources of the library project are compiled and packaged together with the application.
Therefore a library module can be considered to be a compile-time artifact.
2.2. Creating custom Android library modules
Using library projects helps you to structure your application code. To create a new library module in Android Studio, select File New Module and select Android Library .
3. Prerequisite
The following example assumes that you have created an Android project with the com.example.android.rssfeed top level package based on the following tutorial: https://www.vogella.com/tutorials/AndroidFragments/article.html#fragments_tutorial
4. Exercise: Create an Android library module
Our library project will contain the data model and a method to get the number of instances. The library provides access to (fake) RSS data. An RSS document is an XML file which can be used to publish blog entries and news.
4.1. Create library module
For Android Studio each library is a module. To create a new library module in Android Studio, select File New Module and select Android Library .
Use com.example.android.rssfeedlibrary as module name and Rssfeed Library as library name.
If prompted for a template select that no activity should be created. As a result Android Studio shows another module.
4.2. Remove generated dependency from build.gradle of the library project
Open the build.gradle of the library project. Delete the dependencies closure, your library does not need any dependency and the generated dependency can cause problems for the build.
Ensure you remove dependencies from the correct library project and not from your app.
4.3. Create the model class
Create an RssItem class which can store data of an RSS entry.
Generate the getters and setter, the constructor and a toString() method. The result should look like the following class:
4.4. Create instances
Create a new class called RssFeedProvider with a static method to return a list of RssItem objects. This method does currently only return test data.
4.5. Define dependency to the library project
To use the library add it as a dependency in your project select File Project Structure . Select the app entry. Switch to the Dependencies tab and select Module dependencies via the + sign.
4.6. Use library project to update detailed fragments
Update the updateDetail method in your MyListFragment class to use the RssFeedProvider provider. This is only test code.
4.7. Validate implementation
Start your application and ensure that the toString value of the list of RssItems is displayed in the DetailFragment .
The list is currently generated randomly every time you press the button. |
5. Exercise: Deploy a library project
Create a new library project called recyclerbaseadapter with the same top level package. Add the following to its build.gradle file.
Create or move a MyBaseAdapter class in this library.
Deploy it by running the gradle uploadArchives task.
You can now define a dependency to this library, by adding mavenLocal() and using:
Источник
Как собрать нативную библиотеку для Android
Собрать и заставить работать приложение с небольшим количеством нативного кода несложно. Если же вы хотите использовать нативную библиотеку, в которой много файлов, становится труднее. Сложность в том, что нативные библиотеки распространяются в виде исходного кода, который нужно компилировать под нужную архитектуру процессора. На примере аудиокодека Opus я покажу, как это сделать.
В своей предыдущей статье я на примере аудиокодека Opus показал, как решить задачу сборки нативной библиотеки для Android, но в проект был добавлен весь исходный код Opus, а так делать не хочется. По-хорошему нужно иметь в проекте уже собранные .so или .a, и используя их, писать свои врапперы на C/C++ и собирать приложение. Об этом и пойдёт речь в статье. На примере того же аудиокодека Opus я покажу, как с помощью NDK получить скомпилированные .so и .a, чтобы далее использовать их в Android-проекте.
Чтобы использовать любую нативную библиотеку, нам нужно написать враппер на C/C++, в котором мы будем вызывать методы самой библиотеки. Мы скомпилируем и соберём библиотеку Opus как статическую (.a).
Мы вызываем функцию в нашем C файле, которая описана в другой библиотеке. Для этого в начале файла пишем #include . Далее на этапе компиляции C файла, если библиотека статическая(.a), то вместо #include компилятор подставит весь код этой функции. Если динамическая(.so), то подставит только ссылку на функцию. Таким образом, при использовании статической библиотеки у нас есть весь код, который нам необходим из другой библиотеки, а при использовании динамической код будет подгружаться динамически.
Использование .so в зависимостях может уменьшить размер нашей итоговой библиотеки. Это было бы так, если бы Opus был стандартной библиотекой, но так как её нет в Android, мы должны её предоставить. Поэтому итоговый размер нашего libjniopus.so будет одинаковым, что при использовании libopus.a, что при использовании libopus.so.
Для компиляции нативных библиотек из исходников в NDK c версии 19 есть готовые удобные инструменты «из коробки». В документации описано, как их использовать. Описание довольно короткое, поэтому человек, не имеющий опыта в сборке нативных библиотек, неизбежно столкнётся со сложностями. Как их решить, я разберу ниже.
Собираем Opus из исходников
Сначала скачиваем исходники: либо из репозитория (git clone), либо архивом. У Opus есть Autoconf — утилита, которая автоматически создаёт конфигурационные скрипты. В проекте с Autoconf можно задать toolchain для компиляции, используя ENV-переменные. Autoconf, как правило, работает только на Unix подобных системах. Поэтому если у вас Windows, то вам, скорее всего, придётся использовать средства эмуляции.
В итоге мы хотим получить 4 файла библиотеки с расширением .a по одной на каждую архитектуру процессора: armeabi-v7a, arm64-v8a, x86, x86-64.
Для каждой архитектуры нужно задать свои ENV-переменные. Общими будут только NDK, HOST_TAG и TOOLCHAIN. В Linux, ENV-переменную для текущей сессии терминала можно задать, используя команду export:
Однако заданные ENV-переменные будут существовать, пока активна текущая сессия терминала, и только в ней. Если вы откроете другую сессию, то там этих переменных не будет. Чтобы каждый раз не прописывать ENV-переменные заново, можно добавить их в конец файла .bashrc, который находится в домашнем каталоге (
). Перед запуском каждой сессии терминала запускается .bashrc. Поэтому переменные, объявленные в нём, будут существовать во всех сессиях терминала. Вот ENV-переменные, которые отличаются в зависимости от архитектуры:
Сначала скомпилируем под arm64-v8a, поэтому закомментируем в .bashrc объявление ENV-переменных для остальных архитектур. Стоит отметить, что в переменных CC и CXX есть цифра 21, которая очень похожа на Android API level. Так и есть, а если быть точнее, то это ваша minSdkVersion. В нашем примере это 21, но если у вас другие потребности, можете смело поменять. В NDK доступны версии с 16 по 29 для 32-разрядных ABI (armeabi-v7a и x86) и с 21 по 29 для 64-разрядных ABI (arm64-v8a и x86-64).
Итак, мы объявили ENV-переменные, которые определяют toolchain для компиляции под выбранную архитектуру, и выкачали репозиторий с исходниками Opus. Теперь открываем Readme в папке с исходниками Opus и видим, что для компиляции библиотеки надо всего-навсего запустить:
Но не всё так просто. Таким образом мы скомпилируем библиотеку под архитектуру нашей рабочей машины, а нам надо под 4 архитектуры мобильных девайсов.
В NDK с версии r19 «из коробки» идут toolchains. Как и показано в примере на странице, нужно выставить ENV-переменные (выставили их выше в .bashrc), соответствующие архитектуре, под которую компилируется библиотека. Затем нужно запустить команду configure с соответствующим аргументом host. Для arm64-v8a получается такое:
/.bashrc необходимо запускать, чтобы изменения переменных среды для сборки «подхватывались» текущей сессией терминала без перезапуска.
После выполнения всех вышеописанных команд, в папке с исходниками Opus появится папка .libs. В ней будут находиться все артефакты компиляции, в том числе и нужный нам libopus.a.
Далее в .bashrc комментим/раскомменчиваем, чтобы активными были ENV-переменные для armeabi-v7a, и прописываем команды уже с другим аргументом host:
./autogen.sh не выполняем, так как он нужен был только для первоначальной генерации конфигурационных файлов и того самого configure, который мы запускаем.
В этот раз команда make завершится ошибкой. Я долго не понимал, из-за чего так происходит. Поискав в интернете похожие проблемы, понял, что файлы и тесты, которые создаются для определённой архитектуры, во время компиляции не удаляются автоматически после переконфигурации на другую архитектуру (запуск configure с другим host). А так как в папке с исходниками, в которой появляются все артефакты сборки, и так много файлов, понять, какие из них надо удалять потом, очень сложно.
Решение оказалось довольно простым. Можно запускать все команды для конфигурации и компиляции, не находясь в папке с исходниками Opus. Тогда можно создать свою отдельную папку для каждой архитектуры, где будут все артефакты сборки и временные файлы для этой архитектуры.
Теперь всего-то нужно создать 4 папки и последовательно сделать одни и те же действия по выставлению ENV-переменных и прописыванию команд с нужным аргументом. Это слишком долго и скучно, поэтому есть отличная возможность написать bash-скрипт, который всё это сделает за нас. Вот такой небольшой скрипт получился:
Если использовать скрипт, то объявлять ENV-переменные в .bashrc нет необходимости, так как они объявлены в скрипте.
Чтобы использовать скрипт, нужно сделать его исполняемым:
Далее необходимо прописать путь к NDK на вашей машине и поменять HOST_TAG, если вы не на Linux (в скрипте значение linux-x86_64):
- 32-bit Windows: windows
- 64-bit Windows: windows-x86_64
- macOS: darwin-x86_64
Затем запускаем скрипт таким образом:
Ему нужно передать minSdkVersion и путь к папке с исходниками Opus. Опционально можно передать путь к папке, куда поместим артефакты сборки. По умолчанию создаётся папка opus_android_build в папке, где расположен скрипт buildOpus.sh.
Выполнение скрипта займёт некоторое время. Потом в папке opus_android_build или в той, которую вы передали, будут располагаться папки с названиями ABI, под которые была скомпилирована библиотека. Соответственно, внутри каждой папки будет уже знакомая нам папка .libs, в которой лежат все артефакты сборки.
Добавляем собранные библиотеки в проект
Отлично, у нас есть 4 файла libopus.a под разные архитектуры, самое сложное позади. Осталось добавить их в наш Android-проект и поправить CmakeLists.txt, чтобы к итоговой .so линковалась скомпилированная нами статическая библиотека Opus.
У нас есть папка app/src/main/cpp, в которой лежит наш C-враппер (jniopus.c), к которому идут external вызовы из Kotlin. В ней создаём папку includes. Затем в неё копируем содержимое папки include из исходников Opus. Там находятся файлы хедеров (.h). Они нам нужны для использования функции и структуры Opus в нашем C-враппере. Файлы хедеров содержат прототипы функций и являются своего рода контрактом, который определяет, какие аргументы может принимать и возвращать та или иная функция.
После этого в той же в папке app/src/main/cpp создадим папку libopus, а внутри неё 4 папки с названиями, идентичными названиям ABI, под которые мы скомпилировали библиотеку Opus: armeabi-v7a, arm64-v8a, x86, x86-64. В каждую из них помещаем файл libopus.a, скомпилированный под соответствующую архитектуру.
Далее модифицируем CmakeLists.txt, который был в предыдущей статье, где мы собирали Opus из исходников прямо в проекте. Сначала удаляем «простыню» со всеми путями к исходникам. Затем задаём переменные для путей:
Теперь добавляем Opus в сборку:
add_library используется для добавления библиотеки в сборку. Сначала идёт имя, которое у неё будет, далее тип библиотеки STATIC(.a) или SHARED(.so) и путь к исходникам, либо слово IMPORTED, если у нас уже собранная библиотека и мы хотим её использовать. В этом случае путь к готовым (импортируемым) библиотекам указывается ниже с помощью конструкции:
ANDROID_ABI это NDK toolchain аргумент, который сам туда подставит ABI.
Для компиляции нашего С-враппера, куда идут external вызовы из Kotlin, мы оставляем:
Также оставляем библиотеку, которая идёт «из коробки» в NDK для логирования таким образом:
И в конце мы объединяем всё это вместе:
Первым параметром идёт имя итоговой библиотеки, далее идут имена библиотек, которые нужно прилинковать к нашей. Вот и всё, теперь синхронизируем проект с помощью Gradle, нажимаем Run, и готово. Приложение открывается, жмём на кнопку Start call и говорим. Если тут же слышим то, что сказали, значит всё работает как надо.
Источник