background
This week I am doing the compression of Yoga packages. Yoga itself is compiled with BUCK scripts, and the final compiled several packages with a total size of about 7M, which cannot meet the APK size limit in the project, so it needs to be compressed.
Here we will first rewrite the Yoga compilation script with CMAKE so that it can be used directly in android studio and output an AAR package. It was compressed later, and finally compressed the size of the Yoga package to more than 200 KB.
Here are some methods that can be used to reduce the size of Android SO packages in NDK development:
How to use
For C++ library, there are two reference methods:
- Static mode
- Dynamic method (shared)
Among them, the static method will directly copy the relevant code used to the destination file during compilation; while the dynamic method will type the relevant code into a so file for multiple references. Since the compiler cannot know all the referenced places at compile time, a lot of irrelevant code will be entered at the same time.
Therefore, if there are many functions that refer to library in the project, the dynamic method can avoid multiple copies and save space. On the contrary, using the static method directly will save space.
In NDK development, you can configure it through gradle settings:
defaultConfig{ externalNativeBuild{ cmake{ // gnustl_shared dynamic arguments "-DANDROID_STL=gnustl_static" } } }
In Yoga, when the stl is used in the project, the Android runtime uses static instead of shared, so the static method is used here. After adopting this approach, the size of the package was reduced from 2.7M to 2M.
2. Do not use Exception and RTTI
The exception and RTTI functions of C++ are turned off by default in NDK.But it can be opened by configuration.
:
APP_CPPFLAGS += -fexceptions -frtti
CMake:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -frtti")
Exception and RTTI will significantly increase the volume of the package, so it is not necessary to use it when necessary.
RTTI
Through RTTI, the actual type of the object it refers to can be retrieved through a pointer or reference to the base class, that is, the actual type of the object is obtained at runtime. C++ provides RTTI through the following two operators.
(1) typeid: Returns the actual type of the pointer or references the object referred to.
(2) dynamic_cast: Convert a pointer or reference of the base class type safely to a pointer or reference of the derived type.
In yoga, the RTTI option is turned on by default, and the code does not actually use related functions, so it can be turned off directly here.
Exception
Using C++ exceptions will increase the size of the package, and currently JNI supports C++ exceptions with bugs. For example, the following code will cause the program to crash (for the lower version of android NDK).
Therefore, to introduce exceptions into the program, you need to implement the relevant logic by yourself. That's what Yoga does, which increases the size of the package body. For developers, exceptions can help quickly locate problems, but are not that important to users, so they can be removed here.
try { ... } catch (std::exception& e) { env->ThrowNew(env->FindClass("java/lang/Exception"), "Error occured"); }
In yoga, after turning off the RTTI and Exception functions and removing all the exception-related code, the package size is reduced from 2M to 1.8M.
3. Use gc-sections to remove unused functions
Removing unused code obviously reduces the size of the package body, and in the development of NDK, there is no need to do this manually.You can enable the compiler's gc-sections option, so that the compiler can automatically help you do this.
The compiler can configure automatically remove unused functions and variables. The following is the configuration method:
CMake:
# Remove unused functions and variablesset(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") # Set the flag to remove unused code linksSET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
:
LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections LOCAL_CFLAGS += -ffunction-sections -fdata-sections LOCAL_LDFLAGS += -Wl,--gc-sections
4. Remove redundant code
In the NDK,The linker also has an option "-icf = safe", which can be used to remove redundant code from the code. But it should be noted that this option may also remove the defined inline function, and trade-offs must be made here.
The following is the configuration method:
CMake:
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe")
:
LOCAL_LDFLAGS += -Wl,--gc-sections,--icf=safe
5. Set the compiler's optimization flag
The compiler has an optimized flag to set.They are -Os (minimum volume), -O3 (optimal performance), etc. Here the compiler's optimized flag is set to -Os to reduce volume.
CMake:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
LOCAL_CPPFLAGS += -Os LOCAL_CFLAGS += -Os
After adopting the 3, 4, and 5 methods, the size of the Yoga package has been reduced from 1.8M to 1.7M. The reduction here is relatively small because Yoga has done a good job in this regard, and other libraries may be more effective.
6. Set up the Visibility Feature of the compiler
There is another way to reduce the size of the package, which is to set the compiler's visibility feature.
Visibility Feature is used to control which functions can be input in the symbol table. Since C++ is not completely object-oriented, non-class methods do not have a modifier like public. Therefore, Visibility Feature needs to be used to control which functions can be called externally.
And JNI provides a macro - JNIEXPORT to control this. So just add this macro to the function, like this:
// JNIEXPORT is to control visible macros// JNICALL has no meaning in NDK, it's just an identification macroJNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
Then turn on -fvisibility = hidden in the compiler's FLAGS option. In this way, not only can the visibility of the function be controlled, but the size of the package can be reduced.
CMake:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
7. Set the compiler's Strip options
During the process of compiling the Yoga library into an AAR package, I found that its size is obviously larger than the size of the last packaged APK, which is very unreasonable, but the reason cannot be found.
Finally, I found that this was a bug in Google NDK. During the process of typing the AAR package, whether it is the debug version or the release version,NDK toolchain will not automatically delete data from the C++ symbol table that is convenient for debugging, but will only perform this operation when APK package is used. This leads to the significantly larger SO volume in the AAR package.
After finding the cause, this problem is easy to solve. You can manually add strip parameters to the link options, and the configuration is as follows:
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe,-s")
existForce strip operationAfter that, the volume of the Yoga package was successfully reduced from 1.7M to 282KB.
8. Remove iostream-related code in C++ code
Using the iostream related library in STL will significantly increase the volume of the package, and the NDK itself has a precompiled library (android/) that can replace this function. In Yoga, log functions are used instead of all functions in iostream, such as:
//Replace all iostream library functions//cout << obj->toString() << endl; __android_log_print(ANDROID_LOG_VERBOSE,"Yoga","Node is: %s",obj->toString().c_str());
After the replacement, the volume of the yoga package was reduced from 282KB to 218KB.
Summarize
After completing this series of work, the volume of the Yoga package was finally successfully compressed, from a few M to the end output of a 218KB AAR package for use. The above methods are not limited to the reduction of Yoga packages. In NDK development, you can try these ways to reduce the volume of SO packages.
The above is the detailed explanation of the SO package size compression method in Android NDK development. For more information on Android NDK development SO package compression, please pay attention to my other related articles!