移植Android*应用到x86架构
1.移植带NDK的应用
- 获得NDK工具(android-ndk-r10e,https://developer.android.com/ndk/index.html)
- 如果当前程序有makefile文件,修改当前程序的makefile文件(.mk),编辑APP_ABI项包含x86 (如果支持64bit, 则还应包含x86_64)
- APP_ABI := armeabi armeabi-v7a x86 x86_64(或者APP_ABI := all)
- 如果当前程序没有makefile文件,在命令行中添加x86
- $ ndk-build APP_ABI="armeabi armeabi-v7a x86 x86_64"
- 将包含新库的包重新打包成APK(以hello-jni为例)
- $ android.bat update project --path C:/Tools/android-ndk-r10e/samples/hello-jni
Updated local.properties
Added f le C:\Tools\android-ndk-r10e\samples\hello-jni\build.xml
Added file C:\Tools\android-ndk-r10e\samples\hello-jni\proguard.cfg
$ ant -f hello-jni/build.xml debug
Buildfile: C:\Tools\android-ndk-r10e\samples\hello-jni\build.xml
…
debug:
[echo] Running zip align on final apk...
[echo] Debug Package: android-ndk-r10e\samples\hello-jni\bin\HelloJni-debug.apk
BUILD SUCCESSFUL
- 最后一步在x86架构的设备或者x86的模拟器上来执行和测试其正确性。(注意:需对所有的动态库都加上x86的编译选项,并且编译。如果App用到了第三方SDK, 第三方SDK相关的.so文件也需要将对应的x86版本放置在lib/x86/ 和lib/x86_64/目录中)
- 注: 为保证兼容性,请不要将编译出来的.so文件随便挪移到其他目录进(比如assets),以免运行时发生错误
2.如何判断当前CPU的类型
- 为保证任何情况下都能返回正确的结果,请使用类似如下的java代码来检查当前运行的设备是否是x86设备(不要使用其他的方式,包括NDK中的android_getCpuFamily接口, 以及 android.os.Build.ABI这样的java接口):
InputStreamReader ir = null; BufferedReader br = null; try { Process process = java.lang.Runtime.getRuntime().exec("getprop ro.product.cpu.abi"); ir = new InputStreamReader(process.getInputStream()); br = new BufferedReader(ir); final String abi = br.readLine(); if(null != abi && abi.contains(“x86”)){ // x86 device }else if(null != abi && abi.contains(“arm”)){ // arm device } } catch(Exception ex){ //@TODO: exception handling }finally{ //@TODO: resource clean up }
3.了解NDK中的GCC
- 针对x86的默认的GCC编译器标志
目标:Pentium Pro
-march=i686 –msse3 -mstackrealign -mfpmath=sse
- 默认情况下,ABI不包含MOVBE, SSSE3, SSE4等
- IA-32 Only
- 对于大多数x86平台而言,使用默认的GCC标志很可能不能产生最有效率的代码
4.让GCC生成的代码更有效率
- 探测CPU的类型并提供相应的应急方案
- 针对CPU调整编译标志
-march=core2
-march=atom
…
- 使用特别指令集
-msse4
-mavx
-maes
…
- 更多信息可以参阅:
https://gcc.gnu.org/onlinedocs/gcc-4.5.3/gcc/i386-and-x86_002d64-Options.html
5.让GCC生成的代码更有效率-示例
Example: /jni/Android.mk: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c ifeq ($(TARGET_ARCH_ABI),x86) LOCAL_CFLAGS += -O3 -ffast-math -mtune=atom -msse3 -mfpmath=sse //针对特定x86平台使用对应的编译参数 else LOCAL_CFLAGS += … //针对其它平台使用对应的编译参数 endif include $(BUILD_SHARED_LIBRARY)
6.ARM vs x86 内存地址对齐问题 1
#define LOG_TAG "libtestjni" #define LOGI(...) _android_log_print(ANDROID_LOG_INFO,LOG_TAG,VA_ARGS_) #define LOGE(...) _android_log_print(ANDROID_LOG_ERROR,LOG_TAG,VA_ARGS_) #define OFFSET(x, y) &((x*)0)->y struct TestStruct { int mVar1; //32bit long long mVar2; //64bit int mVar3; //32bit }; JNIEXPORT void JNICALL Java_com_intel_TESTJNILib_run( JNIEnv* env, jobject thiz ) { LOGI("TestStruct (size: %d)", sizeof(TestStruct)); LOGI("-- Var1 offset: %d", OFFSET( TestStruct, mVar1 ) ); LOGI("-- Var2 offset: %d", OFFSET( TestStruct, mVar2 ) ); LOGI("-- Var3 offset: %d", OFFSET( TestStruct, mVar3 ) ); }
7.ARM vs x86 内存地址对齐问题 2
- ARM I/libtestjni( 4675): TestStruct (size: 24) I/libtestjni( 4675): -- Var1 offset: 0 I/libtestjni( 4675): -- Var2 offset: 8 I/libtestjni( 4675): -- Var3 offset: 16 - x86 I/libtestjni( 4079): TestStruct (size: 16) I/libtestjni( 4079): -- Var1 offset: 0 I/libtestjni( 4079): -- Var2 offset: 4 I/libtestjni( 4079): -- Var3 offset: 12
对于TestStruct而言,64-bit的mVar2会有不同的布局,因为对于像mVar2这样64-bit变量,ARM需要用8-byte来对齐。不过在大多数情况下,这并不会造成什么麻烦,因为x86和ARM应用都需要一个完整的重编译过程。
然而,如果一个应用要序列化类或者结构体,这将造成一种尺寸的不匹配。例如:一个基于ARM的应用要保存TestStruct结构体到一个文件,如果之后这个文件被一个x86设备载入,那么这里将会出现文件内容尺寸不匹配的问题,正如我们能想象到的,相同的问题通常会出现在网络传输,使用第三方游戏引擎的游戏开发中。
8.ARM vs x86 内存地址对齐问题 3
- GCC的编译选项“-malign-double”能够针对x86和ARM产生相同的内存对齐方式,不过OS并未使用该选项来编译,所以一些OS的调用还是会出现问题
- 变量的对齐方式还可以通过编译器属性来控制,如果我们通知GCC使用align(8)来对齐mVar2,x86和ARM应用将会拥有相同的对齐方式
struct TestStruct { int mVar1; long long mVar2 _attribute_ ((aligned(8))); int mVar3; }; 输出如下: ARM I/libtestjni( 4675): TestStruct (size: 24) I/libtestjni( 4675): -- Var1 offset: 0 I/libtestjni( 4675): -- Var2 offset: 8 I/libtestjni( 4675): -- Var3 offset: 16 x86 I/libtestjni( 4678): TestStruct (size: 24) I/libtestjni( 4678): -- Var1 offset: 0 I/libtestjni( 4678): -- Var2 offset: 8 I/libtestjni( 4678): -- Var3 offset: 16
9.ARM vs x86 内存地址对齐问题 4
- 在64bit Android中,则不存在内存地址对齐的问题。就是说,arm64-v8a和x86_64这两种架构下,数据对齐方式是一致的
APK打包及发布
1.APK打包
打包方式1 | 1个APK FILE (推荐方式) | 同时支持arm /x86架构 注意:a) lib/x86目录和lib/arm*目录下的.so数量务必保持一致;b) 如果应用支持64bit架构,注意lib/x86_64/下的.so数量必须是完全的,以避免在64bit机器上产生库文件找不到的问题;c) 请不要将arm版本的.so文件放置于x86目录; | 示范应用:墨迹天气 |
打包方式2 | 2个APK FILE | 1 个APK FILE 支持x86架构 (包名注明: for x86) 1个APK FILE 支持arm架构 | 当APK FILE包安装到手机里,运行APK时可以在JAVA代码部分判断当前架构,如果用户下错包,建议增加提示语:如您下载的包有误:请直接从…….下载 或者请下载支持x86芯片的安装程序。 |
2.发布APP
- 建议上传应用至:Google Play,开发者官方网站,以及国内主流下载市场: Baidu Store, Wandoujia Store, 360 Store, 腾讯应用宝,MM商店,联想商店,Appchina等。
附录
如何快速从apk包来判断一个app是NDK还是Dalvik应用
- 使用zip解压缩工具展开apk包
- 查看展开目录下是否有lib目录,以及lib目录里的内容
NDK App Dalvik App