日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
ANDROID動(dòng)態(tài)加載使用SO庫(kù)時(shí)要注意的一些問(wèn)題

基本信息

公司主營(yíng)業(yè)務(wù):成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。創(chuàng)新互聯(lián)推出白沙黎族免費(fèi)做網(wǎng)站回饋大家。

作者:kaedea

項(xiàng)目:Android-dynamical-loading

Android項(xiàng)目里的SO庫(kù)

正好動(dòng)態(tài)加載系列文章談到了加載SO庫(kù)的地方,我覺(jué)得這里可以順便談?wù)勈褂肧O庫(kù)時(shí)需要注意的一些問(wèn)題。或許這些問(wèn)題對(duì)于經(jīng)常和SO庫(kù)開(kāi)發(fā)打交道的同學(xué)來(lái)說(shuō)已經(jīng)是老生長(zhǎng)談,但是既然要討論一整個(gè)動(dòng)態(tài)加載系列,我想還是有必要說(shuō)說(shuō)使用SO庫(kù)時(shí)的一些問(wèn)題。

在項(xiàng)目里使用SO庫(kù)非常簡(jiǎn)單,在 加載SD卡中的SO庫(kù) 中也有談到,只需要把需要用到的SO庫(kù)拷貝進(jìn) jniLibs(或者Eclipse項(xiàng)目里面的libs) 中,然后在JAVA代碼中調(diào)用 System.loadLibrary(“xxx”) 加載對(duì)應(yīng)的SO庫(kù),就可以使用JNI語(yǔ)句調(diào)用SO庫(kù)里面的Native方法了。

但是有同學(xué)注意到了,SO庫(kù)文件可以隨便改文件名,卻不能任意修改文件夾路徑,而是“armeabi”、“armeabi-v7a”、“x86”等文件夾名有著嚴(yán)格的要求,這些文件夾名有什么意義么?

SO庫(kù)類型和CPU架構(gòu)類型

原因很簡(jiǎn)單,不同CPU架構(gòu)的設(shè)備需要用不同類型SO庫(kù)(從文件名也可以猜出來(lái)個(gè)大概嘛 ╮( ̄▽ ̄”)╭)。

記得還在學(xué)校的時(shí)候,提及ARM處理器時(shí),老師說(shuō)以后移動(dòng)設(shè)備的CPU基本就是ARM類型的了。老師不曾欺我,早期的Android系統(tǒng)幾乎只支持ARM的CPU架構(gòu),不過(guò)現(xiàn)在至少支持以下七種不同的CPU架構(gòu):ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64。每一種CPU類型都對(duì)應(yīng)一種ABI(Application Binary Interface),“armeabi-v7a”文件夾前面的“armeabi”指的就是ARM這種類型的ABI,后面的“v7a”指的是ARMv7。這7種CPU類型對(duì)應(yīng)的SO庫(kù)的文件夾名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。

不同類型的移動(dòng)設(shè)備在運(yùn)行APP時(shí),需要加載自己支持的類型的SO庫(kù),不然就GG了。通過(guò) Build.SUPPORTED_ABIS 我們可以判斷當(dāng)前設(shè)備支持的ABI,不過(guò)一般情況下,不需要開(kāi)發(fā)者自己去判斷ABI,Android系統(tǒng)在安裝APK的時(shí)候,不會(huì)安裝APK里面全部的SO庫(kù)文件,而是會(huì)根據(jù)當(dāng)前CPU類型支持的ABI,從APK里面拷貝最合適的SO庫(kù),并保存在APP的內(nèi)部存儲(chǔ)路徑的 libs 下面。(這里說(shuō)一般情況,是因?yàn)橛欣獾那闆r存在,比如我們動(dòng)態(tài)加載外部的SO庫(kù)的時(shí)候,就需要自己判斷ABI類型了。)

一種CPU架構(gòu) = 一種對(duì)應(yīng)的ABI參數(shù) =  一種對(duì)應(yīng)類型的SO庫(kù)

到這里,我們發(fā)現(xiàn)使用SO庫(kù)的邏輯還是比較簡(jiǎn)單的,但是Android系統(tǒng)加載SO庫(kù)的邏輯還是給我們留下了一些坑。

使用SO庫(kù)時(shí)要注意的一些問(wèn)題

1. 別把SO庫(kù)放錯(cuò)地方

SO庫(kù)其實(shí)都是APP運(yùn)行時(shí)加載的,也就是說(shuō)APP只有在運(yùn)行的時(shí)候才知道SO庫(kù)文件的存在,這就無(wú)法通過(guò)靜態(tài)代碼檢查或者在編譯APP時(shí)檢查SO庫(kù)文件是否正常。所以,Android開(kāi)發(fā)對(duì)SO庫(kù)的存放路徑有嚴(yán)格的要求。

使用SO庫(kù)的時(shí)候,除了“armeabi-v7a”等文件夾名需要嚴(yán)格按照規(guī)定的來(lái)自外,SO庫(kù)要放在項(xiàng)目的哪個(gè)文件夾下也要按照套路來(lái),以下是一些總結(jié):

  • Android Studio 工程放在 jniLibs/xxxabi 目錄中(當(dāng)然也可以通過(guò)在build.gradle文件中的設(shè)置jniLibs.srcDir屬性自己指定);
  • Eclipse 工程放在 libs/xxxabi 目錄中(這也是使用ndk-build命令生成SO庫(kù)的默認(rèn)目錄);
  • aar 依賴包中位于 jni/ABI 目錄中(SO庫(kù)會(huì)自動(dòng)包含到引用AAR壓縮包到APK中);
  • 最終構(gòu)建出來(lái)的APK文件中,SO庫(kù)存在 lib/xxxabi 目錄中(也就是說(shuō)無(wú)論你用什么方式構(gòu)建,只要保證APK包里SO庫(kù)的這個(gè)路徑?jīng)]錯(cuò)就沒(méi)問(wèn)題);
  • 通過(guò) PackageManager 安裝后,在小于 Android 5.0 的系統(tǒng)中,SO庫(kù)位于 APP 的 nativeLibraryPath 目錄中;在大于等于 Android 5.0 的系統(tǒng)中,SO庫(kù)位于 APP 的 nativeLibraryRootDir/CPU_ARCH 目錄中;

既然扯到了這里,順便說(shuō)一下,我在使用 Android Studio 1.5 構(gòu)建APK的時(shí)候,發(fā)現(xiàn) Gradle 插件只會(huì)默認(rèn)打包application類型的module的jniLibs下面的SO庫(kù)文件,而不會(huì)打包aar依賴包的SO庫(kù),所以會(huì)導(dǎo)致最終構(gòu)建出來(lái)的APK里的SO庫(kù)文件缺失。暫時(shí)的解決方案是把所有的SO庫(kù)都放在application模塊中(這顯然不是很好的解決方案),不知道這是不是Studio的BUG,同事的解決方案是通過(guò)修改Gradle插件來(lái)增加對(duì)aar依賴包的SO庫(kù)的打包支持(GitHub有開(kāi)源的第三方Gradle插件項(xiàng)目,使用Java和Groovy語(yǔ)言開(kāi)發(fā))。

2. 盡可能提供CPU支持的最優(yōu)SO庫(kù)

當(dāng)一個(gè)應(yīng)用安裝在設(shè)備上,只有該設(shè)備支持的CPU架構(gòu)對(duì)應(yīng)的SO庫(kù)會(huì)被安裝。但是,有時(shí)候,設(shè)備支持的SO庫(kù)類型不止一種,比如大多的X86設(shè)備除了支持X86類型的SO庫(kù),還兼容ARM類型的SO庫(kù)(目前應(yīng)用市場(chǎng)上大部分的APP只適配了ARM類型的SO庫(kù),X86類型的設(shè)備如果不能兼容ARM類型的SO庫(kù)的話,大概要嗝屁了吧)。

所以如果你的APK只適配了ARM類型的SO庫(kù)的話,還是能以兼容的模式在X86類型的設(shè)備上運(yùn)行(比如華碩的平板),但是這不意味著你就不用適配X86類型的SO庫(kù)了,因?yàn)閄86的CPU使用兼容模式運(yùn)行ARM類型的SO庫(kù)會(huì)異??D(試著回想幾年前你開(kāi)始學(xué)習(xí)Android開(kāi)發(fā)的時(shí)候,在PC上使用AVD模擬器的那種感覺(jué))。

3. 注意SO庫(kù)的編譯版本

除了要注意使用了正確CPU類型的SO庫(kù),也要注意SO庫(kù)的編譯版本的問(wèn)題。雖然現(xiàn)在的Android Studio支持在項(xiàng)目中直接編譯SO庫(kù),但是更多的時(shí)候我們還是選擇使用事先編譯好的SO庫(kù),這時(shí)就要注意了,編譯APK的時(shí)候,我們總是希望使用最新版本的build-tools來(lái)編譯,因?yàn)锳ndroid SDK最新版本會(huì)幫我們做出最優(yōu)的向下兼容工作。

但是這對(duì)于編譯SO庫(kù)來(lái)說(shuō)就不一樣了,因?yàn)镹DK平臺(tái)不是向下兼容的,而是向上兼容的。應(yīng)該使用app的minSdkVersion對(duì)應(yīng)的版本的NDK標(biāo)本來(lái)編譯SO庫(kù)文件,如果使用了太高版本的NDK,可能會(huì)導(dǎo)致APP性能低下,或者引發(fā)一些SO庫(kù)相關(guān)的運(yùn)行時(shí)異常,比如“UnsatisfiedLinkError”,“dlopen: failed”以及其他類型的Crash。

一般情況下,我們都是使用編譯好的SO庫(kù)文件,所以當(dāng)你引入一個(gè)預(yù)編譯好的SO庫(kù)時(shí),你需要檢查它被編譯所用的平臺(tái)版本。

4. 盡可能為每種CPU類型都提供對(duì)應(yīng)的SO庫(kù)

比如有時(shí)候,因?yàn)闃I(yè)務(wù)的需求,我們的APP不需要支持AMR64的設(shè)備,但這不意味著我們就不用編譯ARM64對(duì)應(yīng)的SO庫(kù)。舉個(gè)例子,我們的APP只支持armeabi-v7a和x86架構(gòu),然后我們的APP使用了一個(gè)第三方的Library,而這個(gè)Library提供了AMR64等更多類型CPU架構(gòu)的支持,構(gòu)建APK的時(shí)候,這些ARM64的SO庫(kù)依然會(huì)被打包進(jìn)APK里面,也就是說(shuō)我們自己的SO庫(kù)沒(méi)有對(duì)應(yīng)的ARM64的SO庫(kù),而第三方的Library卻有。這時(shí)候,某些ARM64的設(shè)備安裝該APK的時(shí)候,發(fā)現(xiàn)我們的APK里帶有ARM64的SO庫(kù),會(huì)誤以為我們的APP已經(jīng)做好了AMR64的適配工作,所以只會(huì)選擇安裝APK里面ARM64類型的SO庫(kù),這樣會(huì)導(dǎo)致我們自己項(xiàng)目的SO庫(kù)沒(méi)有被正確安裝(雖然armeabi-v7a和x86類型的SO庫(kù)確實(shí)存在APK包里面)。

這時(shí)正確的做法是,給我們自己的SO庫(kù)也提供AMR64支持,或者不打包第三方Library項(xiàng)目的ARM64的SO庫(kù)。使用第二種方案時(shí),可以把APK里面不需要支持的ABI文件夾給刪除,然后重新打包,而在Android Studio下,則可以通過(guò)以下的構(gòu)建方式指定需要類型的SO庫(kù)。

 
 
 
 
  1. productFlavors { 
  2.     flavor1 { 
  3.         ndk { 
  4.             abiFilters "armeabi-v7a" 
  5.             abiFilters "x86" 
  6.             abiFilters "armeabi" 
  7.         } 
  8.     } 
  9.     flavor2 { 
  10.         ndk { 
  11.             abiFilters "armeabi-v7a" 
  12.             abiFilters "x86" 
  13.             abiFilters "armeabi" 
  14.             abiFilters "arm64-v8a" 
  15.             abiFilters "x86_64" 
  16.         } 
  17.     } 
  18. }  

需要說(shuō)明的是,如果我們的項(xiàng)目是SDK項(xiàng)目,我們最好提供全平臺(tái)類型的SO庫(kù)支持,因?yàn)锳PP能支持的設(shè)備CPU類型的數(shù)量,就是項(xiàng)目中所有SO庫(kù)支持的最少CPU類型的數(shù)量(使用我們SDK的APP能支持的CPU類型只能少于等于我們SDK支持的類型)。

5. 不要通過(guò)“減少其他CPU類型支持的SO庫(kù)”來(lái)減少APK的體積

確實(shí),所有的x86/x86_64/armeabi-v7a/arm64-v8a設(shè)備都支持armeabi架構(gòu)的SO庫(kù),因此似乎移除其他ABIs的SO庫(kù)是一個(gè)減少APK大小的好辦法。但事實(shí)上并不是,這不只影響到函數(shù)庫(kù)的性能和兼容性。

X86設(shè)備能夠很好的運(yùn)行ARM類型函數(shù)庫(kù),但并不保證100%不發(fā)生crash,特別是對(duì)舊設(shè)備,兼容只是一種保底方案。64位設(shè)備(arm64-v8a, x86_64, mips64)能夠運(yùn)行32位的函數(shù)庫(kù),但是以32位模式運(yùn)行,在64位平臺(tái)上運(yùn)行32位版本的ART和Android組件,將丟失專為64位優(yōu)化過(guò)的性能(ART,webview,media等等)。

過(guò)減少其他CPU類型支持的SO庫(kù)來(lái)減少APK的體積不是很明智的做法,如果真的需要通過(guò)減少SO庫(kù)來(lái)做APK瘦身,我們也有其他辦法。

減少SO庫(kù)體積的正確姿勢(shì)

1. 構(gòu)建特定ABI支持的APK

我們可以構(gòu)建一個(gè)APK,它支持所有的CPU類型。但是反過(guò)來(lái),我們可以為每個(gè)CPU類型都單獨(dú)構(gòu)建一個(gè)APK,然后不同CPU類型的設(shè)備安裝對(duì)應(yīng)的APK即可,當(dāng)然前提是應(yīng)用市場(chǎng)得提供用戶設(shè)備CPU類型設(shè)別的支持,就目前來(lái)說(shuō),至少PLAY市場(chǎng)是支持的。

Gradle可以通過(guò)以下配置生成不同ABI支持的APK(引用自別的文章,沒(méi)實(shí)際使用過(guò)): 

 
 
 
 
  1. android { 
  2.     ... 
  3.     splits { 
  4.         abi { 
  5.             enable true 
  6.             reset() 
  7.             include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for 
  8.             universalApk true //generate an additional APK that contains all the ABIs 
  9.         } 
  10.     } 
  11.  
  12.     // map for the version code 
  13.     project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9] 
  14.  
  15.     android.applicationVariants.all { variant -> 
  16.         // assign different version code for each output 
  17.         variant.outputs.each { output -> 
  18.             output.versionCodeOverride = 
  19.                     project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode 
  20.         } 
  21.     } 
  22.  }  

2. 從網(wǎng)絡(luò)下載當(dāng)前設(shè)備支持的SO庫(kù)

說(shuō)到這里,總算回到動(dòng)態(tài)加載的主題了。⊙﹏⊙

使用Android的動(dòng)態(tài)加載技術(shù),可以加載外部的SO庫(kù),所以我們可以從網(wǎng)絡(luò)下載SO庫(kù)文件并加載了。我們可以下載所有類型的SO庫(kù)文件,然后加載對(duì)應(yīng)類型的SO庫(kù),也可以下載對(duì)應(yīng)類型的SO庫(kù)然后加載,不過(guò)無(wú)論哪種方式,我們最好都在加載SO庫(kù)前,對(duì)SO庫(kù)文件的類型做一下判斷。

我個(gè)人的方案是,存儲(chǔ)在服務(wù)器的SO庫(kù)依然按照APK包的壓縮方式打包,也就是,SO庫(kù)存放在APK包的 libs/xxxabi 路徑下面,下載完帶有SO庫(kù)的APK包后,我們可以遍歷libs路徑下的所有SO庫(kù),選擇加載對(duì)應(yīng)類型的SO庫(kù)。

具體實(shí)現(xiàn)代碼看上去像是: 

 
 
 
 
  1. /** 
  2.  * 將一個(gè)SO庫(kù)復(fù)制到指定路徑,會(huì)先檢查改SO庫(kù)是否與當(dāng)前CPU兼容 
  3.  * 
  4.  * @param sourceDir     SO庫(kù)所在目錄 
  5.  * @param so            SO庫(kù)名字 
  6.  * @param destDir       目標(biāo)根目錄 
  7.  * @param nativeLibName 目標(biāo)SO庫(kù)目錄名 
  8.  * @return 
  9.  */ 
  10. public static boolean copySoLib(File sourceDir, String so, String destDir, String nativeLibName) throws IOException { 
  11.  
  12.     boolean isSuccess = false; 
  13.     try { 
  14.         LogUtil.d(TAG, "[copySo] 開(kāi)始處理so文件"); 
  15.  
  16.         if (Build.VERSION.SDK_INT >= 21) { 
  17.             String[] abis = Build.SUPPORTED_ABIS; 
  18.             if (abis != null) { 
  19.                 for (String abi : abis) { 
  20.                     LogUtil.d(TAG, "[copySo] try supported abi:" + abi); 
  21.                     String name = "lib" + File.separator + abi + File.separator + so; 
  22.                     File sourceFile = new File(sourceDir, name); 
  23.                     if (sourceFile.exists()) { 
  24.                         LogUtil.i(TAG, "[copySo] copy so: " + sourceFile.getAbsolutePath()); 
  25.                         isSuccess = FileUtil.copyFile(sourceFile.getAbsolutePath(), destDir + File.separator + nativeLibName + File.separator + so); 
  26.                         //api21 64位系統(tǒng)的目錄可能有些不同 
  27.                         //copyFile(sourceFile.getAbsolutePath(), destDir + File.separator +  name); 
  28.                         break; 
  29.                     } 
  30.                 } 
  31.             } else { 
  32.                 LogUtil.e(TAG, "[copySo] get abis == null"); 
  33.             } 
  34.         } else { 
  35.             LogUtil.d(TAG, "[copySo] supported api:" + Build.CPU_ABI + " " + Build.CPU_ABI2); 
  36.  
  37.             String name = "lib" + File.separator + Build.CPU_ABI + File.separator + so; 
  38.             File sourceFile = new File(sourceDir, name); 
  39.  
  40.             if (!sourceFile.exists() && Build.CPU_ABI2 != null) { 
  41.                 name = "lib" + File.separator + Build.CPU_ABI2 + File.separator + so; 
  42.                 sourceFile = new File(sourceDir, name); 
  43.  
  44.                 if (!sourceFile.exists()) { 
  45.                     name = "lib" + File.separator + "armeabi" + File.separator + so; 
  46.                     sourceFile = new File(sourceDir, name); 
  47.                 } 
  48.             } 
  49.             if (sourceFile.exists()) { 
  50.                 LogUtil.i(TAG, "[copySo] copy so: " + sourceFile.getAbsolutePath()); 
  51.                 isSuccess = FileUtil.copyFile(sourceFile.getAbsolutePath(), destDir + File.separator + nativeLibName + File.separator + so); 
  52.             } 
  53.         } 
  54.  
  55.         if (!isSuccess) { 
  56.             LogUtil.e(TAG, "[copySo] 安裝 " + so + " 失敗 : NO_MATCHING_ABIS"); 
  57.             throw new IOException("install " + so + " fail : NO_MATCHING_ABIS"); 
  58.         } 
  59.  
  60.     } catch (IOException e) { 
  61.         e.printStackTrace(); 
  62.         throw e; 
  63.     } 
  64.  
  65.     return true; 
  66. }  

總結(jié)

  1. 一種CPU架構(gòu) = 一種ABI = 一種對(duì)應(yīng)的SO庫(kù);
  2. 加載SO庫(kù)時(shí),需要加載對(duì)應(yīng)類型的SO庫(kù);
  3. 盡量提供全平臺(tái)CPU類型的SO庫(kù)支持;

題外話,SO庫(kù)的使用本身就是一種最純粹的動(dòng)態(tài)加載技術(shù),SO庫(kù)本身不參與APK的編譯過(guò)程,使用JNI調(diào)用SO庫(kù)里的Native方法的方式看上去也像是一種“硬編程”,Native方法看上去與一般的Java靜態(tài)方法沒(méi)什么區(qū)別,但是它的具體實(shí)現(xiàn)卻是可以隨時(shí)動(dòng)態(tài)更換的(更換SO庫(kù)就好),這也可以用來(lái)實(shí)現(xiàn)熱修復(fù)的方案,與Java方法一旦加載進(jìn)內(nèi)存就無(wú)法再次更換不同,Native方法不需要重啟APP就可以隨意更換。

出于安全和生態(tài)控制的原因,Google Play市場(chǎng)不允許APP有加載外部可執(zhí)行文件的行為,一旦你的APK里被檢查出有額外的可執(zhí)行文件時(shí)就不好玩了,所以現(xiàn)在許多APP都偷偷把用于動(dòng)態(tài)加載的可執(zhí)行文件的后綴名換成“.so”,這樣被發(fā)現(xiàn)的幾率就降低了,因?yàn)榧虞dSO庫(kù)看上去就是官方合法版本的動(dòng)態(tài)加載啊(不然SO庫(kù)怎么工作),雖然這么做看起來(lái)有點(diǎn)掩耳盜鈴。 


文章標(biāo)題:ANDROID動(dòng)態(tài)加載使用SO庫(kù)時(shí)要注意的一些問(wèn)題
文章起源:http://www.dlmjj.cn/article/dpddcco.html