Make writing a habit together! This is the 7th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

1. The background

There are dozens of dynamic libraries finally compiled by the full-link voice SDK based on the transformation of Amazon AVS Device SDK. The size of the single architecture dynamic library is tens of megabytes, which barely runs in Iot devices before, but this volume is fatal for mobile applications. It is good that each module can optimize a few K volume with great effort. I directly to the last tens of megabytes, APP platform side certainly cannot accept. However, first of all, I had business requirements, and second, I wanted to promote SDK to mobile APP to increase the number of users and verify the stability and interactive experience of SDK. Therefore, I started a long process of slimming down, and finally reduced the single architecture to less than 5 megabytes. Although it was still a little large, it was greatly improved compared with the previous one.

2. Delete unnecessary modules

AVS Device SDK is mainly used in audio console programs, and the code is cross-platform, so one is a lot of redundancy for cross-platform, and two is that there are many modules we do not use at all. For example, in order to do local storage, we introduced a Sqlite dynamic library. We don’t need local storage, such as alarm clock Settings can be put into the APP layer, and even if we need storage, we can also use Sqlite provided by Android and iOS platforms. Removing unused modules is the biggest and fastest package volume optimization space.

3. Third-party libraries are replaced by Android/iOS platforms to provide capabilities

AVS Device SDK on the Android platform based on FFMPEG to do decoding to achieve audio player, for our scene mainly use the player to play TTS, and TTS is a fixed MP3 format and service negotiation, there is no need to introduce a huge FFMPEG library for an MP3 decoding. Here we use the Jni layer media library provided by the Android platform to do audio decoding. And even if the Android platform doesn’t support the JNI layer, you can rely on a single MP3 decoder library instead of the massive FFMPEG. Third party modules tend to be relatively large for the overall package size.

4. Use the strip

The NDK toolchain can be used to remove data from the C++ Symbol Table. This is done automatically by APK, and can also be set manually:

Manually add the strip parameter to the link option and set it as follows:

SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,-s")
Copy the code

You can also manually run the aarch64-linux-Android-strip command provided by the NDK to remove debugging information from the dynamic library. This way, in addition to the previous method, the highest volume is optimized, such as liblibSampleApp. so from 48M to 992K.

4. Set optimization flags for the compiler

The compiler has an optimization flag that can be set: -OS (minimum volume), -O3(best performance), etc. The compiler’s optimization flag is set to -OS to reduce volume.

CMake:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
Copy the code

Android.mk

LOCAL_CPPFLAGS += -Os
LOCAL_CFLAGS += -Os
Copy the code

Compiler optimization is the method with the most room for optimization in addition to directly removing large modules. After setting -OS, occupy the first few libraries to submit large volume comparison:

The library Pre-optimization volume Optimized volume
libLibSampleApp.so 48M 33M
libAVSCommon.so 28M 22M
libDefaultClient.so 14M 9.9 M

5. Use gc-sections to remove unused functions

It is not possible to manually detect useless functions when the code is large. Enable the compiler’s GC-sections option and let the compiler automatically do this for you.

The compiler can be configured to automatically remove unused functions and variables. Here’s how:

CMake:

Set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections") set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS}") # SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-wl,-- gC-sections ")Copy the code

Android.mk:

OCAL_CPPFLAGS += -ffunction-sections -fdata-sections
LOCAL_CFLAGS += -ffunction-sections -fdata-sections 
LOCAL_LDFLAGS += -Wl,--gc-sections
Copy the code

6. Set the Visibility Feature of the compiler

The Visibility Feature is used to control which functions can be input in the symbol table. Since C++ is not fully object-oriented, non-class methods do not have the modifier public. Therefore, the Visibility Feature is used to control which functions can be called externally. JNI provides a macro, JNIEXPORT, to control this. So just add this macro to the function, like this:

// JNICALL does not make sense in NDK, JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jString javaString)Copy the code

Then turn on -fvisibility = hidden in the FLAGS option of the compiler. In this way, you can not only control the visibility of the function, but also reduce the size of the package.

7. Remove direct IO related code such as iostream from C++ code

Using the iostream-related libraries in the STL will significantly increase the package size, and Android itself has a precompiled library (Android /log.h) that can replace the tools typed into the console. In our SDK, I/O was used because it was a console application before, which was not excluded during compilation, causing some volume redundancy.

8. Use of STL

For C++ libraries, there are two types of references:

  • Static mode
  • Dynamic mode (Shared)

In the static mode, the relevant code used during compilation will be directly copied to the destination file; The dynamic approach, on the other hand, files the relevant code into so files for multiple references. Because the compiler does not know all references at compile time, it inserts a lot of irrelevant code at the same time.

Therefore, if a project has a large number of functions that reference library, using a dynamic approach can avoid multiple copies and save space. Instead, it is more space-efficient to use static mode directly. Because there are so many modules in our SDK, and other businesses have introduced dynamic library in the overall APK, we use the way of dynamic library.

9. Don’t use Exception and RTTI

There is no practice about these two points seen on the Internet, but it can be used as a reference for continuous optimization of package volume.

RTTI

With RTTI, the actual type of the object to which it refers can be retrieved by a pointer or reference to the base class, that is, the actual type of the object retrieved at runtime. C++ provides RTTI through two operators.

Typeid: returns the actual type of the object to which the pointer or reference refers.

(2) dynamic_cast: safely converts a pointer or reference of a base type to a pointer or reference of a derived type.

The RTTI option is turned off by default, and the code does not actually use the function, so it can be turned off directly.

Exception

Using C++ exception increases package size, and the current JNI support for C++ exception is buggy, such as the following code that causes the application to crash (for earlier versions of android NDK). So introducing exception in your program requires you to implement your own logic, but that increases the package size. Exceptions are useful for developers to quickly locate problems, but are not important to users and can be removed here.

10 summary

This article describes how to help dynamic library volume optimization by removing useless modules, replacing third-party libraries with platform capabilities, using strips, setting compiler optimization flags, removing unused functions using GC-sections, setting visibility, removing IOstream, etc.