Android L开始 APK 64bit 32bit 运行环境原理及决定运行环境的规则

自开发64bit Android L以来,遇到了不少关于64bit的问题,有编译的,有运行时的;编译方面的已经有一个文档介绍,这篇主要介绍64bit运行时,主要是APK的运行环境原理,以及谁决定了或如何让你的app运行在64bit或32bit的运行环境。html

Android L 64bit系统里的进程

说到这里值得注意的是,在64bit Android L里,也并非全部的进程都运行在64bit下,并且有的进程只运行在32bit下,好比mediaserver进程只有32bit。32Bit进程和64bit进程间跟其余进程同样经过binder进行通讯。好比meidaplayer app运行在64bit环境,它要同过jni/binder调用到mediaserver进程里的服务。(见图1)
操做系统教科书里说好啊,系统资源是按照进程分配的,每一个进程之间的资源是独立的。java

zygote进程

要说APK的运行空间,确定要说到zygote,zygote是一个很是重要的进程,zygote进程的创建是真正的Android运行空间。
以mtk6595为例看看zygote进程,发现有两个zygote一个是zygote64一个是zygote,他们分别对应了64bit和32bit的运行空间。(见图2)android

屏幕快照 2018-04-03 下午3.39.46
clipboard.pngc++

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环境下的规则

是谁决定了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

这就涉及到了一些规则:

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"socket

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/lib64ide

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/lib64google

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的。