Android NDK开发问题集
如何把jni中的的crash堆栈解析为cpp源代码位置,目前版本(r7/r7b)NDK提供了如下命令
ndk-stack.exe -sym <path> [-dump <path>]
其 中"sym"对应的path是编译出来的二进制symbol的文件夹路径,通常是$PROJECT_PATH/obj/local /<abi>,“dump”对应的是crash log的路径。crash log来自logcat输出,详细内容请参考NDK文档:android-ndk-r7\docs\NDK-STACK.html
库工程的组织方式:大体上来说有Prebuilt,ImportModule,include 库文件的mk三种方式。
Prebuilt 方式按照正常的思路只能按照编译成Shared Library,单独的一个库定义为Static Library make之后不会生成.a文件,Static库只有在被其他Shared库引用的时候才会进行编译。当然这里我们可以有一个ugly的补丁,在 Static库后面定义一个dummy的Shared库引用之,这样就可以编译生成对应的.a文件,但是我没有试过是否实际可行并且也不推荐这种做法,原 因在下一段。
Import Module方式和直接include公共库的mk文件很相似,唯一区别就是一个在$(NDK_MODULE_PATH)环境变量中查找,一个手工指定路 径,相对来说我更倾向于后者。这两种库文件都可以自行选择为shared库或者Static库。在Static方式编译的库文件的时候,库文件本身的 Application.mk设定STL版本和LOCAL_LDLIBS设定无效,也就是说link选项取决于引入库文件的设定,为了保证引用的库文件和 主工程保持一致建议使用这种方式编译Static库而不是上面dummy那种。
如下命令可以获取java函数的签名,用于jni代码中C++对于java的调用,详情见java文档
javap -s -p <ClassName>
NDK build如何使用并行编译:ndk-build -j 或者 ndk-build -j4 ,j 后面的数字表示指定的并行编译进程数,通常你的CPU有多少核心就指定多少进程。通常make -j是可以自动获取系统CPU数量决定开启进程数的,但是根据我在win7+NDK r5的测试结果表明,NDK的make无法正确获取cpu数量,会开启几十个编译,导致速度反而变慢,而且出现编译错误的情况,因此只能手工指定-j数 量。
如果需要联网,需要在androidmanifest.xml文件中声明:
<uses-permission android:name="android.permission.INTERNET" />
开发遇到的问题:
LOCAL_SHARED_LIBRARIES 和LOCAL_SHARED_LIBRARY:注意前者是复数S形式,用于Link多个库(只有一个也可以用),后者只能添加一个链接库,可恶的文档关于 Prebuilts的介绍里面给出的例子是LOCAL_SHARED_LIBRARY,使用两个库的时候第二个库死也link不上。
某 些依赖关系很有问题,makefile只能clean掉jni文件夹下的obj文件;更换不同版本的C++ RunTime实现必须要clean,否则会出现link错误;注意一共有四种libstdc++实现,什么都不写默认是系统自带没有STL的版本,另外 三种在SDK自带文档CPLUSPLUS-SUPPORT当中列出。从系统默认libstdc++切换到带STL的版本同样需要clean
使用STL的时候,APP_STL := xxx 这句话需要写在Application.mk当中,而不是Android.mk
argument list too long问 题:文件很多的时候会出现这种编译或链接报错的情况,不过这个问题源自孩儿他爸Linux的遗传,某些系统命令对于输入参数长度有限制,把工程文件夹映射 到一个盘符路径会好一些。对于Android有人给出了修改SDK的makefile的补丁,似乎是去掉了某个echo命令。
2012.02.26 补充,NDK r7未发现此问题,可能已修正
使用$(call import-module, hello-gl2/jni) 命令import库的时候,如果发生下面的报错:
Android NDK: Are you sure your NDK_MODULE_PATH variable is properly
需要注意NDK_MODULE_PATH是一个环境变量而不是makefile中的变量。不把库的搜索路径作为一个工程属性而是一个系统属性,这种方式或许更适用于系统库,而不是自己写的第三方库。
关于Android不支持wchar_t的 说法:wchar_t作为一种变量类型是内置支持的,和大多数*nix实现一样,为4字节,但是wchar.h中的一系列函数在Android NDK还没有提供,也就是说你需要自己想办法计算wchar_t*的字符串长度、比较字符内容等等。推荐使用CrystaX制作的加强版NDK解决此问 题,传送门如下:
http://www.crystax.net/en/android/ndk
经试验由官方NDK r7迁移到此版本(r7-4)没遇到什么问题,作者的修改最大的两个亮点,
1. 增加了GCC4.6.3的选择,可以获得c++0x的部分支持,以及更好的内存性能(这一点没有测试过)
2. 完善了wchar_t一族函数的支持,当然,需要启动之后setlocale一下才能够正确的swprintf汉字的字符串。这一点多说两句,虽然很多人 骂微软,但是的确windows才是对开发人员最友好的平台,大家的locale默认都是"C",但是win32上面啥都不用改就能swprintf汉字 文本,*niux、iOS都做不到。
NDK代码编译完毕后,需要在Eclipse中选中工程按F5 refresh一下,在Eclipse之外的任何代码资源改动都需要此步骤。有些时候还会出现莫名其妙的编译错误需要clean,嗯,是不是想起了XCode?
资 源组织方式,可以使用AssetManager,需要注意不要让SDK的打包apk工具把asset目录的文件压缩,否则在AssetManager Open文件的时候可能会抛异常(具体情况取决于android系统版本,在某些机器上正常读取,某些机器会抛异常。根据某些人的说法,读取带有压缩的 asset文件,AssetManager会把文件解压到内存中,如果系统内存不足就会抛异常;对于非压缩文件,直接读取数据即可没有这一道开销)。通常 在asset目录容量超过1M的文件会被压缩,但是有几种扩展名的文件可以确保不会被压缩,打包工具认为这些类型的文件已经过压缩,无需再压,常见的.zip.mp3都没有问题 (2012.03.11 更正:".zip"不行,如果是真正的zip文件不会被压缩,如果其他格式文件改后缀伪装成zip仍然会被压缩,使用mp3后缀名看来是最好的选择)
例子:如果你在asset文件夹下面放了一个2M的a.wav文件,改成a.mp3或者a.zip即可(当然你读取的时候还是按照wav读)。具体有没有被压缩,用zip文件管理工具(譬如7zip)打开apk文件,看原始大小和压缩后大小即可。