All you need to know about compatibility and adaptation with Android so files whether discovered or not, all credit goes to the big guys.

When developing Android applications, sometimes the Java layer encoding can not meet the implementation requirements, it is necessary to generate SO file after C/C++ implementation, and then use system.loadLibrary () to load the call, which becomes the implementation of JNI layer. Common scenarios such as: encryption and decryption algorithms, audio and video coding and decoding. When generating SO files, it is necessary to consider adapting to different mobile PHONE CPU architectures in the market, and generate SO files supporting different platforms for compatibility. Android currently supports seven different CPU architectures: ARMv5, ARMv7 (since 2010), x86 (since 2011), MIPS (since 2012), ARMv8, MIPS64, and X86_64 (since 2014).

The existing common ABI Application Binary Interface defines the format specification for Binary files (in particular.so files) that their corresponding CPU architecture can execute. On Android, different Android phones use different cpus and therefore support different instruction sets. Each combination of CPU and instruction set has its own application binary interface (OR ABI). The ABI defines very precisely how the application’s machine code interacts with the system at run time. You must specify an ABI for each CPU architecture to be used by the application: ArmeABI, Armeabi-v7A, x86, MIPS, ARM64-V8A, MIPS64, x86_64 [1].

A typical ABI contains the following information:

  • The set of CPU instructions that machine code should use.
  • Byte order for runtime memory storage and loading.
  • The format of executable binaries, such as programs and shared libraries, and the types of content they support.
  • Conventions for parsing data between content and the system. These conventions include alignment restrictions and how the system uses the stack and registers when a function is called.
  • A list of function symbols available at run time for machine code – usually from a very specific set of libraries.
  • Support for one or more instruction sets.

There is an ABI for each CPU architecture. One CPU belongs to one architecture. Multicore cpus need to belong to the same architecture to work together, and many devices only support one CPU architecture.

If you want perfect compatibility with all types of phones, the theory is to place the SO files for each architecture platform in the liBS directory.

This will work with all models, but your project will be very bulky. Is it necessary to bring SO many SO files for compatibility? The answer is no.

For CPUS, different architectures do not necessarily mean incompatible, according to the current Android supports seven different TYPES of CPU architectures, their compatibility characteristics can be summarized as follows:

Armeabi devices are compatible only with Armeabi; Armeabi-v7a devices are compatible with ArmeabI-V7A and ArmeABI; Arm64-v8a devices compatible with ARM64-V8A, ArMEABI-V7A, ArmeABI; X86 devices are compatible with X86 and Armeabi; X86_64 devices are compatible with X86_64, X86, and Armeabi; Mips64 devices are compatible with MIPS64 and MIPS. MIPS is only compatible with MIPS;Copy the code

According to the compatibility summary above, we can also get some rules:

  • Armeabi’s SO file is basically a panacea that runs on devices other than MIPS and MIPS64, but still suffers from performance loss on non-Armeabi devices;
  • 64-bit CPU architectures are always backward compatible with their corresponding 32-bit instruction sets. For example, X86_64 is X86 compatible, ARM64-V8A is Armeabi-V7A compatible, and MIPS64 is MIPS compatible.

#.so file related notes

When an application is installed on a device, only.so files corresponding to the CPU architecture supported by the device will be installed. On x86 devices, the.so file in the libs/x86 directory will be installed if it exists, and the.so file in armeabi-v7a will be selected if it does not exist either. Select the. So file in the armeabi directory (because x86 devices also support Armeabi-v7a and Armeabi).

From the current mobile CPU market share data, ARM architecture is almost monopoly, SO, unless your user is very special, you can almost ignore the separate compilation into X86, X86_64, MIPS, MIPS64 architecture SO files. After these four architectures are removed, three different types of armeabi, Armeabi-V7A, and ARM64-V8A are added, which can increase the size of the installation package for an application with a large number of SO files.

If your application still has a lot of Armeabi type devices, consider only keeping SO files in the Armeabi directory (Balm features). However, although Armeabi is compatible with a variety of platforms, there are still some operations that perform poorly in Armeabi-V7A and ARM64-V8A using Armeabi’s SO file, SO it should be used for the corresponding platform architecture.

Note: This is not to bring a set of SO files to different directories, but to put the SO files of a certain Armeabi-V7A and ARM64-V8A platform with obvious performance differences into the Armeabi directory, and then determine the CPU type of the device through the code, and then load the SO files of the corresponding architecture. That’s what a lot of big apps do. You should provide. So files optimized for each ABI as much as possible, but either all or none of them are supported: you should not mix them. You should provide a.so file for each ABI directory. For example, although there is only one directory armeabi under the lib of wechat, the files in the directory still contain SO files of V5 and V7A architecture, which are used to deal with some performance computing problems caused by compatibility.

In terms of current market share, most devices are already ArmeabI-V7A and ARM64-V8A. You can also consider keeping only ARmeabI-V7A architecture SO files, which can achieve better performance effects. The performance difference is obvious add a single SO file and judge in the code.

Error introducing.so file

When you introduce a.so file, it doesn’t just affect the CPU architecture. There are a number of common errors that can be seen from other developers, the most common of which are “UnsatisfiedLinkError”, “dlopen: Failed “and other types of crashes or poor performance:

1. The. So file compiled using the Android-21 platform version runs on an Android-15 device

With the NDK, you might be tempted to use the latest build platform, but in fact this is wrong, because the NDK platform is not backward compatible, it is forward compatible. It is recommended to use the compilation platform corresponding to minSdkVersion of the app.

This also means that when you import a precompiled.so file, you need to check the platform version on which it was compiled.

2. Mix.so files compiled by different C++ runtimes

So files can be statically compiled or dynamically loaded depending on different C++ runtimes. Mixing different C++ runtimes can cause many strange crashes and should be avoided. As a rule of thumb, it is fine to statically compile the C++ runtime when there is only one. So file; otherwise, when there are multiple. So files, you should have all of them dynamically link to the same C++ runtime.

This means that when introducing a new precompiled. So file, and there are other. So files in the project, we need to first verify that the C++ runtime used by the newly introduced.

3. No. So file is provided for each supported CPU architecture

This has already been mentioned, but you should really pay special attention to it because it can happen without even realizing it.

For example, your app supports Armeabi-v7A and x86, and then uses Android Studio to add a library dependency that includes. So files and supports more CPU architectures, such as the android-gif-drawable library:

Compile 'pl. Droidsonroids. GIF: android - GIF - drawable: 1.1 +'Copy the code

After releasing our app, we will find that it will Crash on some devices, such as Galaxy S6. Finally, we can find that only the. So file under the 64-bit directory is installed into the phone.

Solution: Recompile our.so files to support the missing ABIs, or Settings

ndk.abiFilters
Copy the code

Displays the specified supported ABIs.

** If you are an SDK provider and provide a library that does not support all ABIs, you will screw up your users because they will have to support less ABIs than you provide.

4. Put the. So file in the wrong place

It’s easy to get confused about where.so files should be placed or generated, but here’s a summary:

  • Android Studio projects are placed in the jniLibs/ABI directory (you can also specify jnilibs. srcDir by setting the jnilibs. srcDir property in build.gradle)
  • Eclipse projects are placed in the libs/ABI directory (which is also where the ndk-build command generates the.so file by default)
  • The AAR package is in the JNI /ABI directory (so files are automatically included in the APK that references the AAR package)
  • Finally APK file lib/ABI directory through PackageManager installation, in less than Android 5.0 system, so file in app nativeLibraryPath directory; On Android 5.0 or higher, the.so file is located in the nativeLibraryRootDir/CPU_ARCH directory of the app.

5. Only provide. So files for Armeabi architecture and ignore other ABIs

As mentioned earlier, all x86/x86_64/ Armeabi-v7A/ARM64-V8A devices support armeabi-architecture. So files, so it seems that removing other ABIs. So files is a good technique to reduce the size of APK. But it’s not: it doesn’t just affect library performance and compatibility.

X86 devices can run arm-type libraries well, but there is no guarantee that crashes will not occur 100% of the time, especially on older devices. 64-bit devices (ARM64-V8A, X86_64, MIPS64) can run 32-bit libraries, but run in 32-bit mode, running 32-bit versions of ART and Android components on 64-bit platforms will lose performance optimized for 64-bit (ART, WebView, Media, etc.).

The excuse of reducing the size of the APK package is a false one, because you can also choose to upload APK with the specified ABI in the app market. The APK that generates different ABI versions can be configured in build.gradle as follows:

android {
    ... 
    splits {
        abi {
            enable true
            reset()
            include 'x86'.'x86_64'.'armeabi-v7a'.'arm64-v8a' //select ABIs to build APKs for
            universalApk true //generate an additional APK that contains all the ABIs
        }
    }

    // map for the version code
    project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2.'arm64-v8a': 3.'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]

    android.applicationVariants.all { variant ->
        // assign different version code for each output
        variant.outputs.each { output ->
            output.versionCodeOverride =
                    project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode
        }
    }
 }
Copy the code

# Appendix: [1]

Armeabi This ABI works with ARM-based cpus that support at least the ARMv5TE instruction set. Please refer to the following documentation for details:

  • ARM architecture Reference manual
  • Procedure call standard for ARM architecture
  • ARM ELF file format
  • Application Binary Interface (ABI) for ARM Architecture
  • ABI, the base platform for the ARM architecture
  • The ARM architecture C library ABI
  • C++ ABI for ARM architecture
  • The runtime ABI for the ARM architecture
  • ELF System V applies binary interface
  • Universal /Itanium C++ ABI

The AAPCS standard defines the EABI as a family of similar but different ABIs. In addition, Android uses the small byte order ARM GNU/Linux ABI. This ABI does not support hardware-assisted floating-point calculations. Instead, all floating-point operations use software helper functions in the compiler’s libgcc.a static library. The ARmeABI supports ARM’s Thumb (also known as thumb-1) instruction set. The NDK generates Thumb code by default, unless you specify a different behavior using the LOCAL_ARM_MODE variable in the Android.mk file.

Armeabi -v7a This ABI extends the ArmeABI to include multiple CPU instruction set extensions. The directive extensions supported by this Android specific ABI include:

  • Thumb-2 instruction set extension, with performance comparable to 32-bit ARM instructions and similar simplicity to Thumb-1.
  • VFP hardware FPU instruction. More specifically, this includes VFPV3-D16, which contains 16 dedicated 64-bit floating-point registers in addition to the 16 32-bit registers in the ARM core.

Other extensions to the V7-A ARM specification, including Advanced SIMD (also known as NEON), VFPV3-D32, and ThumbEE, are optional with this ABI. Since they are not guaranteed to exist, the system should check for the availability of extensions at run time. If not, alternate code paths must be used. This check is similar to what the system performs when checking or using other specialized instruction sets on MMX, SSE2, and x86 cpus.

For information on how to perform these runtime checks, see the CPUFeatures library. Also, for information about the NDK support for building machine code for NEON, see NEON Support.

The Armeabi-v7A ABI uses the -mfloating-abi = softFP switch to enforce rules that require the compiler to pass all double values in the core register pair, rather than dedicated floating-point values, when a function is called. The system can use the FP register to perform all internal calculations. This can greatly speed up the calculation.

Arm64-v8a This ABI works with ARMv8-based cpus that support AArch64. It also contains NEON and VFPv4 instruction sets. For more information, see ARMv8 Technical Preview and contact ARM for further details.

X86 This ABI works with cpus that support instruction sets commonly referred to as “x86” or “IA-32.” The features of this ABI include:

  • The instructions are typically generated by GCC with compiler flags, as follows:
-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
Copy the code

These flags point to the Pentium Pro instruction set with MMX, SSE, SSE2, SSE3, and SSSE3 instruction set extensions. The generated code is balanced and optimized between top-level Intel 32-bit cpus. For more information about compiler flags, especially related to performance tuning, see GCC x86 Performance Tips.

  • Use standard Linux x86 32-bit calling conventions, as opposed to those used by SVR. For more information, see section 6, “use of registers,” calling conventions for different C++ compilers and operating systems.

The ABI does not include any other optional IA-32 instruction set extensions, such as:

  • MOVBE
  • Any variant of SSE4.

You can still use these extensions, as long as you enable them with run-time feature probes and provide an alternate method for devices that do not support them. The NDK toolchain assumes 16-bit stack alignment prior to function calls. Default tools and options enforce this rule. If you are writing assembly code, you must ensure stack alignment, and other compilers follow this rule. Please refer to the following documentation for details:

  • GCC online documentation: Intel 386 and AMD x86-64 options
  • Calling conventions for different C++ compilers and operating systems
  • Intel IA-32 Intel Architecture Software Developer’s Manual Volume 2: Instruction Set Reference
  • Intel IA-32 Intel Architecture Software Developer’s Manual Volume 3: System Programming Guide
  • System V application binary interface: Intel386 processor architecture supplement

X86_64 This ABI works with cpus that support an instruction set commonly referred to as “x86-64.” It supports directives that GCC typically generates using the following compiler flags:

-march= x86-64-msse4.2-mpopcnt-m64-mtune = IntelCopy the code

These flags point to the x86-64 instruction set (according to the GCC documentation), along with MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and POPCNT instruction set extensions. The generated code is balanced and optimized between top-level Intel 64-bit cpus. For more information about compiler flags, especially related to performance tuning, see GCC x86 Performance. This ABI does not include any other optional x86-64 instruction set extensions, such as:

  • MOVBE
  • SHA
  • AVX
  • AVX2

You can still use these extensions, as long as you enable them with run-time feature probes and provide an alternate method for devices that do not support them. Please refer to the following documentation for details:

  • Calling conventions for different C++ compilers and operating systems
  • Intel64 and ia-32 architecture software developer’s manual volume 2: instruction set reference
  • Intel64 and ia-32 architecture software developer’s manual volume 3: system programming

MIPS This ABI works with MIPs-based cpus that support at least the MIPS32r1 instruction set. It contains the following features:

  • MIPS32 revision 1 ISA
  • Little byte order
  • O32
  • Hard floating-point
  • None DSP application-specific extension

For more information, please refer to the following documentation:

  • Programmer’s Architecture (“MIPSARCH”)
  • ELF System V applies binary interface
  • Itanium/ universal C++ ABI

For more detailed information, see the MIPS32 architecture. Please refer to the MIPS FAQ for frequently asked questions.

Mips64 This ABI works with MIPS64 R6. For more information, see MIPS64 architecture.

All you need to know about Android so files is compatibility and adaptation with ABI Management