自开发64bit Android L以来,遇到了很多关于64bit的问题,有编译的,有运行时的;编译方面的已经有一个文档介绍,这篇主要介绍64bit运行时,主要是APK的运行环境原理,以及谁决定了或如何让你的app运行在64bit或32bit的运行环境。
Android L 64bit系统里的进程
说到这里值得注意的是,在64bit Android L里,也并不是所有的进程都运行在64bit下,而且有的进程只运行在32bit下,比如mediaserver进程只有32bit。32Bit进程和64bit进程间跟其他进程一样通过binder进行通信。比如meidaplayer app运行在64bit环境,它要同过jni/binder调用到mediaserver进程里的服务。(见图1)
操作系统教科书里说好啊,系统资源是按照进程分配的,每个进程之间的资源是独立的。
zygote进程
要说APK的运行空间,肯定要说到zygote,zygote是一个非常重要的进程,zygote进程的建立是真正的Android运行空间。
以mtk6595为例看看zygote进程,发现有两个zygote一个是zygote64一个是zygote,他们分别对应了64bit和32bit的运行空间。(见图2)
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
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环境下的规则
是谁决定了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呢??
这就涉及到了一些规则:
1.对于第三方安装的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"
2.对于系统自带的放在/system/app或/system/priv-app根目录下的APP
由于获取不到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/lib64
3.对于系统自带的放在/system/app或/system/priv-app子目录下的APP
比如/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/lib64
4.对于预编译的apk的情况
预编译时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
刚出64bit的时候写的内容可能没有那么准确了。
apk so库打包规则
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 怎么解
apk链接系统库有两点注意:
1.Android N上的规则变化:https://developer.android.goo...
针对ndk的应用,简言之就是app不要随便链接system/lib下的系统库或放在system/lib下的私有库
2.app使用的私有库最好起个专有的名字,不要和system/lib下的库重名
针对以上两点,第三方应用非system app和system app要分开来看
1).对于第三方应用,必须遵循第1点,保证能在所有android版本上运行,尤其是android N;
可以通过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里都会被正确加载
2).对于system app,可以不遵循第1点,但必须遵循第2点
因为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的。