<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android NDK 減少 so 庫體積方法總結

          共 3665字,需瀏覽 8分鐘

           ·

          2022-05-22 20:00

          1. 背景

          基于亞馬遜 AVS Device SDK 改造的全鏈路語音 SDK 最終編譯的動態(tài)庫有幾十個,單架構動態(tài)庫大小有幾十兆,之前在Iot設備中勉強跑著,但是這個體積對于手機應用來說是致命的,各個模塊費事費力能優(yōu)化個幾K的體積就不錯了,我這直接給上個幾十兆的,APP平臺方肯定無法接受。

          但是一是有業(yè)務需求,二是自己又想把SDK推到手機APP,提高用戶量,驗證SDK的穩(wěn)定性和交互體驗,所以開始了漫長的瘦身過程,最后單架構壓縮到了五兆一下,雖然還是有點大,但是比起之前有了很大的提升。

          2. 刪除無用模塊

          AVS Device SDK是主要應用在音響的控制臺程序,而且代碼是跨平臺的,所以一是有很多為了跨平臺做的冗余,二是有很多我們根本用不到的模塊。

          比如為了做本地存儲引入了一個Sqlite的動態(tài)庫,我們本身也用不到本地存儲,像鬧鐘設置之類的放到APP層即可,而且就算是需要存儲也完全可以使用Android和iOS平臺提供的Sqlite。刪除用不到的模塊是包體積優(yōu)化空間最大最快的。

          3. 第三方庫替換為Android/iOS平臺提供能力

          AVS Device SDK在Android平臺基于ffmpeg做解碼實現(xiàn)了音頻播放器,對于我們的場景主要使用用播放器來播放TTS,而TTS是和服務協(xié)商好固定的mp3格式,完全沒有必要為了一個mp3解碼引入一個龐大的ffmpeg庫。

          這里我們使用Android平臺提供的Jni層的媒體庫來做音頻解碼。而且即使是Android平臺JNI層不支持,也可以單獨依賴一個mp3解碼庫,而不是龐大的ffmpeg。對于整個包體積來說,第三方模塊往往相對來說是比較大的。

          4. 使用strip

          使用NDK toolchain可以把調試的C++ 符號表(Symbol Table)中數(shù)據(jù)刪除,我們一般我們打成APK會自動幫我們做這個工作,當然也可以手動設置:

          手動的在鏈接選項中加入 strip參數(shù),配置如下所示:

          SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,-s")

          也可以手動執(zhí)行ndk提供的aarch64-linux-android-strip命令移除動態(tài)庫中的調試信息,這種方式除了前面方法外優(yōu)化體積最高的方式,比如libLibSampleApp.so從48M直接優(yōu)化到了992k。

          4. 設置編譯器的優(yōu)化flag

          編譯器有個優(yōu)化flag可以設置,分別是-Os(體積最小),-O3(性能最優(yōu))等。這里將編譯器的優(yōu)化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

          除了直接刪除占用體積較大的模塊外,編譯器優(yōu)化是排下來優(yōu)化空間最大的方法。設置完-Os后占用提交較大的前幾個庫體積對比:

          庫名優(yōu)化前體積優(yōu)化后體積
          libLibSampleApp.so48M33M
          libAVSCommon.so28M22M
          libDefaultClient.so14M9.9M

          5. 使用 gc-sections去除沒有用到的函數(shù)

          有些時候代碼量比較大的時候我們沒辦法手動發(fā)現(xiàn)無用的函數(shù),這個時候可以可以開啟編譯器的gc-sections選項,讓編譯器自動的幫你做到這一點。

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

          CMake:

          # 去除未使用函數(shù)與變量
          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:

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

          6. 設置編譯器的 Visibility Feature

          Visibility Feature就是用來控制在哪些函數(shù)可以在符號表中被輸入,由于C++并不是完全面向對象的,非類的方法并沒有public這種修飾符,因此,要用Visibility Feature來控制哪些函數(shù)可以被外部調用。而JNI提供了一個宏-JNIEXPORT來控制這點。所以只要對函數(shù)加上這個宏,像這樣:

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

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

          7. 去除C++代碼中的iostream等直接IO相關代碼

          使用STL中的iostream相關庫會明顯的增加包的體積,而Android本身是有預編譯庫(android/log.h)可以代替輸入到控制臺的工具的。在我們的SDK中由于之前是控制臺程序所以用到了輸入輸出,編譯的時候沒有把這塊排除出去,造成了一定的體積冗余。

          8. STL的使用方式

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

          • 靜態(tài)方式(static)

          • 動態(tài)方式(shared)

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

          所以,如果項目中引用library的函數(shù)較多時,用動態(tài)方式可以避免多次拷貝,節(jié)省空間。相反,則直接使用靜態(tài)方式會更節(jié)省空間。由于我們SDK的模塊特別多,再加上整體APK里面已經(jīng)有其他業(yè)務引入了動態(tài)庫,所以我們用動態(tài)庫的方式。

          9. 不使用Exception和RTTI

          關于這兩點在網(wǎng)上看到的沒有實踐過,不過拿過來可以作為包體積持續(xù)優(yōu)化的參考。

          RTTI

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

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

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

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

          Exception

          使用C++的exception會增加包的大小,而目前JNI對C++的exception的支持是有bug的,比如下面這段代碼就會引起程序的crash(對于低版本的android NDK)。因此要在程序中引入exception要自己實現(xiàn)相關邏輯,但是這樣又會增加包體大小。對于開發(fā)者來說,exception可以幫助快速定位問題,而對于使用者并不是那么重要,這里可以去掉。

          10 總結

          本文介紹了刪除無用模塊,平臺能力替代第三方庫,使用 strip,設置編譯器優(yōu)化的 flag,使用gc-sections去除沒有用到的函數(shù),設置可見性,去除iostream等有助于動態(tài)庫體積優(yōu)化的方法。

          原文鏈接: https://juejin.cn/post/7084238491089027079

          推薦閱讀:

          NDK | 帶你梳理 JNI 函數(shù)注冊的方式和時機

          Android NDK 開發(fā):JNI 基礎篇

          Android NDK 開發(fā):Java 與 Native 相互調用

          Android NDK POSIX 多線程編程

          NDK 開發(fā)中 Native 方法的靜態(tài)注冊與動態(tài)注冊

          Android NDK 開發(fā)中快速定位 Crash 問題

          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91AV在线观看爱 | 请立即播放黑人大黑吊日白人小嫩逼视频 | 亚洲AV无码成人精品国产一区 | 人人操人人看人人爱 | 99视频在线免费观看视频 |