Android ndk build armeabi v7a

ABI Management

On this page

Different Android handsets use different CPUs, which in turn support different instruction sets. Each combination of CPU and instruction sets has its own Application Binary Interface, or ABI. The ABI defines, with great precision, how an application’s machine code is supposed to interact with the system at runtime. You must specify an ABI for each CPU architecture you want your app to work with.

A typical ABI includes the following information:

  • The CPU instruction set(s) that the machine code should use.
  • The endianness of memory stores and loads at runtime.
  • The format of executable binaries, such as programs and shared libraries, and the types of content they support.
  • Various conventions for passing data between your code and the system. These conventions include alignment constraints, as well as how the system uses the stack and registers when it calls functions.
  • The list of function symbols available to your machine code at runtime, generally from very specific sets of libraries.

This page enumerates the ABIs that the NDK supports, and provides information about how each ABI works.

Supported ABIs

Each ABI supports one or more instruction sets. Table 1 provides an at-a-glance overview of the instruction sets each ABI supports.

Table 1. ABIs and supported instruction sets.

ABI Supported Instruction Set(s) Notes
armeabi
  • ARMV5TE and later
  • Thumb-1
  • No hard float.
    armeabi-v7a ( armeabi-v7a-hard)
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Other, optional
  • Hard float when specified as armeabi-v7a-hard . Incompatible with ARMv5, v6 devices.
    arm64-v8a
  • AArch-64
  • x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • No support for MOVBE or SSE4.
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
  • mips
  • MIPS32r1 and later
  • Uses hard-float, and assumes a CPU:FPU clock ratio of 2:1 for maximum compatibility. Provides neither micromips nor MIPS16.
    mips64
  • MIPS64r6
  • More detailed information about each ABI appears below.

    armeabi

    This ABI is for ARM-based CPUs that support at least the ARMv5TE instruction set. Please refer to the following documentation for more details:

    The AAPCS standard defines EABI as a family of similar but distinct ABIs. Also, Android follows the little-endian ARM GNU/Linux ABI.

    This ABI does not support hardware-assisted floating point computations. Instead, all floating-point operations use software helper functions from the compiler’s libgcc.a static library.

    The armeabi ABI supports ARM’s Thumb (a.k.a. Thumb-1) instruction set. The NDK generates Thumb code by default unless you specify different behavior using the LOCAL_ARM_MODE variable in your Android.mk file.

    armeabi-v7a (armeabi-v7a-hard)

    This ABI extends armeabi to include several CPU instruction set extensions. The instruction extensions that this Android-specific ABI supports are:

    • The Thumb-2 instruction set extension, which provides performance comparable to 32-bit ARM instructions with similar compactness to Thumb-1.
    • The VFP hardware-FPU instructions. More specifically, VFPv3-D16, which includes 16 dedicated 64-bit floating point registers, in addition to another 16 32-bit registers from the ARM core.

    Other extensions that the v7-a ARM spec describes, including Advanced SIMD (a.k.a. NEON), VFPv3-D32, and ThumbEE, are optional to this ABI. Since their presence is not guaranteed, the system should check at runtime whether the extensions are available. If they are not, you must use alternative code paths. This check is similar to the one that the system typically performs to check or use MMX, SSE2, and other specialized instruction sets on x86 CPUs.

    For information about how to perform these runtime checks, refer to The cpufeatures Library. Also, for information about the NDK’s support for building machine code for NEON, see NEON Support.

    The armeabi-v7a ABI uses the -mfloat-abi=softfp switch to enforce the rule that the compiler must pass all double values in core register pairs during function calls, instead of dedicated floating-point ones. The system can perform all internal computations using the FP registers. Doing so speeds up the computations greatly.

    Although the requirement to use core register pairs produces a modest performance hit, it ensures compatibility with all existing armeabi binaries. If you need the additional performance, you can specify your ABI as armeabi-v7a-hard instead. Doing so allows you to use hard floats, while still linking with Android native APIs that use softfp . For more information, refer to the comments in $NDK/tests/device/hard-float/jni/android.mk .

    Note: You cannot specify APP_ABI as both armeabi-v7a and armeabi-v7a-hard . In either case, the build system places the shared libraries in the armeabi-v7a/ directory.

    armeabi-v7a-hard

    This variant of the armeabi-v7a ABI is unique to the NDK. The NDK build system adds the following flags in addition to those that it uses for the armeabi-v7a ABI:

    The compiler compiles all code with hard-float, and links it with libm_hard.a . This math library is the same one as libm.a , except that it follows hard-float ABI conventions. In the APK, the generated shared libraries reside in /lib/armeabi-v7a/ .

    arm64-v8a

    This ABI is for ARMv8-based CPUs that support AArch64. It also includes the NEON and VFPv4 instruction sets.

    For more information, see the ARMv8 Technology Preview, and contact ARM for further details.

    This ABI is for CPUs supporting the instruction set commonly referred to as «x86» or «IA-32». Characteristics of this ABI include:

      Instructions normally generated by GCC with compiler flags such as the following:

    These flags target the the Pentium Pro instruction set, along with the the MMX, SSE, SSE2, SSE3, and SSSE3 instruction set extensions. The generated code is an optimization balanced across the top Intel 32-bit CPUs.

    For more information on compiler flags, particularly related to performance optimization, refer to GCC x86 performance hints.

  • Use of the standard Linux x86 32-bit calling convention, as opposed to the one for SVR. For more information, see section 6, «Register Usage», of Calling conventions for different C++ compilers and operating systems.
  • The ABI does not include any other optional IA-32 instruction set extensions, such as:

    You can still use these extensions, as long as you use runtime feature-probing to enable them, and provide fallbacks for devices that do not support them.

    The NDK toolchain assumes 16-byte stack alignment before a function call. The default tools and options enforce this rule. If you are writing assembly code, you must make sure to maintain stack alignment, and ensure that other compilers also obey this rule.

    Refer to the following documents for more details:

    x86_64

    This ABI is for CPUs supporting the instruction set commonly referred to as «x86-64.» It supports instructions that GCC typically generates with the following compiler flags:

    These flags target the x86-64 instruction set, according to the GCC documentation. along with the MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and POPCNT instruction-set extensions. The generated code is an optimization balanced across the top Intel 64-bit CPUs.

    For more information on compiler flags, particularly related to performance optimization, refer to GCC x86 Performance.

    This ABI does not include any other optional x86-64 instruction set extensions, such as:

    You can still use these extensions, as long as you use runtime feature probing to enable them, and provide fallbacks for devices that do not support them.

    Refer to the following documents for more details:

    This ABI is for MIPS-based CPUs that support at least the MIPS32r1 instruction set. It includes the following features:

    • MIPS32 revision 1 ISA
    • Little-endian
    • O32
    • Hard-float
    • No DSP application-specific extensions

    For more information, please refer to the following documentation:

    The MIPS-specific documentation is available here, with further information here.

    mips64

    This ABI is for MIPS64 R6. For more information, see MIPS64 Architecture.

    Generating Code for a Specific ABI

    By default, the NDK generates machine code for the armeabi ABI. You can generate ARMv7-a-compatible machine code, instead, by adding the following line to your Application.mk file.

    To build machine code for two or more distinct ABIs, using spaces as delimiters. For example:

    This setting tells the NDK to build two versions of your machine code: one for each ABI listed on this line. For more information on the values you can specify for the APP_ABI variable, see Android.mk.

    When you build multiple machine-code versions, the build system copies the libraries to your application project path, and ultimately packages them into your APK, so creating a fat binary. A fat binary is larger than one containing only the machine code for a single system; the tradeoff is gaining wider compatibility, but at the expense of a larger APK.

    At installation time, the package manager unpacks only the most appropriate machine code for the target device. For details, see Automatic extraction of native code at install time.

    ABI Management on the Android Platform

    This section provides details about how the Android platform manages native code in APKs.

    Native code in app packages

    Both the Play Store and Package Manager expect to find NDK-generated libraries on filepaths inside the APK matching the following pattern:

    Here, is one of the ABI names listed under Supported ABIs, and is the name of the library as you defined it for the LOCAL_MODULE variable in the Android.mk file. Since APK files are just zip files, it is trivial to open them and confirm that the shared native libraries are where they belong.

    If the system does not find the native shared libraries where it expects them, it cannot use them. In such a case, the app itself has to copy the libraries over, and then perform dlopen() .

    In a fat binary, each library resides under a directory whose name matches a corresponding ABI. For example, a fat binary may contain:

    Note: ARMv7-based Android devices running 4.0.3 or earlier install native libraries from the armeabi directory instead of the armeabi-v7a directory if both directories exist. This is because /lib/armeabi/ comes after /lib/armeabi-v7a/ in the APK. This issue is fixed from 4.0.4.

    Android Platform ABI support

    The Android system knows at runtime which ABI(s) it supports, because build-specific system properties indicate:

    • The primary ABI for the device, corresponding to the machine code used in the system image itself.
    • An optional, secondary ABI, corresponding to another ABI that the system image also supports.

    This mechanism ensures that the system extracts the best machine code from the package at installation time.

    For best performance, you should compile directly for the primary ABI. For example, a typical ARMv5TE-based device would only define the primary ABI: armeabi . By contrast, a typical, ARMv7-based device would define the primary ABI as armeabi-v7a and the secondary one as armeabi , since it can run application native binaries generated for each of them.

    Many x86-based devices can also run armeabi-v7a and armeabi NDK binaries. For such devices, the primary ABI would be x86 , and the second one, armeabi-v7a .

    A typical MIPS-based device only defines a primary abi: mips .

    Automatic extraction of native code at install time

    When installing an application, the package manager service scans the APK, and looks for any shared libraries of the form:

    If none is found, and you have defined a secondary ABI, the service scans for shared libraries of the form:

    When it finds the libraries that it’s looking for, the package manager copies them to /lib/lib .so , under the application’s data directory ( data/data/

    If there is no shared-object file at all, the application builds and installs, but crashes at runtime.

    Источник

    Введение в Android NDK

    Для разработки приложений под ОС Android, Google предоставляет два пакета разработки: SDK и NDK. Про SDK существует много статей, книжек, а так же хорошие guidelines от Google. Но про NDK даже сам Google мало что пишет. А из стоящих книг я бы выделил только одну, Cinar O. — Pro Android C++ with the NDK – 2012.

    Эта статья ориентирована на тех, кто ещё не знаком (или мало знаком) с Android NDK и хотел бы укрепить свои знания. Внимание я уделю JNI, так как мне кажется начинать нужно именно с этого интерфейса. Так же, в конце рассмотрим небольшой пример с двумя функциями записи и чтения файла. Кто не любит много текста, тот может посмотреть видео версию.

    Что такое Android NDK?

    Android NDK (native development kit) – это набор инструментов, которые позволяют реализовать часть вашего приложения используя такие языки как С/С++.

    Для чего используют NDK?

    Google рекомендует прибегать к использованию NDK только в редчайших случаях. Зачастую это такие случаи:

    • Нужно увеличить производительность (например, сортировка большого объема данных);
    • Использовать стороннюю библиотеку. Например, много уже чего написано на С/С++ языках и нужно просто заиспользовать существующий материал. Пример таких библиотек, как, Ffmpeg, OpenCV;
    • Программирование на низком уровне (например, всё что выходит за рамки Dalvik);

    Что такое JNI?

    Java Native Interface – стандартный механизм для запуска кода, под управлением виртуальной машины Java, который написан на языках С/С++ или Assembler, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот.

    Преимущества JNI

    Основное преимущество перед аналогами (Netscape Java Runtime Interface или Microsoft’s Raw Native Interface and COM/Java Interface) является то что JNI изначально разрабатывался для обеспечения двоичной совместимости, для совместимости приложений, написанных на JNI, для любых виртуальных машин Java на конкретной платформе (когда я говорю о JNI, то я не привязываюсь к Dalvik машине, потому как JNI был написан Oracle для JVM который подходит для всех Java виртуальных машин). Поэтому скомпилированный код на С/С++ будет выполнятся в не зависимости от платформы. Более ранние версии не позволяли реализовывать двоичную совместимость.

    Двоичная совместимость или же бинарная совместимость – вид совместимости программ, позволяющий программе работать в различных средах без изменения её исполняемых файлов.

    Как устроен JNI


    JNI таблица, организована как таблица виртуальных функций в С++. VM может работать с несколькими такими таблицами. Например, одна будет для отладки, вторая для использования. Указатель на JNI интерфейс действителен только в текущем потоке. Это значит, что указатель не может гулять с одного потока в другой. Но нативные методы могут быть вызваны из разных потоков. Пример:

    • *env – указатель на интерфейс;
    • оbj – ссылка на объект в котором описан нативный метод;
    • i and s – передаваемые аргументы;

    Примитивные типы копируются между VM и нативным кодом, а объекты передаются по ссылке. VM обязана отслеживать все ссылки которые передаются в нативный код. Все переданные ссылки в нативный код не могут быть освобождены GC. Но нативный код в свою очередь должен информировать VM о том что ему больше не нужны ссылки на переданные объекты.

    Локальные и глобальные ссылки

    JNI делит ссылки на три типа: локальные, глобальные и слабые глобальные ссылки. Локальные действительны пока не завершиться метод. Все Java объекты которые возвращает функции JNI являются локальными. Программист должен надеется на то что VM сама подчистит все локальные ссылки. Локальные ссылки доступны лишь в том потоке в котором были созданы. Однако если есть необходимость то их можно освобождать сразу методом JNI интерфейса DeleteLocalRef:

    Глобальные ссылки остаются пока они явно не будут освобождены. Что бы зарегистрировать глобальную ссылку следует вызвать метод NewGlobalRef. Если же глобальная ссылка уже не нужна, то её можно удалить методом DeleteGlobalRef:

    Обработка ошибок

    JNI не проверяет ошибки такие как NullPointerException, IllegalArgumentException. Причины:

    • снижение производительности;
    • в большинстве функций C библиотек очень и очень трудно защитится от ошибок.

    JNI позволяет использовать Java Exception. Большинство JNI функций возвращают код ошибок а не сам Exception, и поэтому приходится обрабатывать сам код, а в Java уже выбрасывать Exception. В JNI следует проверять код ошибки вызываемых функций и после них следует вызвать ExceptionOccurred(), которая в свою очередь возвращает объект ошибки:

    Например, некоторые функции JNI доступа к массивам не возвращают ошибки, но могут вызвать исключения ArrayIndexOutOfBoundsException или ArrayStoreException.

    Примитивные типы JNI

    В JNI существуют свои примитивные и ссылочные типы данных.

    Java Type Native Type Description
    boolean jboolean unsigned 8 bits
    byte jbyte signed 8 bits
    char jchar unsigned 16 bits
    short jshort signed 16 bits
    int jint signed 32 bits
    long jlong signed 64 bits
    float jfloat 32 bits
    double jdouble 64 bits
    void void N/A

    Ссылочные типы JNI

    Модифицированный UTF-8

    JNI использует модифицированную кодировку UTF-8 для представления строк. Java в свою очередь использует UTF-16. UTF-8 в основном используется в С, потому что он кодирует \u0000 в 0xc0, вместо привычной 0x00. Изменённые строки кодируются так, что последовательность символов, которые содержат только ненулевой ASCII символы могут быть представлены с использованием только одного байта.

    Функции JNI

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

    Пример использования функций JNI

    Небольшой пример, что бы вы усвоили пройденный материал:

    Разберём построчно:

    • JavaVM – предоставляет интерфейс для вызова функций, которые позволяют создавать и уничтожать JavaVM;
    • JNIEnv – обеспечивает большинство функций JNI;
    • JavaVMInitArgs – аргументы для JavaVM;
    • JavaVMOption – опции для JavaVM;

    Метод JNI_CreateJavaVM() инициализирует JavaVM и возвращает на неё указатель. Метод JNI_DestroyJavaVM() выгружает созданную JavaVM.

    Потоки

    Всеми потоками в Linux управляет ядро, но они могут быть прикреплены к JavaVM функциями AttachCurrentThread и AttachCurrentThreadAsDaemon. Пока поток не присоединён он не имеет доступа к JNIEnv. Важно, Android не приостанавливает потоки которые были созданы JNI, даже если срабатывает GC. Но перед тем как поток завершиться он должен вызвать метод DetachCurrentThread что бы отсоединиться от JavaVM.

    Первые шаги

    Структура проекта у вас должна выглядеть следующим образом:

    Как мы видим из рисунка 3, весь нативный код находится в папке jni. После сборки проекта, в папке libs создастся четыре папки под каждую архитектуру процессора, в которой будет лежать ваша нативная библиотека (количество папок зависит от количество выбранных архитектур).

    Для того, чтобы создать нативный проект, нужно создать обычный Android проект и проделать следующие шаги:

    • В корне проекта нужно создать папку jni, в которую поместить исходники нативного кода;
    • Создать файл Android.mk, который будет собирать проект;
    • Создать файл Application.mk, в котором описываются детали сборки. Он не является обязательным условием, но позволяет гибко настроить сборку;
    • Создать файл ndk-build, который будет запускать процесс сборки (тоже не является обязательным).

    Android.mk

    Как упоминалось уже выше, это make файл для сборки нативного проекта. Android.mk позволяет группировать ваш код в модули. Модули могут быть как статические библиотеки (static library, только они будут скопированные в ваш проект, в папку libs), разделяемые библиотеки (shared library), автономный исполняемый файл (standalone executable).

    Пример минимальной конфигурации:

    Рассмотрим детально:

    • LOCAL_PATH := $(call my-dir) – функция call my-dir возвращает путь папки в которой вызывается файл;
    • include $(CLEAR_VARS) – очищает переменные которые использовались до этого кроме LOCAL_PATH. Это необходимо так как все переменные являются глобальными, потому что сборка происходит в контексте одного GNU Make;
    • LOCAL_MODULE – имя выходного модуля. В нашем примере имя выходной библиотеки установлено как NDKBegining, но после сборки в папке libs создадутся библиотеки с именами libNDKBegining. Android добавляет к названию префикс lib, но в java коде при подключении вы должны указывать название библиотеки без префикса (то есть названия должны совпадать с установленными в make файлах);
    • LOCAL_SRC_FILES – перечисление исходных файлов из которых следует создать сборку;
    • include $(BUILD_SHARED_LIBRARY) – указывает тип выходного модуля.

    В Android.mk можно определить свои переменные, но они не должны иметь такой синтаксис: LOCAL_, PRIVATE_, NDK_, APP_, my-dir. Google, рекомендует называть свои переменные, как MY_. Например:

    Application.mk

    NDK-BUILDS

    Ndk-build из себя представляет обёртку GNU Make. После 4-й версии ввели флаги для ndk-build:

    • clean – очищает все сгенеренные бинарные файлы;
    • NDK_DEBUG=1 – генерирует отладочный код;
    • NDK_LOG=1 – показывает лог сообщений (используется для отладки);
    • NDK_HOST_32BIT=1 – Android имеет средства для поддержки 64-х битных версий утилит (например NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64 и т.д.);
    • NDK_APPLICATION_MK — указывается путь к Application.mk.

    В 5-й версии NDK был введён такой флаг как NDK_DEBUG. Если он установлен в 1 то создаётся отладочная версия. Если флаг не установлен то ndk-build по умолчанию проверяет стоит ли атрибут android:debuggable=«true» в AndroidManifest.xml. Если вы используете ndk выше 8-й версии, то Google не рекомендует использовать атрибут android:debuggable в AndroidManifest.xml (потому что если вы используете «ant debug» или строите отладочную версию с помощью ADT плагина то они автоматически добавляют флаг NDK_DEBUG=1).

    По умолчанию устанавливается поддержка 64-х разрядной версии утилит, но вы можете принудительно собрать только для 32-х установив флаг NDK_HOST_32BIT=1. Google, рекомендует всё же использовать 64-х разрядность утилит для повышения производительности больших программ.

    Как собрать проект?

    Раньше это было мучением. Нужно было установить CDT плагин, скачать компилятор cygwin или mingw. Скачать Android NDK. Подключить это всё в настройках Eclipse. И как на зло это всё оказывалось не рабочим. Я первый раз когда столкнулся с Android NDK, то настраивал это всё 3 дня (а проблема оказалось в том что в cygwin нужно было дать разрешение 777 на папку проекта).

    Сейчас с этим всё намного проще. Идёте по этой ссылке. Качаете Eclipse ADT Bundle в котором уже есть всё то что необходимо для сборки.

    Вызов нативных методов из Java кода

    Для того что бы использовать нативный код из Java вам сперва следует определить нативные методы в Java классе. Например:

    Перед методом следует поставить зарезервированное слово «native». Таким образом компилятор знает, что это точка входа в JNI. Эти методы нам нужно реализовать в С/С++ файле. Так же Google рекомендует начинать именовать методы со слова nativeХ, где Х – реальное название метода. Но перед тем как реализовывать эти методы вручную, следует сгенерировать header файл. Это можно сделать вручную, но можно использовать утилиту javah, которая находится в jdk. Но пойдём дальше и не будет использовать её через консоль, а будем это делать при помощи стандартных средств Eclipse.

    Теперь можете запускать. В директории bin/classes будут лежать ваши header файлы.

    Далее копируем эти файлы в jni директорию нашего нативного проекта. Вызываем контекстное меню проекта и выбираем пункт Android Tools – Add Native Library. Это позволит нам использовать jni.h функции. Дальше вы уже можете создавать cpp файл (иногда Eclipse его создаёт по умолчанию) и писать тела методов, которые уже описаны в header файле.

    Пример кода я не стал добавлять в статью, чтобы не растягивать её. Пример вы можете посмотреть/скачать с github.

    Источник

    Читайте также:  Android studio http запрос get
    Оцените статью