日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Android?NDK?開發中?SO?包大小壓縮方法詳解_Android

作者:Android社區 ? 更新時間: 2022-10-30 編程語言

背景

這周在做Yoga包的壓縮工作。Yoga本身是用BUCK腳本編譯的,而最終編譯出幾個包大小大總共約為7M,不能滿足項目中對于APK大小的限制,因此需要對它進行壓縮。

這里先將Yoga編譯腳本用CMAKE重新改寫,以便可以在android studio中直接使用并輸出一個AAR的包。后面又對它進行了壓縮,最終將Yoga包的大小壓縮到200多KB。

下面整理了一些可以用于減少NDK開發中Android SO包大小的方法:

1.STL的使用方式

對于C++的library,引用方式有2種:

  • 靜態方式(static)
  • 動態方式(shared)

其中,靜態方式在編譯時會將用到的相關代碼直接復制到目的文件中;而動態方式則會將相關的代碼打成so文件,以便多次引用。由于編譯器在編譯時并不能知道所有被引用的地方,所以同時會打入了很多不相關的代碼。

所以,如果項目中引用library的函數較多時,用動態方式可以避免多次拷貝,節省空間。相反,則直接使用靜態方式會更節省空間。

NDK開發中,可以通過gradle的設置來配置:

defaultConfig{
  externalNativeBuild{
    cmake{
      // gnustl_shared 動態
      arguments "-DANDROID_STL=gnustl_static" 
    }
  }
}

在Yoga中,項目里的stl使用較少時,安卓運行時使用static的方式,而不是shared,所以這里采用static的方式。在采取了這種方式后,包的大小從2.7M縮減到了2M。

2.不使用Exception和RTTI

C++的exception和RTTI功能在NDK中默認是關閉的,但是可以通過配置打開的。

Android.mk:

APP_CPPFLAGS += -fexceptions -frtti

CMake:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -frtti")

Exception和RTTI會顯著的增加包的體積,所以非必須的時候,沒有必要使用。

RTTI

通過RTTI,能夠通過基類的指針或引用來檢索其所指對象的實際類型,即運行時獲取對象的實際類型。C++通過下面兩個操作符提供RTTI。

(1)typeid:返回指針或引用所指對象的實際類型。

(2)dynamic_cast:將基類類型的指針或引用安全的轉換為派生類型的指針或引用。

在yoga中,RTTI的選項是默認打開的,而代碼中其實并沒有用到相關的功能,這里可以直接關閉。

Exception

使用C++的exception會增加包的大小,而目前JNI對C++的exception的支持是有bug的,比如下面這段代碼就會引起程序的crash(對于低版本的android NDK)。

因此要在程序中引入exception要自己實現相關邏輯,yoga就是這么做的,這個又增加了一些包體大小。對于開發者來說,exception可以幫助快速定位問題,而對于使用者并不是那么重要,這里可以去掉。

try {
    ...
} catch (std::exception& e) {
    env->ThrowNew(env->FindClass("java/lang/Exception"), "Error occured");
}

在yoga中,在關閉RTTI和Exception功能并把exception相關的代碼都去掉后,包的大小從2M縮減到的1.8M。

3.使用 gc-sections去除沒有用到的函數

去除未使用的代碼顯然可以減少包體的大小,而在NDK的開發中,并不需要手動的來做這一點。可以開啟編譯器的gc-sections選項,讓編譯器自動的幫你做到這一點。

編譯器可以配置自動去除未使用的函數和變量,以下是配置方式:

CMake:

# 去除未使用函數與變量
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
# 設置去除未使用代碼的鏈接flag
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections")

Android.mk:

LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections
LOCAL_CFLAGS += -ffunction-sections -fdata-sections 
LOCAL_LDFLAGS += -Wl,--gc-sections

4.去除冗余代碼

在NDK中,鏈接器還有一個選項 “-icf = safe”,可以用于去除代碼中的冗余代碼。但是要注意的是,這個選項也有可能去除定義好的inline函數,這里必須要做好權衡。

下面是配置方式:

CMake:

SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe")

Android.mk:

LOCAL_LDFLAGS += -Wl,--gc-sections,--icf=safe

5.設置編譯器的優化flag

編譯器有個優化flag可以設置,分別是-Os(體積最小),-O3(性能最優)等。這里將編譯器的優化flag設置為-Os,以便減少體積。

CMake:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

Android.mk

LOCAL_CPPFLAGS += -Os
LOCAL_CFLAGS += -Os

在采用了3,4,5這幾種方式后,Yoga包的大小從1.8M減少到了1.7M。這里減少的比較少是因為Yoga在這方面已經做的挺好了,其他的庫可能會更有效。

6.設置編譯器的 Visibility Feature

還有個減少包體大小的方法,就是設置編譯器的visibility feature。

Visibility Feature就是用來控制在哪些函數可以在符號表中被輸入,由于C++并不是完全面向對象的,非類的方法并沒有public這種修飾符,因此,要用Visibility Feature來控制哪些函數可以被外部調用。

而JNI提供了一個宏-JNIEXPORT來控制這點。所以只要對函數加上這個宏,像這樣:

// JNIEXPORT就是控制可見的宏
// JNICALL在NDK這里沒有什么意義,只是個標識宏
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)

然后在編譯器的FLAGS選項開啟 -fvisibility = hidden 就可以。這樣,不僅可以控制函數的可見性,并且可以減少包體的大小。

CMake:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

7.設置編譯器的Strip選項

我在把Yoga庫編譯成AAR包的過程中發現,它的體積明顯會大于最后打包進APK的大小,這點非常不合理,但是無法找到原因。

最終搜索到這是谷歌NDK的一個bug,在打AAR包的過程中,無論是debug版本還是release版本,NDK toolchain不會自動的把方便調試的C++ 符號表(Symbol Table)中數據刪除,而只會在打APK包的時候進行這一操作。這就導致了打成的AAR包中的SO體積明顯偏大。

找到原因后這個問題就很好解決了,可以手動的在鏈接選項中加入 strip參數,配置如下所示:

SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe,-s")

強制進行strip操作后,將Yoga包的體積從1.7M成功減少到了282KB。

8.去除C++代碼中的iostream相關代碼

使用STL中的iostream相關庫會明顯的增加包的體積,而NDK本身是有預編譯庫(android/log.h)可以代替這一功能的,在Yoga這里,用log的函數代替了iostream中的所有函數,如:

//代替所有的iostream庫里函數
//cout << obj->toString() << endl;
__android_log_print(ANDROID_LOG_VERBOSE,"Yoga","Node is: %s",obj->toString().c_str());

在做完代替之后,yoga包的體積從282KB減少到了218KB。

總結

在做完這一系列工作后,最終成功的壓縮了Yoga包的體積,從幾M到最后輸出一個218KB的AAR包提供使用。以上幾種方法并不局限于Yoga包的縮減。在NDK開發中,要縮減SO包的體積都可以按照這幾種方式嘗試一下。

原文鏈接:https://www.androidos.net.cn/doc/2022/8/17/133.html

欄目分類
最近更新