好久沒有寫點(diǎn)東西發(fā)了,工作中的事情有點(diǎn)雜,也找不到整塊東西可以寫的。
創(chuàng)新互聯(lián)是一家專注于做網(wǎng)站、成都網(wǎng)站制作與策劃設(shè)計,扎蘭屯網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:扎蘭屯等地區(qū)。扎蘭屯做網(wǎng)站價格咨詢:18982081108
最近調(diào)查了一個問題,稍微追了一下流程,這里記錄一下。
由于我們支持的設(shè)備相對比競品,zygote進(jìn)程多占用了好幾倍的內(nèi)存空間。通過dump meminfo后發(fā)現(xiàn),我們的設(shè)備在so庫,ttf,和unkonwn mmap的內(nèi)存空間相比競品一共大了20多M,其中so庫多了15M左右。
通過查看zygote進(jìn)程的smaps,確定了占用空間最大的幾個so庫確實(shí)是我們自己的。雖然確定了內(nèi)存占用大的原因,還是得把這些so庫是加載在zygote進(jìn)程中的時機(jī)確定了才行。
我的第一反應(yīng)就是在zygote啟動時,加載sharedLibrary()時,把這些庫加載了,于是去看了這部分源碼,并沒有。
通過反復(fù)調(diào)試以及追蹤源碼,最后發(fā)現(xiàn)是在JVM啟動的過程中加載了這些so庫,這些so庫的配置在“system/etc/public.libraries.txt”下。
這個文件里配置的都是public的so庫,能夠被普通app訪問的。類似的配置文件還有“vendor/etc/”下面的,還有一些其他的配置地方,我沒有深入去看,想要看的盆友可以自己去看源碼或者注釋。
下面我就帶大家一起看看虛擬機(jī)加載這些so庫的流程。
調(diào)用棧:
frameworks/base/cmds/app_process/app_main.cpp
----runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
frameworks/base/core/jni/AndroidRuntime.cpp
----AndroidRuntime::startVm
---JNI_CreateJavaVM(pJavaVM, pEnv, initArgs)
art/runtime/jni/java_vm_ext.cc
---android::InitializeNativeLoader();
system/core/libnativeloader/native_loader.cpp
----Initialize()
----ReadConfig(public_native_libraries_system_config, sonames, always_true, error_msg)
其中,public_native_libraries_system_config 為 system/etc/public.libraries.txt
這部分流程是在安卓設(shè)備開機(jī)過程中的,在執(zhí)行ZygoteInit.main()之前會先啟動java虛擬機(jī)的,這樣fork其他java進(jìn)程的時候,java環(huán)境就已經(jīng)有了,不用再創(chuàng)建虛擬機(jī)了。
最后貼一下Initialize()函數(shù):
1,在項目根目錄下建立文件夾libs/armeabi文件夾
2,將so庫放入libs/armeabi文件夾注意事項:
1,如果采用靜態(tài)注冊的方式請注意C文件中嚴(yán)格按照命名規(guī)則Java_packageName_className_method()的方式命名
2,在Android項目中建立同上述命名規(guī)則中packageName中相同的包名,在此包名下建立同上述命名規(guī)則中className相同的類名
3,在className聲明native方法
4,程序中加載so庫System.loadLibrary(data/data/xxx.xxx.xxx/lib/xx.so)或者System.loadLibrary(xx),例如:System.loadLibrary(data/data/com.dtBank.app.service/lib/libjnixcld.so);
作者君主要做SDK開發(fā),對接一些廠商或運(yùn)行商的普通應(yīng)用或系統(tǒng)應(yīng)用。
當(dāng)對接系統(tǒng)應(yīng)用時,由于系統(tǒng)應(yīng)用是由于覆蓋機(jī)型比較廣,會碰到Android多個版本機(jī)型,有的可能出現(xiàn)so找不到的問題。
普通install安裝apk的方式,apk會被安裝在 /data/app 目錄下,那么So則會被映射到/data/app/項目目錄下/lib。
首次安裝只能通過直接push到/system/app/下的方式來安裝,而不是如普通應(yīng)用般采取install的方式。
android在開機(jī)掃描應(yīng)用的時候會對 相應(yīng)目錄進(jìn)行掃描,如果發(fā)現(xiàn) data/app目錄下 存在和系統(tǒng)應(yīng)用同包名的應(yīng)用,并且版本號比系統(tǒng)應(yīng)用的版本號更高則構(gòu)成升級關(guān)系,校驗(yàn)簽名等安全驗(yàn)證通過。此時data/app下的這個應(yīng)用就是系統(tǒng)應(yīng)用
而push到系統(tǒng)目錄下如system/app/....,則會優(yōu)先尋找system/lib(lib64)目錄下的so,由于so
不會自動釋放到該目錄下,所以需要手動push到該路徑下。
作者君還遇到過這樣的問題,有時候?yàn)榱藴p少包的大小或者其他,不會把所有ABI類型的so都放進(jìn)目錄,另外so的提供者團(tuán)隊沒有提供64的so(哈哈,如果提供了,下面的問題直接就沒有了,那為什么還拿出來說呢,主要是簡單地了解一下系統(tǒng)底層的一些基本原理,感興趣的可以看下一下~~)
機(jī)型類型是64位的,其他apk僅提供了64位的so。而某個apk由于只集成了32位so, install安裝是正常的,但是把a(bǔ)pk通過push到/system/app下,so放到/system/lib下則報如下錯誤:
32位機(jī)器上當(dāng)然是正常的。
那么為什么會出錯呢?首先是系統(tǒng)級應(yīng)用,需要理解Android系統(tǒng)的原理(當(dāng)然啦,也許廠商定制了一番,那則另一回事。):
系統(tǒng)有幾個屬性,其中app.info.primaryCpuAbi這個值用來決定apk關(guān)聯(lián)ABI類型。而PackageManager會對這個值有所影響。比如:通過apk包里包含的so庫的架構(gòu)來決定app的primaryCpuAbi的值。
另外:
如果機(jī)器里有64位的apk,且PackageManager掃描到第一正好是這個apk,PackageManager調(diào)整所有apk要加載的都是64位的so。不再去加載32位的so,那么只含32位so的apk就會跑出異常。反之,則64位的apk正常運(yùn)行,32位的則出錯。
作者君能力有限,如有錯處,請書友們指導(dǎo),作者君會第一時間修改。
一起學(xué)習(xí) 一起進(jìn)步 ?( ′???` )比心
1、在src/main中添加
jniLibs文件夾
,把.so復(fù)制進(jìn)去
2、在build.gradle中就添加這么幾行
,
看圖
復(fù)制內(nèi)容到剪貼板
sourceSets
{
main
{
jniLibs.srcDirs
=
['libs']
}
}
3、然后make
project
4、切換到android結(jié)構(gòu)下,你會看到
jniLibs
中.so已經(jīng)變成了.jar文件,證明已經(jīng)成功
android中加載so文件:
在Android中調(diào)用動態(tài)庫文件(*.so)都是通過jni的方式,而且往往在apk或jar包中調(diào)用so文件時,都要將對應(yīng)so文件打包進(jìn)apk或jar包,工程目錄下圖:
Android中加載so文件的提供的API:
void System.load(String pathName);
說明:
1、pathName:文件名+文件路勁;
2、該方法調(diào)用成功后so文件中的導(dǎo)出函數(shù)都將插入的系統(tǒng)提供的一個映射表(類型Map);
3、具體代碼如下:
try {?
String localPath = Environment.getExternalStorageDirectory() + path;?
Log.v(TAG, "LazyBandingLib localPath:" + localPath);
String[] tokens = mPatterns.split(path);?
if (null == tokens || tokens.length = 0?
|| tokens[tokens.length - 1] == "") {?
Log.v(TAG, "非法的文件路徑!");?
return -3;?
}?
// 開辟一個輸入流?
File inFile = new File(localPath);?
// 判斷需加載的文件是否存在?
if (!inFile.exists()) {?
// 下載遠(yuǎn)程驅(qū)動文件?
Log.v(TAG, inFile.getAbsolutePath() + " is not fond!");?
return 1;?
}?
FileInputStream fis = new FileInputStream(inFile);
File dir = context.getDir("libs", Context.MODE_PRIVATE);?
// 獲取驅(qū)動文件輸出流?
File soFile = new File(dir, tokens[tokens.length - 1]);?
if (!soFile.exists()) {?
Log.v(TAG, "### " + soFile.getAbsolutePath() + " is not exists");?
FileOutputStream fos = new FileOutputStream(soFile);?
Log.v(TAG, "FileOutputStream:" + fos.toString() + ",tokens:"?
+ tokens[tokens.length - 1]);
// 字節(jié)數(shù)組輸出流,寫入到內(nèi)存中(ram)?
ByteArrayOutputStream baos = new ByteArrayOutputStream();?
byte[] buffer = new byte[1024];?
int len = -1;?
while ((len = fis.read(buffer)) != -1) {?
baos.write(buffer, 0, len);?
}?
// 從內(nèi)存到寫入到具體文件?
fos.write(baos.toByteArray());?
// 關(guān)閉文件流?
baos.close();?
fos.close();?
}?
fis.close();?
Log.v(TAG, "### System.load start");?
// 加載外設(shè)驅(qū)動?
System.load(soFile.getAbsolutePath());?
Log.v(TAG, "### System.load End");
return 0;
} catch (Exception e) {?
Log.v(TAG, "Exception?? " + e.getMessage());?
e.printStackTrace();?
return -1;
}
我們在Android應(yīng)用程序會常常的加載一些So文件來完成我們的目標(biāo),那么我們的APK加載So是有哪些平時我們沒有注意到的事情呢?
1. 首先我們一般開發(fā)會遇見兩種APK(其實(shí)一般大部分只會遇到一種),一種為系統(tǒng)級APK,另外一種為普通APK。那么這個兩種APK跟So加載有什么關(guān)系呢?別急,讓我們先聊聊我們那些操作會產(chǎn)生這些類型的APK。
普通級AKP:?
pm install +?包名將會把APK安裝到 /data/app 目錄下,同時會把So映射到/data/app-lib/包命/ 目錄下。這個就是普通的APK(pm Install -r 會替換原有的APK,當(dāng)然必須是一樣的簽名)。
系統(tǒng)級APK:
push? + 絕對路徑 + 包名 /system/app 目錄下(必須把原有的包名刪除哦!),這時APK就會在System/app下面了,這時你需要把你的APK的So 同時push到system/lib里面。因?yàn)閍pk里面的So并不會自動映射到system/lib下面。
一般我們在使用加載So的方法時候,會使用到System.load(pathName)和?System.loadLibrary(libName)這兩種方法。這篇文章主要講講System.load(pathName)這個絕對路徑加載的注意點(diǎn)。
我們通常會直接使用
context.getApplicationInfo().nativeLibraryDir +/具體名字.so? 來讓系統(tǒng)幫我尋找加載So所需要的路徑。那么這里問題就來了。
如果是系統(tǒng)級APK
context.getApplicationInfo().nativeLibraryDir = /system/lib/
如果是普通級APK
context.getApplicationInfo().nativeLibraryDir ?=/data/data-lib/PackageName/ 對!就是那個映射的So系統(tǒng)會根據(jù)這個去data/app/包名下面尋找真正的So文件。
這個需要注意的細(xì)節(jié),主要用于在中間件,系統(tǒng)預(yù)置程序的研發(fā)人員與測試上面。我們在拿到芯片廠商給予調(diào)試模式的開發(fā)硬件上進(jìn)行Demo和So的更換測試的時候,需要自己和測試都需要知道,自己安裝的APK是什么類型,會加載什么路徑,以免我們的底層老司機(jī)在幫忙測試問題的時候造成不必要的麻煩。