Android NDK 開發(fā)中 SO 包大小壓縮方法詳解
背景
這周在做Yoga包的壓縮工作。Yoga本身是用BUCK腳本編譯的,而最終編譯出幾個包大小大總共約為7M,不能滿足項目中對于APK大小的限制,因此需要對它進行壓縮。
這里先將Yoga編譯腳本用CMAKE重新改寫,以便可以在android studio中直接使用并輸出一個AAR的包。后面又對它進行了壓縮,最終將Yoga包的大小壓縮到200多KB。
下面整理了一些可以用于減少NDK開發(fā)中Android SO包大小的方法:
1.STL的使用方式
對于C++的library,引用方式有2種:
- 靜態(tài)方式(static)
- 動態(tài)方式(shared)
其中,靜態(tài)方式在編譯時會將用到的相關(guān)代碼直接復(fù)制到目的文件中;而動態(tài)方式則會將相關(guān)的代碼打成so文件,以便多次引用。由于編譯器在編譯時并不能知道所有被引用的地方,所以同時會打入了很多不相關(guān)的代碼。
所以,如果項目中引用library的函數(shù)較多時,用動態(tài)方式可以避免多次拷貝,節(jié)省空間。相反,則直接使用靜態(tài)方式會更節(jié)省空間。
NDK開發(fā)中,可以通過gradle的設(shè)置來配置:
defaultConfig{ externalNativeBuild{ cmake{ // gnustl_shared 動態(tài) arguments "-DANDROID_STL=gnustl_static" } } }
在Yoga中,項目里的stl使用較少時,安卓運行時使用static的方式,而不是shared,所以這里采用static的方式。在采取了這種方式后,包的大小從2.7M縮減到了2M。
2.不使用Exception和RTTI
C++的exception和RTTI功能在NDK中默認是關(guān)閉的,但是可以通過配置打開的。
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:將基類類型的指針或引用安全的轉(zhuǎn)換為派生類型的指針或引用。
在yoga中,RTTI的選項是默認打開的,而代碼中其實并沒有用到相關(guān)的功能,這里可以直接關(guān)閉。
Exception
使用C++的exception會增加包的大小,而目前JNI對C++的exception的支持是有bug的,比如下面這段代碼就會引起程序的crash(對于低版本的android NDK)。
因此要在程序中引入exception要自己實現(xiàn)相關(guān)邏輯,yoga就是這么做的,這個又增加了一些包體大小。對于開發(fā)者來說,exception可以幫助快速定位問題,而對于使用者并不是那么重要,這里可以去掉。
try { ... } catch (std::exception& e) { env->ThrowNew(env->FindClass("java/lang/Exception"), "Error occured"); }
在yoga中,在關(guān)閉RTTI和Exception功能并把exception相關(guān)的代碼都去掉后,包的大小從2M縮減到的1.8M。
3.使用 gc-sections去除沒有用到的函數(shù)
去除未使用的代碼顯然可以減少包體的大小,而在NDK的開發(fā)中,并不需要手動的來做這一點。可以開啟編譯器的gc-sections選項,讓編譯器自動的幫你做到這一點。
編譯器可以配置自動去除未使用的函數(shù)和變量,以下是配置方式:
CMake:
# 去除未使用函數(shù)與變量 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") # 設(shè)置去除未使用代碼的鏈接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函數(shù),這里必須要做好權(quán)衡。
下面是配置方式:
CMake:
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe")
Android.mk:
LOCAL_LDFLAGS += -Wl,--gc-sections,--icf=safe
5.設(shè)置編譯器的優(yōu)化flag
編譯器有個優(yōu)化flag可以設(shè)置,分別是-Os(體積最?。?,-O3(性能最優(yōu))等。這里將編譯器的優(yōu)化flag設(shè)置為-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在這方面已經(jīng)做的挺好了,其他的庫可能會更有效。
6.設(shè)置編譯器的 Visibility Feature
還有個減少包體大小的方法,就是設(shè)置編譯器的visibility feature。
Visibility Feature就是用來控制在哪些函數(shù)可以在符號表中被輸入,由于C++并不是完全面向?qū)ο蟮?,非類的方法并沒有public這種修飾符,因此,要用Visibility Feature來控制哪些函數(shù)可以被外部調(diào)用。
而JNI提供了一個宏-JNIEXPORT來控制這點。所以只要對函數(shù)加上這個宏,像這樣:
// JNIEXPORT就是控制可見的宏 // JNICALL在NDK這里沒有什么意義,只是個標識宏 JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
然后在編譯器的FLAGS選項開啟 -fvisibility = hidden 就可以。這樣,不僅可以控制函數(shù)的可見性,并且可以減少包體的大小。
CMake:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
7.設(shè)置編譯器的Strip選項
我在把Yoga庫編譯成AAR包的過程中發(fā)現(xiàn),它的體積明顯會大于最后打包進APK的大小,這點非常不合理,但是無法找到原因。
最終搜索到這是谷歌NDK的一個bug,在打AAR包的過程中,無論是debug版本還是release版本,NDK toolchain不會自動的把方便調(diào)試的C++ 符號表(Symbol Table)中數(shù)據(jù)刪除,而只會在打APK包的時候進行這一操作。這就導(dǎo)致了打成的AAR包中的SO體積明顯偏大。
找到原因后這個問題就很好解決了,可以手動的在鏈接選項中加入 strip參數(shù),配置如下所示:
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe,-s")
在強制進行strip操作后,將Yoga包的體積從1.7M成功減少到了282KB。
8.去除C++代碼中的iostream相關(guān)代碼
使用STL中的iostream相關(guān)庫會明顯的增加包的體積,而NDK本身是有預(yù)編譯庫(android/log.h)可以代替這一功能的,在Yoga這里,用log的函數(shù)代替了iostream中的所有函數(shù),如:
//代替所有的iostream庫里函數(shù) //cout << obj->toString() << endl; __android_log_print(ANDROID_LOG_VERBOSE,"Yoga","Node is: %s",obj->toString().c_str());
在做完代替之后,yoga包的體積從282KB減少到了218KB。
總結(jié)
在做完這一系列工作后,最終成功的壓縮了Yoga包的體積,從幾M到最后輸出一個218KB的AAR包提供使用。以上幾種方法并不局限于Yoga包的縮減。在NDK開發(fā)中,要縮減SO包的體積都可以按照這幾種方式嘗試一下。
以上就是Android NDK 開發(fā)中 SO 包大小壓縮方法詳解的詳細內(nèi)容,更多關(guān)于Android NDK開發(fā)SO包壓縮的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android自定義View繪制貝塞爾曲線實現(xiàn)流程
貝塞爾曲線的本質(zhì)是通過數(shù)學計算的公式來繪制平滑的曲線,分為一階,二階,三階及多階。但是這里不講數(shù)學公式和驗證,那些偉大的數(shù)學家已經(jīng)證明過了,所以就只講講Android開發(fā)中的運用吧2022-11-11Android集成GreenDao數(shù)據(jù)庫的操作步驟
這篇文章主要介紹了Android集成GreenDao數(shù)據(jù)庫,使用數(shù)據(jù)庫存儲時候,一般都會使用一些第三方ORM框架,比如GreenDao,本文分幾步給大家介紹Android集成GreenDao數(shù)據(jù)庫的方法,需要的朋友可以參考下2022-10-10android 6.0 權(quán)限授權(quán)方法
今天小編就為大家分享一篇android 6.0 權(quán)限授權(quán)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07內(nèi)存泄露導(dǎo)致Android?中setVisibility()?失效原理
這篇文章主要介紹了內(nèi)存泄露導(dǎo)致Android?中setVisibility()?失效原理,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下2022-07-07Android App中實現(xiàn)相冊瀑布流展示的實例分享
這篇文章主要介紹了Android App中實現(xiàn)相冊瀑布流展示的實例分享,例子中利用到了緩存LruCache類的相關(guān)算法來解決大量加載問題,需要的朋友可以參考下2016-04-04Android源碼學習之觀察者模式應(yīng)用及優(yōu)點介紹
定義對象間一種一對多的依賴關(guān)系,使得當一個對象改變狀態(tài),則所有依賴于它的對象都會得到通知并被自動更新等等,需要了解的朋友可以參考下2013-01-01