Android中so使用知識和問題總結以及插件開發過程中加載so的方案解析

轉自:https://blog.csdn.net/jiangwei0910410003/article/details/52312451

一、前言

Android中有時候爲了效率以及平臺開發庫的支持,難免會用到NDK開發,那麼都會產生一個so文件,通過native方法進行調用,開發和調用步驟很簡單,這裏就不多說了,本文主要來介紹,我們在使用so的時候總是會出現一些常見的問題,而現在插件化開發也很普遍了,有時候插件中也會包含一些so文件,需要加載,這時候也會出現一些問題。本文就來詳細總結一下這些問題出現的原因,以及解決方法,主要還是通過源碼來分析。

 

二、涉及到的源碼類

因爲本文主要通過分析源碼來分析so使用的知識點和問題總結,所以涉及到了很多的源碼類,這裏就現提供一下:

1、PackageManagerService.java
+setNativeLibraryPaths:設置應用的native庫路徑
+scanPackageDirtyLI:掃描包內容初始化應用信息

2、ActivityManagerService.java
+startProcessLocked:發送命令給Zygote進程啓動一個虛擬機

3、NativeLibraryHelper.java

底層實現類:com_android_internal_content_NativeLibraryHelper.cpp

+copyNativeBinariesWithOverride:釋放apk中的so文件到本地目錄

+findSupportedAbi:遍歷apk中的so文件結合abiList值得到應用支持的abi類型索引值

4、LoadApk類和ApplicationLoaders類

5、VMRuntime.java

底層實現類:dalvik_system_VMRuntime.c

+getInstructionSet:獲取虛擬機的指令集類型

+is64BitAbi:判斷VM是否爲64位

6、Runtime.java

底層實現類:dalvik/vm/native/java_lang_Runtime.cpp,dalvik/vm/Native.cpp

+nativeLoad:加載so文件

 

三、Android中so文件的編譯平臺

Android中在進行NDK開發的時候,都知道因爲機型雜而多的原因,沒有一個大的標準,所以很多廠商都會採用不同型號的cpu,那麼在編譯so文件的時候,就需要進行交叉編譯出多個cpu平臺版本,現在主流的cpu架構版本:

armeabi/armeabi-v7a:這個架構是arm類型的,主要用於Android4.0之後的,cpu值32位的

x86/x86_64:這個架構是x86類型的,有32位和64位,佔用的設備比例比較小

arm64-v8:這個架構是arm類型,主要用於Android5.0之後,cpu是64位的

這裏可以看到,其中arm類型的是往下兼容策略,比如arm64-v8a肯定兼容armeabi/armeabi-v7a,也就是說armeabi/armeabi-v7a架構的so文件可以用在arm64-v8a的設備中的,而armeabi-v7a也是兼容armeabi的,但是因爲cpu型號不同,所以arm體系和x86體系之間是不能相互兼容的。

 

四、Android中so加載流程

在Android中如果想使用so的話,首先得先加載,加載現在主要有兩種方法,一種是直接System.loadLibrary方法加載工程中的libs目錄下的默認so文件,這裏的加載文件名是xxx,而整個so的文件名爲:libxxx.so。還有一種是加載指定目錄下的so文件,使用System.load方法,這裏需要加載的文件名是全路徑,比如:xxx/xxx/libxxx.so。

上面的兩種加載方式,在大部分場景中用到的都是第一種方式,而第二種方式用的比較多的就是在插件中加載so文件了。

不管是第一種方式還是第二種方式,其實到最後都是調用了Runtime.java類的加載方法doLoad:

這裏會先從類加載中獲取到nativeLib路徑,然後在調用native方法nativeLoad(java_lang_Runtime.cpp)

這裏調用了一個核心的方法dvmLoadNativeCode(dalvik/vm/Native.cpp)

注意:

這裏有一個檢測異常的代碼,而這個錯誤,是我們在使用插件開發加載so的時候可能會遇到的錯誤,比如現在我們使用DexClassLoader類去加載插件,但是因爲我們爲了插件能夠實時更新,所以每次都會賦值新的DexClassLoader對象,但是第一次加載so文件到內存中了,這時候退出程序,但是沒有真正意義上的退出,只是關閉了Activity了,這時候再次啓動又會賦值新的加載器對象,那麼原先so已經加載到內存中了,但是這時候是新的類加載器那麼就報錯了,解決辦法其實很簡單,主要有兩種方式:

第一種方式:在退出程序的時候採用真正意義上的退出,比如調用System.exit(0)方法,這時候進程被殺了,加載到內存的so也就被釋放了,那麼下次賦值新的類加載就在此加載so到內存了,

第二種方式:就是全局定義一個static類型的類加載DexClassLoader也是可以的,因爲static類型是保存在當前進程中,如果進程沒有被殺就一直存在這個對象,下次進入程序的時候判斷當前類加載器是否爲null,如果不爲null就不要賦值了,但是這個方法有一個弊端就是類加載器沒有從新賦值,如果插件這時候更新了,但是還是使用之前的加載器,那麼新插件將不會進行加載。

 

繼續往下看:

這裏主要調用了兩個核心的系統方法,dlopen和dlsym,這兩個方法用途還是很多的,一般是先加載so文件,然後得到指定函數的指針,最後直接調用即可,主要用於調用動態的調用so中的指定函數功能。而且這裏注意到了最開始先調用so中的JNI_OnLoad函數,這個函數是so被加載之後調用的第一個方法。

 

到這裏我們就總結一下Android中加載so的流程:

1、調用System.loadLibrary和System.load方法進行加載so文件

2、通過Runtime.java類的nativeLoad方法進行最終調用,這裏需要通過類加載器獲取到nativeLib路徑。

3、到底層之後,就開始使用dlopen方法加載so文件,然後使用dlsym方法調用JNI_OnLoad方法,最終開始了so的執行。

 

五、Android中類加載器關聯so路徑

上面分析so加載過程中可以發現有一個地方,就是通過類加載器來獲取到so的路徑,那麼Android中的主要類加載器有兩個,一個是PathClassLoader和DexClassLoader,關於這兩個類加載不多說了,網上資料很多可以自行查找閱讀。而PathClassLoader是我們Android中默認的類加載器,也就是apk文件就是由他來加載的,我們可以通過查看源碼得知,Android中加載apk的類加載可以從LoadApk.java類查找到:

注意:

這個類很重要的,而這個類加載器也是我們在做插件的時候,需要做一些操作,比如需要把加載插件的DexClassLoader類給添加到這個系統加載器中,就可以解決插件中組件的生命週期問題。

 

看看這個類加載器在哪裏賦值的:

去看看ApplicationLoaders.java類:

看到了,這裏就是定義了PathClassLoader類了,所以我們Android中應用的默認加載器是PathClassLoader,再去看看這個類加載器的nativeLib是哪裏:

 

六、Android中so文件如何釋放

我們在使用System.loadLibrary加載so的時候,傳遞的是so文件的libxxx.so中的xxx部分,那麼系統是如何找到這個so文件然後進行加載的呢?這個就要先從apk文件安裝時機說起。

我們如果還沒有分析源碼之前,大致能夠猜想到的流程是:

在安裝apk的時候,系統解析apk文件,因爲so文件肯定是存放在libs下指定平臺目錄中的,而apk文件本身就是一個壓縮文件,所以可以進行解壓,然後讀取libs目錄下的so文件,進行本地釋放解壓到指定目錄,然後在加載的時候就先拼接so文件的全路徑,最後在進行加載工作即可。

通過猜想,下面就通過源碼來分析一下流程,系統在安裝apk的時候,是調用系統類:PackageManagerService.java類:

主要的核心方法是scanPackageDirtyLI:

這個方法主要通過傳遞的pkg變量,開始構造applicationInfo信息。我們往下面看,找到設置nativeLib信息的代碼:

這裏注意有一個判斷,是不是多平臺架構的應用:

所以,我們看看info.flags有沒有設置這個標誌,我們看到上面的pkg變量是通過解析apk文件的類PackageParser.java類中獲取到的,所以可以去這個類中找這個標誌位的設置。

這裏看到了,如果在AndroidManifest.xml中設置了Application中的multiArch屬性值的話就有,但是我們默認都沒有設置這個屬性值,那麼就是false,也就是說一般應用都不是多平臺的。所以上面的isMultiArch方法就返回false,代碼就走到了這裏:

在這裏就有很多知識點了,而這裏可以看到,就涉及到了so文件的釋放工作了,主要是在NativeLibraryHelper類中,但是這裏看到首先獲取abiList值:

通過Build.SUPPORTED_ABIS來獲取到的:

最終是通過獲取系統屬性:ro.product.cpu.abilist的值來得到的,我們可以使用getprop命令來查看這個屬性值:

這裏獲取到的值是:arm64-v8a,armeabi-v7a,armeabi,我用的是64位的cpu設備,所以可以看到他有多個cpu架構可選,而且看到這個順序會想到,這個順序正好是向下兼容的順序。

 

現在去看看NativeLibraryHelper類的copyNativeBinariesForSupportedAbi方法:

這個方法中主要乾了三件事:

第一件事是獲取應用所支持的arch架構類型

第二件事是通過架構類型獲取so釋放的目錄

第三件事是native層中釋放apk中的指定架構的so到設備目錄中

 

第一件事:獲取應用所支持的arch架構類型

NativeLibraryHelper類的findSupportedAbi方法,其實這個方法就是查找系統當前支持的架構型號索引值:

看看native方法的實現:

這裏看到了,會先讀取apk文件,然後遍歷apk文件中的so文件,得到全路徑然後在和傳遞進來的abiList進行比較,得到合適的索引值,其實實現邏輯很簡單:abiList是:arm64-v8a,armeabi-v7a,armeabi,然後就開始比例apk中有沒有這些架構平臺的so文件,如果有,就直接返回abiList中的索引值即可,比如說apk中的libs結構如下:

那麼這時候返回來的索引值就是0,代表的是arm64-v8a架構的。如果apk文件中沒有arm64-v8a目錄的話,那麼就返回1,代表的是armeabi-v7a架構的。依次類推。得到應用支持的架構索引之後就可以獲取so釋放到設備中的目錄了。

 

第二件事:獲取so釋放之後的目錄

這裏主要通過VMRuntime.java中的getInstructionSet方法:

這裏調用了一個map結構值:

這裏的arch架構和目錄對應關係,如果arch是arm64-v8a的話,那麼目錄就是arm64了。

 

第三件事:釋放apk中的so文件

直接調用的是native層方法iterateOverNativeFiles:

 

好了到這裏就講完了上面的三件事了,而這三件事做完之後,apk中的so文件就會被釋放到本地設備中的指定目錄中了,當然這裏系統會根據abiList中的值以及apk中包含的arch類型的so來決定釋放哪個目錄中的so文件,比如這裏通過ApplicationInfo類來打印當前應用的nativeLibraryDir值:

打印的結果:

看到了,因爲是arm64-v8a類型的,所以目錄是arm64的,而且可以看到這個應用不是多平臺的。

 

我們可以看到Android中是如何釋放apk中的so文件到本地目錄的:

1、通過遍歷apk文件中的so文件的全路徑,然後和系統的abiList中的類型值進行比較,如果匹配到了就返回arch類型的索引值

2、得到了應用所支持的arch類型之後,就開始獲取創建本地釋放so的目錄

3、然後開始釋放so文件

 

我們在PackageMangerService類中繼續往下看:

這裏還要保存上面獲取到應用支持的arch類型值,我們可以使用反射打印這個值:

打印結果:

這個值在後面應用創建VM的時候會用到。

 

接着開始設置應用的nativeLib路徑了:

看看這個方法的實現:

這裏先判斷是不是64位:

通過arch類型對應的目錄來判斷的:

這裏如果是64位,目錄就是lib,如果是32位就是lib64:

這樣就和我們上面釋放so文件的目錄保持一致了,所以這裏的ApplicationInfo類中的lib路徑就是我們上面釋放so之後的路徑了。

 

在之前說到了類加載器中的lib路徑,我們可以打印一下庫路徑的,這裏直接使用getClassLoader得到加載器打印即可:

這裏看到Library的目錄包含很多路徑。

 

七、Android中64位系統如何兼容32位的so

上面分析完了,so文件的釋放工作,下面繼續來看一下如果一個64位系統的Android設備如何做到能夠運行32位的so文件,這個就需要從應用的啓動說起了,那麼這個類就是ActivityManagerService.java,有一個核心的方法:startProcessLocked,這個方法就是向Zygote進程發送一個消息,爲這個應用創建虛擬機開始運行程序了:

這裏在發送消息給Zygote進程,看到這裏通過ApplicationInfo中的primaryCpuAbi類型告訴Zygote改創建多少位的虛擬機,我們查看系統啓動文件init.rc內容:

這裏會啓動一個64位的Zygote進程

然後啓動一個32位的Zygot進程

 

所以這裏應該就可以想明白了,原來系統啓動的時候,如果是64位的系統設備,會啓動兩個Zygote進程用來兼容32位類型的應用,我們可以使用ps命令查看進程:

看到了,這裏果然啓動了兩個Zygote進程,一個64位的,一個是32位的。所以兼容功能的大致流程圖應該是這樣的:

上層啓動應用的時候會把應用的abi類型帶過來,然後這裏會根據這個類型發送給具體的Zygote進程消息,來創建虛擬機開始運行程序,這樣就做到了兼容。

 

八、Android插件中如何加載so文件

有時候我們在開發插件的時候,可能會調用so文件,一般來說有兩種方案:

一種是在加載插件的時候,先把插件中的so文件釋放到本地目錄,然後在把目錄設置到DexClassLoader類加載器的nativeLib中。

一種在插件初始化的時候,釋放插件中的so文件到本地目錄,然後使用System.load方法去全路徑加載so文件

這兩種方式的區別在於,第一種方式的代碼邏輯放在了宿主工程中,同時so文件可以放在插件的任意目錄中,然後在解壓插件文件找到這個so文件釋放即可。第二種方式的代碼邏輯是放在了插件中,同時so文件只能放在插件的assets目錄中,然後通過把插件文件設置到程序的AssetManager中,最後通過訪問assets中的so文件進行釋放。

 

上面就全部分析完了Android中關於so加載的相關內容:

1、so編譯平臺問題

2、so加載流程分析

3、so文件釋放功能分析

4、so文件兼容功能分析

5、插件中so文件調用功能分析

 

九、常見問題分析

第一個問題:Could not find libxxx.so

這個問題看上去很好理解,就是在調用加載so的方法的時候,到底層使用dlopen方法打開so文件,發現找不到這個so文件,那麼這個問題產生的原因主要有兩個:

第一個是我們的確忘了在工程的libs下存放so文件了;

第二個是我們把so文件放錯目錄了;

第一個原因就不多說了,主要來看第二原因:

有時候我們在開發項目的時候,可能會放多個架構類型的so文件,那麼現在假如我的設備是arm64-v8a類型的,我的項目中有三個so文件,比如叫做AAA.so,BBB.so,CCC.so,然後我再arm64-v8a目錄中放了AAA.so,BBB.so,而CCC.so忘了放了,但是會放到armeabi-v7a和armeabi目錄中,那麼這時候就會發生找不到CCC.so的錯誤,原因很簡單:

上面分析了apk中so文件的釋放邏輯,系統會先遍歷apk中所有so文件的全路徑,然後在結合abiList的值來決定最終釋放哪個目錄中的so文件,那麼現在系統是arm64-v8a了,而apk中的libs下也有arm64-v8a,所以這裏就會把apk中的libs\arm64-v8a中的所有so文件釋放解壓到本地目錄中,而不會在去釋放armeabi/armeabi-v7a了。因爲arm64-v8a中沒有CCC.so文件,所以最終釋放到本地目錄中也是沒有這個so文件的,所以加載時找不到文件了。

解決辦法:就是在使用so文件的時候,需要確定在每個架構類型目錄中都要有相同的so文件即可。

 

第二個問題:32-bit instead of 64-bit

這個問題的原因主要是因爲64位的Zygote進程創建的虛擬機中加載了32位的so文件,這個問題的產生原因主要有兩個:

第一個是我們把不同架構類型的so文件放錯目錄了,比如armeabi/armeabi-v7a的so文件放到了arm64-v8a中了

第二個是我們在開發插件的過程中,宿主工程中有arm64-v8a目錄,但是插件中加載so卻是armeabi/armeabi-v7a類型的

第一個原因就不多說了,主要是因爲so放錯目錄了,來看一下第二個原因,我們在開發插件的時候有時候需要在插件中去加載so文件,一般都是使用System.load方式去加載全路徑的so文件,那麼這裏就可能存在一個問題,比如宿主工程中,放了所有架構的目錄,包括了64位的,因爲考慮插件的大小,所以在插件中只放了armeabi-v7a目錄的so文件,如果設備是64位的系統,那麼這時候插件加載so文件就會報錯。原因就在於上面分析的so兼容問題中說到了,因爲宿主工程中包含了64位的架構arm64-v8a類型,系統的abiList中也有arm64-v8a類型,所以這時候應用的ApplicationInfo的abi就是arm64-v8a了,那麼就會發送消息給Zygote64的進程,創建的也是64位的虛擬機了,而最後插件中加載so的類型是32位的armeabi-v7a,那麼就會報錯了,因爲32位的so文件不能運行在64位的虛擬機中的。

解決辦法:宿主工程和插件工程中的so文件的架構類型保持一致,這個將會帶來一個很大的問題,就是插件包會變得很大,因爲宿主工程爲了兼容多數機型,加入了多個類型的架構so文件,但是插件爲了減小包大小,就放了指定類型的so文件,但是最終會存在這種問題,所以這個解決辦法就要看項目需要了。

 

還有一個類似的問題:64-bit instead of 32-bit:

原理都是一樣的,32位的虛擬機中加載了64位的so文件問題導致的。

 

第三個問題:Shared library already opened

這個問題在上面介紹so加載流程中已經介紹過了,原因主要是因爲之前使用DexClassLoader加載so之後,so沒有釋放還在內存中,而在此啓動有弄了一個新的DexClassLoader對象去加載so問題,就出錯了。

我們使用DexClassLoader類去加載插件,但是因爲我們爲了插件能夠實時更新,所以每次都會賦值新的DexClassLoader對象,但是第一次加載so文件到內存中了,這時候退出程序,但是沒有真正意義上的退出,只是關閉了Activity了,這時候再次啓動又會賦值新的加載器對象,那麼原先so已經加載到內存中了,但是這時候是新的類加載器那麼就報錯了。

解決辦法:

 

第一種方式:在退出程序的時候採用真正意義上的退出,比如調用System.exit(0)方法,這時候進程被殺了,加載到內存的so也就被釋放了,那麼下次賦值新的類加載就在此加載so到內存了。

第二種方式:就是全局定義一個static類型的類加載DexClassLoader也是可以的,因爲static類型是保存在當前進程中,如果進程沒有被殺就一直存在這個對象,下次進入程序的時候判斷當前類加載器是否爲null,如果不爲null就不要賦值了,但是這個方法有一個弊端就是類加載器沒有從新賦值,如果插件這時候更新了,但是還是使用之前的加載器,那麼新插件將不會進行加載。

 

十、技術概要

本文主要介紹了Android中關於so的相關知識,主要包括so編譯多架構問題,so加載流程問題,so釋放問題,so系統兼容問題以及插件中加載so文件的功能解析,看完本文之後,我們需要了解到的知識點:

1、在NDK開發時,可以指定多種架構類型編譯出多種類型的so文件。

2、so的加載流程主要是System類中的兩個加載方法,最終都會調用Runtime中的nativeLoad的native方法,而這個native方法最終會調用dlopen來打開so文件,然後在調用dlsym方法調用so的JNI_OnLoad方法。

3、關於apk文件在安裝的時候釋放so文件到本地目錄中,主要是結合當前設備的abiList信息(這個信息主要是通過系統屬性:ro.product.cpu.abilist值來獲取的)和apk中不同類型架構,來決定最終釋放哪個類型目錄中的so文件,釋放完成之後,還需要設置應用的nativeLib路徑,以及應用的abi信息,因爲這個abi信息在後面啓動虛擬機的時候需要用到。

4、因爲現在有很多設備已經是64位系統了,但是爲了兼容32位的so文件,所以這些64位系統就會在系統啓動的時候創建兩個Zygote進程,一個是64位的,一個是32位的,當一個應用啓動的時候,需要創建虛擬機,那麼這時候就會把應用的架構類型傳遞過去,系統會根據這個類型來交給哪個Zygote進程來處理這個應用啓動事件。這樣就可以做到so調用的兼容問題了。

5、插件中加載so文件現階段主要有兩種方式,一種是先釋放插件中的so文件到本地目錄,然後設置DexClassLoader的nativeLib路徑;還有一種方式是先釋放插件中的so文件,然後調用System.load來加載全局路徑的so文件。

 

十一、問題總結

本文還總結了在使用so文件的時候,會遇到的一些問題,主要是三個問題:

1、so文件找不到問題

這個問題一般是因爲我們忘記放了so文件,或者是so文件沒有放置全部,也就是沒有在libs目錄中所有的架構類型目錄中放置。

2、不同位數的虛擬機運行了不同位數的so文件

這個問題一般是因爲我們在libs目錄中把so文件放錯目錄了,或者是宿主工程和插件工程中的so文件架構類型目錄沒有保持一致。

3、類加載器加載so文件再次加載

這個問題一般是因爲插件開發中使用了不同的DexClassLoader去加載多次相同的so文件導致的。

 

十二、知識延展

我們在開發的過程中有時候想知道系統的位數,那麼這裏網上告知說有好幾種方法,其實那些都是忽悠人的,特別是在使用這個api的時候:android.os.Build.CPU_ABI,我就是在項目中被這個方法坑爹了,這個方法其實不是獲取系統的位數,而是獲取當前應用的架構類型位數,就是我們前面分析的ApplicationInfo中的abi信息,我們可以查看一下源碼:

這裏可以看到,這個字段已經被廢棄了,因爲他不靠譜呀,這個字段在Build類的static塊中進行賦值的:

這裏會通過VMRuntime類的is64Bit方法來判斷當前虛擬機的位數,來獲取這個值

這裏還有兩個系統屬性:

ro.product.cpu.abilist32是32位的所有arch架構類型

ro.product.cpu.abilist64是64位的所有arch架構類型

而這兩個字段值的合集就是前面的ro.product.cpu.abilist屬性值。

而VMRuntime的is64Bit方法是native方法,實現如下:

看到了,這裏得到的是虛擬機的位數,那麼就是上面的Zygote進程的位數了。那麼問題就來了,假如我的設備是64位的,但是我的項目中沒有arm64-v8a類型的so文件,這時候在解析apk進行釋放so文件的時候,就會得知架構類型是armeabi/armeabi-v7a了,因爲遍歷apk文件,沒有找到arm64-v8a類型的so文件,這時候應用的abi類型就是armeabi/armeabi-v7a了,這就是32位的了,就會通知32位的Zygote進程創建了一個32位的虛擬機,那麼此時我的項目中通過Build.CPU_ABI得到的系統位數就是32了,那麼完全不是我們想要的了。

 

所以正確的獲取系統位數的方法是:

Android5.0系統之後,可以通過ro.product.cpu.abilist屬性字段值來判斷,如果這個字段值中包含了64的話,那麼就是64位系統了

Android5.0系統之前,需要通過ro.product.cpu.abi屬性字段值來判斷,不過5.0系統之前都是32位的,還沒有出現64位呢。

 

十三、選擇適當架構類型減小包大小

我們上面分析之後可以看到,如果想做到萬無一失即,項目不報錯,而且so運行效率也是非常高的話,就需要把那幾個架構類型的so文件都要在項目中放一遍,那麼這個問題就來了,如果so文件較大的話,apk包最終也是很大的,所以這裏就需要做一次選擇了。

1、我們在開發一個項目的時候因爲,整個項目的so文件結構我們可以控制,所以爲了防止apk包增大,我們可以考慮只放幾個架構類型的so文件,比如最好的是放armeabi類型的,因爲首先現在大部分設備採用cpu型號都是arm的,少數採用x86或者是mips類型的,其次是防止了armeabi類型之後,對於armeabi-v7a和arm64-v8a就可以兼容了,不會存在報錯問題。但是因爲系統需要兼容所以就會出現so運行效率的問題了,最好的效率就是指定架構類型的so運行在對應架構類型的設備中。因爲現在大部分的設備系統版本都是4.0以上了,所以armeabi-v7a架構類型用的比較多了,所以有時候爲了效率問題,項目中只放了這個架構類型的so文件,那麼像老版本的手機armeabi的話就會報錯了,當然這個錯誤是可以接受的即可。

2、有時候像x86和mips等少數類型架構的設備,開發程序的時候會單獨出一個版本比如叫做xxx應用x86版本

3、在開發SDK的時候,因爲開發之後的SDK包是給其他app接入的,而對於接入的app,我們不能做太多的限制,所以理論上應該把所有架構類型的so都要提供,這樣給需要接入的app進行選擇即可,比如像百度地圖SDK:

 

十四、總結

本文主要是介紹了Android中關於so的相關知識,而這些知識點都是在使用so文件中會經常用到的,同時一些問題也是我們會遇到的,這裏只是做了一個總結,同時也給出了插件中加載so文件的方案已經遇到的問題解決思路等內容。