自開發64bit Android L以來,遇到了不少關於64bit的問題,有編譯的,有運行時的;編譯方面的已經有一個文檔介紹,這篇主要介紹64bit運行時,主要是APK的運行環境原理,以及誰決定了或如何讓你的app運行在64bit或32bit的運行環境。html
說到這裏值得注意的是,在64bit Android L裏,也並非全部的進程都運行在64bit下,並且有的進程只運行在32bit下,好比mediaserver進程只有32bit。32Bit進程和64bit進程間跟其餘進程同樣經過binder進行通訊。好比meidaplayer app運行在64bit環境,它要同過jni/binder調用到mediaserver進程裏的服務。(見圖1)
操做系統教科書裏說好啊,系統資源是按照進程分配的,每一個進程之間的資源是獨立的。java
要說APK的運行空間,確定要說到zygote,zygote是一個很是重要的進程,zygote進程的創建是真正的Android運行空間。
以mtk6595爲例看看zygote進程,發現有兩個zygote一個是zygote64一個是zygote,他們分別對應了64bit和32bit的運行空間。(見圖2)android
屏幕快照 2018-04-03 下午3.39.46
c++
root@mt6595:/ # ps | grep "zy"
root 306 1 2013256 59424 ffffffff a9fe73e4 S zygote64
root 309 1 1449624 53036 ffffffff f754d5c0 S zygote
在腳本里確實能夠看到起了兩個zygote進程,也能夠看到system_server是64bit的
root@mt6595:/ # cat init.zygote64_32.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd shell
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
class main
socket zygote_secondary stream 660 root system
onrestart restart zygote
zygote對應的執行文件在system/bin下
root@mt6595:/ # ls -l system/bin/ | grep "app"
lrwxr-xr-x root shell 2015-04-14 13:05 app_process -> app_process64
-rwxr-xr-x root shell 13672 2015-04-14 13:05 app_process32
-rwxr-xr-x root shell 18056 2015-04-14 13:05 app_process64
經過對zygote進程的分析,能夠得出Android L 64bit APK運行環境能夠是64bit也能夠是32bit。架構
是誰決定了APK的運行時是32bit仍是64bit呢?Packagemanager
Packagemanager爲兩個zygote進程作了處理,安裝APP的時候,它把APP的ABI傳給dexopt,這樣dexopt就能夠編譯出對應abi的dex file。
好比app的abi是armeabi,那dexopt編譯出dex file的運行環境就是32bit,若是app的abi是arm64-v8a那dexopt編譯出的dex file運行環境是64bit.
那問題來了,若是app沒有指定abi呢,若是app是system app呢??app
若是沒有指定ABI,在Android L 64bit系統默認編譯成64bit dex;若是已經指定了ABI則根據ABI編譯出對應的dex,好比app的abi是armeabi,那dexopt編譯出dex file的運行環境就是32bit,若是app的abi是arm64-v8a那dexopt編譯出的dex file運行環境是64bit.
注意abi文件夾下必定要有so文件。
System.Loadlibrary的路徑就分別是(有前後順序):
/data/data/packagename/lib
32bit的狀況
/vender/lib
/system/lib
64bit的狀況
/vender/lib64
/system/lib64
以dlna爲例:
沒指定abi:04-20 14:56:15.478 8844 8844 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.testsystemloadlibray-1/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]] couldn't find "libdlna_jni.so"
指定abi:04-20 14:57:08.551 8950 8950 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.testsystemloadlibray-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.testsystemloadlibray-1/lib/arm, /vendor/lib, /system/lib]]] couldn't find "libdlna_jni.so"socket
因爲獲取不到abi信息,因此默認編譯成64bit dex;也能夠指定編譯成32bit的dex。
若是想要APP編譯出32bit的dex,能夠在/system/lib下建一個與apk同名的文件夾,即/system/lib/apkname,該文件夾能夠是空的,也能夠放入對應的該app的32bit的so庫,這樣編譯出的dex是32bit,即在32bit運行環境下運行。
反之,若是想強制編譯出64bit的dex,也能夠在/system/lib64/下作一樣操做。
另外手動修改建文件夾必須重啓機器。
好比/system/priv-app/TestSystemLoadlibrary.apk
System.Loadlibrary的路徑就分別是(有前後順序):
默認編譯成64bit的狀況
/vender/lib64
/system/lib64
指定32bit編譯
/system/lib/ TestSystemLoadlibrary
/vender/lib
/system/lib
指定64bit編譯
/system/lib64/ TestSystemLoadlibrary
/vender/lib64
/system/lib64ide
好比/system/priv-app/Video/Video.apk,也獲取不到abi信息,因此默認編譯成64bit dex;也能夠編譯成32bit dex。
方法是以Video爲例:在/system/priv-app/Video下創建兩級目錄即/system/priv-app/Video/lib/arm該目錄能夠是空也能夠放app的32bit so庫,那app編譯出的dex是32bit的運行在32bit的環境。
同理64bit也能夠這樣:/system/priv-app/Video/lib/arm64,app編譯出的便是32bit dex,運行在32bit運行環境。
System.Loadlibrary的路徑就分別是(有前後順序):
默認編譯成64bit的狀況
/vender/lib64
/system/lib64
指定32bit編譯
/system/priv-app/lib/arm
/vender/lib
/system/lib
指定64bit編譯
/system/priv-app/lib/arm64
/vender/lib64
/system/lib64google
預編譯時Android.mk中的LOCAL_MULTILIB不直接影響運行時,它的編譯結果在運行時仍遵循上述規則1,2,3
確定會想到Android.mk的LOCAL_MULTILIB := 32; LOCAL_MULTILIB := both,經過LOCAL_MULTILIB來指定編譯的abi
經過實驗,得出結論:
4.1.Android.mk的LOCAL_MULTILIB在純java app編譯中,編譯出的apk運行時不受LOCAL_MULTILIB影響,也就是說LOCAL_MULTILIB不起做用,解開LOCAL_MULTILIB 不設置和設置LOCAL_MULTILIB := 32編譯出來的apk結構上沒有區別。
4.2.Android.mk的LOCAL_MULTILIB在純java app帶jni或第三方庫的編譯中,
C/C++工程中的Android.mk中的LOCAL_MULTILIB用來指定編譯出來的so/.a的是32bit仍是64bit,該flag不設置,默認編譯出64bit so,設置該flag爲32編譯出32bit so,設置該flag爲both編譯出32bit和64bit的so。
Java工程中的Android.mk中的LOCAL_MULTILIB配合LOCAL_JNI_SHARED_LIBRARIES := libxxx_jni,若是
LOCAL_MULTILIB不設置,默認將64bit的so拷貝到/system/app/appname/lib/arm64下,若是LOCAL_MULTILIB設置爲32,則將32bit的so拷貝到/system/app/appname/lib/arm下,若是LOCAL_MULTILIB設置爲both,則將32bit和64bit的so分別拷貝到/system/app/appname/arm和/system/app/appname/arm64,這樣間接對運行時產生影響。
4.1.以下試驗(純java app):分別不設置LOCAL_MULTILIB和LOCAL_MULTILIB爲32編譯出兩個HTMLViewer.apk,該apk activity create的時候會System.loadlibrary(「dlna_jni」),libdlna_jni.so已經從系統移除。
兩個apk都去load lib64下的so 。可見LOCAL_MULTILIB對純java app運行時無影響。
04-21 12:34:53.139 11007 11007 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/system/app/HTMLViewer/HTMLViewer.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]] couldn't find "libdlna_jni.so"
4.2.試驗結果以下(帶jni,java app):
不設置LOCAL_MULTILIB編譯結果
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls
Bluetooth.apk lib
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/
arm64
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/arm
64
libbluetooth_jni.so
設置LOCAL_MULTILIB爲32的編譯結果
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls
Bluetooth.apk lib
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/
arm
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/arm
libbluetooth_jni.so
設置LOCAL_MULTILIB爲both的編譯結果
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls
Bluetooth.apk lib
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/
arm arm64
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/arm
libbluetooth_jni.so
xuzhenming@xuzhenming:~/platform/m85_base/out/target/product/meizu6795_lwt_l/system/app/Bluetooth$ ls lib/arm64
libbluetooth_jni.so
Android L 64bit經過雙zygote的設計兼容64bit和32bit APP運行;app是運行在32bit仍是64bit是由編譯出的dex file決定的,而dex file是32bit仍是64bit是由一系列規則決定的,善加利用這些規則,可讓你的app隨意load /system/lib或/system/lib64下的庫,由於load哪一個下邊的庫是由該進程的運行環境來決定的,對於app就是dex決定的,進程是32bit運行環境laod /system/lib是64bit運行環境laod /system/lib64
apk 在安裝的時候 package manager service 會將 libs 下的庫拷貝到兩個文件夾
一個是/data/data/YourPackageName/lib
一個是/data/app/YourPackageName-1/lib/arm
目前通過試驗和參考 android 代碼發現,在 4.2 及之後的版本中,拷貝規則以下
1.首選 abi 規則
及你的配置文件中 abi 有以下定義
ro.product.cpu.abi=armeabi-v7a ro.product.cpu.abi2=armeabi ro.product.cpu.abilist=armeabi-v7a,armeabi ro.product.cpu.abilist32=armeabi-v7a,armeabi ro.product.cpu.abilist64=
那首選 abi 就是 armeabi-v7a
對應的若是你到 app 下有 armeabi 和 armeabi-v7a 兩個文件夾,那只有 armeabi-v7a
下邊的庫會被拷貝到/data/data/YourPackageName/lib 2.次 abi 規則
若是你的 app 下只有 armeabi 文件夾,那 armeabi 下的庫會被拷貝到 /data/data/YourPackageName/lib
armeabi 編譯的庫在 arm 架構中是比較通用的
3.google 的設計估計是想每一個 arch 都讓你準備一份代碼,更容易跨 cpu 使用,由於 可能架構多了,涉及到 arm32,arm64,x86_32,x86_64,mips_32, mips_64
按以前的設計不管首選 abi 和次 abi 中的 so 庫都會拷貝到 /data/data/YourPackageName/lib,若是首選 abi 和次 abi 文件夾中有重複的庫首選 abi 的庫會覆蓋次 abi 的庫,以此類推,架構多了,好多架構的庫就會都在同一個 文件夾/data/data /YourPackageName/lib 和/data/app/YourPackageName-1/lib/arm
有點亂,我猜 google 是這麼想的 這樣的話 app 又會變大
對於咱們 app 打包 so
1.把全部庫打包到一個 abi 文件夾,僅限於 32 位,64 位沒試過,不論是 armeabi
仍是 armeabi-v7a 都打包到一個 abi 文件夾,也能夠用
2.按照 google 的設計,每一個 abi 文件夾裏打包一份全的 so,app 會變大 google 怎麼解
針對ndk的應用,簡言之就是app不要隨便連接system/lib下的系統庫或放在system/lib下的私有庫
能夠經過readelf -dl libxxx.so來查看連接了哪些庫,若是有些庫不是本身私有的又不在這個列表裏就要當心了
system_libs := \ android \ c \ dl \ jnigraphics \ log \ m \ m_hard \ stdc++ \ z \ EGL \ GLESv1_CM \ GLESv2 \ GLESv3 \ vulkan \ OpenSLES \ OpenMAXAL \ mediandk \ atomic
能夠不遵循第2點,由於第三方app,不管是androidruntime加載好比System.loadlibrary,仍是動態連接好比a.so被runtime加載,它又連接了b.so,
這種狀況下加載的第一優先目錄永遠是/data/app/com.xxx.appname/lib/arm(arm64);
因此即便你的庫與system/lib下的庫重名,只要在apk裏都會被正確加載
由於system app
(1)runtime好比System.loadlibrary加載的路徑優先順序是:
/system/priv-app/Music/lib/arm, /system/fake-libs,
/system/priv-app/Music/app-xxxhdpi-debug.apk!/lib/armeabi, /system/lib, /vendor/lib, /system/vendor/lib, /custom/lib
(2)但動態連接的時候,優先級發生了變化,system/lib是第一位置的,因此若是你的被動態連接的私有庫和system/lib的庫重名了,那你的庫不能被動態加載
這個變化應該是在android6.0上開始的,以前應該和runtime加載庫的行爲一致
最後
固然最好是同時知足這兩點,那你的apk被放在哪一個android版本,放在哪一個位置都是ok的。