- Как собрать нативную библиотеку для Android
- Собираем Opus из исходников
- Добавляем собранные библиотеки в проект
- Creating and Publishing an Android Library
- Introduction
- 1. Creating an Android Library
- Step 1: Add a New Module
- Step 2: Create a Layout
- Step 3: Create a Java Class
- 2. Using the Library Locally
- 3. Publishing Your Library on Bintray
- Step 1: Add Necessary Plugins
- Step 2: Apply the Plugins
- Step 3: Specify POM Details
- Step 4: Generate a Sources JAR
- Step 5: Generate a Javadoc JAR
- Step 6: Include the Generated JAR files
- Step 7: Run Tasks
- Step 8: Configure the Bintray Plugin
- Step 9: Upload Files Using the Bintray Plugin
- 4. Using the Library From Bintray
- 5. Adding the Library to JCenter
- Conclusion
Как собрать нативную библиотеку для 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 и говорим. Если тут же слышим то, что сказали, значит всё работает как надо.
Источник
Creating and Publishing an Android Library
Introduction
Our lives as Android developers would be a lot harder if not for all those third-party libraries out there that we love to include in our projects. In this tutorial, you will learn how to give back to the developer community by creating and publishing your own Android libraries, which people can effortlessly add and use in their projects.
1. Creating an Android Library
If your library is going to be composed of only Java classes, packaging it as a JAR and distributing it using a file host is perhaps the quickest and easiest way to share it. If you were to create it from the console, the following command would suffice:
This tutorial however, shows you how to work with more complex libraries that contain not just Java classes, but also various types of XML files and resources. Such libraries are created as Android library modules and are usually packaged as AAR files.
Let’s create a simple Android library that offers a custom View to developers who use it.
Step 1: Add a New Module
To begin, add a new Android module to your project by selecting New > New Module from the File menu. You will be shown the following screen, which offers lots of choices:
Select Android Library and press Next. In the form that follows, enter a name for your library and press Next. I’ll be calling this library mylittlelibrary.
In the last screen, select Add no Activity and press Finish.
Your project will now have two modules, one for the app and one for the library. Here’s what its structure looks like:
Step 2: Create a Layout
Create a new layout XML by right-clicking on the res folder of your library module and selecting New > XML > Layout XML File. Name it my_view.xml.
To keep this tutorial simple, we’ll be creating a custom View that has two TextView widgets inside a LinearLayout . After adding some text to the TextView widgets, the layout XML file should look like this:
Step 3: Create a Java Class
Create a new Java class and name it MyView.java. Make sure to put this file in the src directory of the library module–not the app module.
To make this class behave as a View , make it a subclass of the LinearLayout class. Android Studio will prompt you to add a few constructors to the class. After adding them, the new class should look like this:
As you can see, we now have two constructors. To avoid adding initialization code to each constructor, call a method named initialize from each constructor. Add the following code to each constructor:
In the initialize method, call inflate to associate the layout we created in the previous step with the class.
2. Using the Library Locally
Now that the library is ready, let’s make use of it in the app module of the same project in order to make sure that there are no issues. To do so, add it as a compile dependency in the build.gradle file of the app module:
Create a new Java class, MainActivity, inside the app module. Make it a subclass of the Activity class and override its onCreate method.
Inside the onCreate method, create an instance of the custom view using its constructor. Pass it to the setContentView method so that it fills all the screen space of the Activity :
Your Activity is now ready. After adding it to the app manifest, build your project and deploy your app to an Android device. You should be able to see the custom view when the app starts.
3. Publishing Your Library on Bintray
Bintray is a popular platform you can use to publish Android libraries. It is free and easy to use.
Start by creating an account on Bintray. After signing in to your account, you will see that you already own six repositories. You can either use one of them or create a new repository. For this tutorial, I will be using the repository called maven, which is a Maven repository.
Visit your profile page and click the Edit button. On the next page, click the API Key link to view your API key.
Make a note of the key, because you will be needing it to authenticate yourself when using the Bintray plugin.
Step 1: Add Necessary Plugins
To interact with Bintray in Android Studio, you should include the Bintray plugin in the dependencies of your project’s build.gradle file.
Because you will be uploading the library to a Maven repository, you should also add the Maven plugin as shown below.
Step 2: Apply the Plugins
Open the build.gradle file of your library module and add the following code to apply the plugins we added in the previous step.
Step 3: Specify POM Details
The Bintray plugin will look for a POM file when it uploads the library. Even though the Maven plugin generates it for you, you should specify the value of the groupId tag and the value of the version tag yourself. To do so, use the group and version variables in your gradle file.
If you are familiar with Maven and you are wondering why we didn’t specify the value of the artifactId tag, it is because the Maven plugin will, by default, use the name of your library as the artifactId .
Step 4: Generate a Sources JAR
To conform to the Maven standards, your library should also have a JAR file containing the library’s source files. To generate the JAR file, create a new Jar task, generateSourcesJar, and specify the location of the source files using the from function.
Step 5: Generate a Javadoc JAR
It is also recommended that your library has a JAR file containing its Javadocs. Because you currently don’t have any Javadocs, create a new Javadoc task, generateJavadocs, to generate them. Use the source variable to specify the location of the source files. You should also update the classpath variable so that the task can find classes that belong to the Android SDK. You can do this by adding the return value of the android.getBootClasspath method to it.
Next, to generate a JAR from the Javadocs, create a Jar task, generateJavadocsJar, and pass the destinationDir property of generateJavadocs to its from function. Your new task should look like this:
To make sure the generateJavadocsJar task only starts when the generateJavadocs task has completed, add the following code snippet, which uses the dependsOn method to order the tasks:
Step 6: Include the Generated JAR files
To include the source and Javadoc JAR files in the list of artifacts, which will be uploaded to the Maven repository, you should add the names of their tasks to a configuration called archives. To do so, use the following code snippet:
Step 7: Run Tasks
It is now time to run the tasks we created in the previous steps. Open the Gradle Projects window and search for a task named install.
Double-click it to run the tasks associated with the library module. Once it’s finished running, you will have everything you need to publish your library, a valid POM file, an AAR file, a sources JAR, and a Javadocs JAR.
Step 8: Configure the Bintray Plugin
To configure the plugin, you should use the bintray closure in your Gradle file. First, authenticate yourself using the user and key variables, corresponding to your Bintray username and API key respectively.
On Bintray, your library will reside inside a Bintray package. You should provide details about it using the intuitively named repo , name , licenses , and vcsUrl parameters of the pkg closure. If the package doesn’t exist, it will be created automatically for you.
When you upload files to Bintray, they will be associated with a version of the Bintray package. Therefore, pkg must contain a version closure whose name property is set to a unique name. Optionally, you can also provide a description, release date, and Git tag using the desc , released , and vcsTag parameters.
Finally, to specify the files that should be uploaded, set the value of the configuration parameter to archives.
This is a sample configuration:
Step 9: Upload Files Using the Bintray Plugin
Open the Gradle Projects window again and search for the bintrayUpload task. Double-click it to begin uploading the files.
Once the task completes, open a browser to visit your Bintray package’s details page. You will see a notification that says that you have four unpublished files. To publish these files, click the Publish link.
4. Using the Library From Bintray
Your library is now available as a Bintray package. Once you share the URL of your Maven repository, along with the group ID, artifact ID, and version number, any developer can access your library. For example, to use the library we created, developers would have to include the following code snippet:
Note that the developer has to explicitly include your repository in the list of repositories before adding the library as a compile dependency.
5. Adding the Library to JCenter
By default, Android Studio searches for libraries in a repository called JCenter. If you include your library in the JCenter repository, developers won’t have to add anything to their repositories list.
To add your library to JCenter, open a browser and visit your Bintray package’s details page. Click the button labeled Add to JCenter.
You will then be taken to a page that lets you compose a message. You can use the Comments field to optionally mention any details about the library.
Click the Send button to begin Bintray’s review process. Within a day or two, the folks at Bintray will link your library to the JCenter repository and you will be able to see the link to JCenter on your package’s details page.
Any developer can now use your library without changing the list of repositories .
Conclusion
In this tutorial, you learned how to create a simple Android library module and publish it to both your own Maven repository and to the JCenter repository. Along the way, you also learned how to create and execute different types of gradle tasks.
To learn more about Bintray, visit Bintray’s user manual.
Источник