最近在Android上开发,主要是将以前Win32以及Windows Mobile上的一些程序库移植到Andriod,并在Java层封装二次开发接口。
虽然Google封装的很彻底,但是Android毕竟是一个Linux系统,Linux相关的很多东西在Android的Native开发中是绕不开的。我一直很懒得碰Linux,多么麻烦啊……当时vista出来没多久,听说ubuntu界面是如何如何华丽blabla的,远不是以前的什么红帽红旗Linux可比,于是第一次装ubuntu,是8.04,其不支持我笔记本的HD2400显卡……后来听说它支持了,于是我装了11.04,可惜此时换了一个新本,它又不支持我的nvidia/i5显卡切换……
前几年真正深入研究使用过的主要是ASP.NET平台和VC这两个环境中的技术。现在托管界中说会.NET就和说会算1+1差不多,连欧美写书的大师们都说.NET程序员整体水平是比Java低……他们怎么就断定学.NET的必定都是拖控件流?……于是别人问我,我就说我写C++的……当然最近确实一直写C++
我发现Android上关于Native开发以及封装的讨论很少,因此边做边总结一些东西。Android NDK和SDK的安装使用、Native开发方法、Android上的JNI等基本的东西已经有好多人写过了好多东西,不再提了,在此主要讨论完全会写Hello Word之后,下一步遇到的各种问题和解决方法。
1. 从 VC 到 Android Native C++的一些改变
函数库、语言相关的东西就不说了,看过C/C++各种开源库源代码的人都知道跨平台对代码的要求,各种宏判断,平台无关性的函数封装,字节对齐,大端小端的处理,我觉得SQLite的VFS是一段多么美丽的跨平台代码……就是写的挺麻烦。
主要提一下编译方面的异同
1.Windows下的DLL为了导出函数,或者要写def文件,或者用丑陋的__declspec(dllexport)。Linux下并不需要类似的机制,默认情况下所有符号都是导出的,而且也不需要.lib这类东西,你可以直接链接一个so,通过头文件定义使用里面的类或函数。
2.Android的应用开发(即不理Android源代码)中,Native编译主要有两种方式:1)使用NDK的ndk-build 2) 使用NDK安装Android GCC工具链,自己写makefile。
VC下估计用nmake编译C++的人不多吧,VC总是智能的编译工程里的所有.cpp,傻瓜式的IDE。NDK提供的这两种编译方式相关的问题之后一点点的总结。
3.Android为了保证后续版本的兼容性,在NDK中仅提供极少数的稳定C++ API,真的是够少的,不过好歹Linux标准C函数和标准的C++库已经基本完备了。除了C函数和STL你还有更多要求?自己编译吧...
4.调试真是一大难,Windows下GDB连接调试只成功过一次,剩下的都莫名其妙的卡死了。暂时先用日志吧,反正也还算方便,因为是移植在Win32和WM上已经正常跑的库,所以需要调试的东西不是很多。
2. Android.mk 与 Application.mk
NDK下的那几页帮助文档是必看的,整个NDK就那么几页文档再不看,光想上网搜搜入门教程是不行滴。
写Android.mk的时候还是遇到了几个小陷阱的。
1. 路径问题。各种mk下千万不要写/cygdrive/....这样的路径。Win下开发Android,NDK是要求使用cygwin的。但是最终运行的编译器gcc之类的还是.exe的Windows程序。mk里还是要写Windows路径的,可以接受的形式诸如 /C:/NDK/xxxxx 或直接 C:/NDK/xxx,注意斜杠的方向
2.LOCAL_PATH := $(call my-dir)这句,陷阱。所以说一定要仔细看那几页文档。$(call my-dir)返回得实际不是【当前mk文件的路径】,而是【最后打开的mk文件的路径】,如下就会出错的
LOCAL_PATH := $(call my-dir)
include $(PROJECT_PATH)/prebuilt.mk
.........
这样用的时候,LOCAL_PATH可能在prebuilt.mk中被改写
include $(PROJECT_PATH)/prebuilt.mk
LOCAL_PATH := $(call my-dir)
.........
这样用的时候,LOCAL_PATH实际获得的是prebuilt.mk的路径
mk实际是makefile的一部分,可以参考NDK里面自带的一些库的Android.mk,其实写法很灵活的
3. 注意使用ndk-build时,ndk-build直接找 jni/Android.mk,所有在此mk中包含的mk也会被加入,但是编译并不是发生在$(BUILD_SHARED_LIBRARY)这句执行时,这句只是记一下一会儿要编译这个库。一直到所有mk执行完,ndk-build才开始编译各个库,所以想在mk中加入拷贝编译好的库到指定位置等指令的想法只是在做梦,好吧我就做过这个梦,不过最终还是另写编译脚本完成
3. 中文编码问题
NDK上的中文编码问题绝对是一个大老虎,至少比纸老虎强一点,皮纸老虎吧……
实际上不用强调”中文“编码,这就是单纯的不同字符集转换问题。
首先,Android2.2以前不支持wchar_t,好吧现在的确支持wchar_t了,不过这又如何,GCC下wchar_t是坑爹的4字节,不是2字节!怪不得你看各种开源库各种不使用wchar_t,人家都直接用void*或者unsigned short。JNI中的jchar也是unsigned short,所以wchar_t纯粹就是坑爹用的。
虽然可以用编译指令-fshort-wchar强制wchar_t使用2字节,不过这样与其他库链接的时候就会收到无数的警告……除非你保证你遇到的所有库(包括C/C++标准库)都是用这个指令编译的。我试过一个犀利无比的WCHAR16定义,用于自动选择最合适的类型,保证WCHAR16总是2字节字符:
template<int> struct tag_auto_WCHAR_CHOOSER; template<> struct tag_auto_WCHAR_CHOOSER<4> { typedef unsigned short WCHAR16; }; template<> struct tag_auto_WCHAR_CHOOSER<2> { typedef unsigned short WCHAR16; }; typedef tag_auto_WCHAR_CHOOSER<sizeof(wchar_t)>::WCHAR16 WCHAR16;
好吧有点跑题了。。。
首先是源代码文件的编码问题:NDK下C++的char*默认编码是UTF-8,但是你如果这样写: const char* szChina = "中国", 那么就天知道什么编码了。默认情况下,如果你的该代码文件本身使用GBK,则szChina里就是GBK编码,如果代码文件是UTF-8, szChina就也是UTF-8。你非要问如果代码文件是UTF-16?我没试,不过如果你的代码能编译过,想来szChina就会是UTF-8,原因吗,说到这里了你们懂得。如果你不懂,你可以先找 MBCS/UNICODE教程,再找GBK/UTF-8/UTF16的编码规则及其与Unicode的关系教程,再找 UCS-2/UTF-16LE/UTF-16BE区别分析,最后看GCC对于不同编码源文件的处理,然后就一定已经懂了
编码转换:NDK下没提供任何直接的编码转换方法,不过你可以编译一个十分著名的库,iconv,当然要写Android.mk,不过你可以下载大好人hgl868已经改好了的……编译后稍微有点大,900多K……不过去掉除了UTF-8,UTF-16LE,GBK之外的其他编码库后,iconv能精简到200K……至于使用,刚才大好人家的传送门点击打开链接
至于传回Java转换的方法,还是省省吧……使用Android内部的那个转换库?你保证你用这个能继续正常在Android 4.0上运行?