如何解决android中蓝牙开发 NDK开发中的NDK

1)打开Android开发者的官网找到Develop点击。如果页面打不开,通过代理来访问。
2)进入后再点击Tools
3)进入后在左侧找到NDK点击,可以见到各种平台的NDK r10。
选择需要的下载
5)解压下载的android-ndk32-r10-windows-x86_64.zip,将其放到你想要的目录下。
注:R7之前的版本,必须要安装Cygwin才能使用NDK。从R7开始,Windows版本的NDK提供了一个ndk-build.cmd脚本,可以直接利用这个脚本编译。想要做到这一 点,只要为Eclipse Android工程添加一个Builder,就能实现Eclipse的自动编译NDK,这一点在后面的步骤会有介绍。
6) NDK实例的实现
启动Eclipse,新建Android工程(此处名为TestNDK),如图所示:
7)在TestNDK工程的根目录下新建文件夹jni,之后找到NDK的安装目录(sampleshello-jnijni)下的 Android.mk和hello-jni.c 这两个文件,将其拷贝到TestNDK-&jni文件夹下面。
注:如果你很好奇jni文件夹里的hello-jni.c里的函数命名如此刁钻,是怎么写的,并且你如果你想的更远,已经在考虑自己以后怎么写这种文件,那么我告诉你它里面的函数命名是直接粘贴的一个头文件里的内容。这个头文件可以通过反编译下边提到的HelloJni.java得到。如果你以后要确实要进行NDK开发,那么你肯定要掌握反编译的方法。为了你能够少走弯路,推荐我的另一篇文章《1分钟攻克NDK开发中javah不能反编译的问题》,链接网址http://blog.csdn.net/golden1314521/article/details/ 。
8) 找到NDK的安装目录(sampleshello-jnisrccomexamplehellojni)下的HelloJni.java文件,将其拷贝到TestNDK工程src文件夹对应的包下。
9) 修改项目文件AndroidManifest.xml,将activity标签下的 android:name =&com.zhw.testndk. MainActivity&修改为 android:name =&com.example.hellojni.HelloJni&。(注:此处的
TestNDKACtivity为项目自动生成的,项目不同,此处也会不同)
10) 新建并配置一个Builder
点击Project-&Properties-&Builders-&New,新建立一个Builder。在弹出的对话框上面点击Program,OK!如图所示
在弹出的对话框【Edit Configuration】中,配置选项卡【Main】:
Location中需要填入nkd-build.cmd的路径(NDK安装目录下)。
Working Diretcoty中需要填入TestNDK的工程根目录。如图所示:
注:不要漏了
配置选项卡【Refresh】,如图所示:
勾选&Refresh resources upon completion&,
勾选&The entire workspace&,
勾选&Recuresively include sub-folders&。
配置选项卡【Build Options】,如图七所示:
勾选&After a &Clean&&,
勾选&During manual builds&,
勾选&During auto builds&,
勾选&Specify working set of relevant resources&。
点击&Specify Resources&&勾选TestNDK工程的&jni&目录 ,Finish!
当在console里面看到如图八所示的信息,则表示一切正常,此时就已经是自动编译代码了。
注:如果不成功:Project-&clean-&选择工程-&点击OK
假如你使用的是NDK-R9版本中的例子,你在console里面会看到比上图要多的编译信息,截图如下
这时候你要检查一下工程根目录下的libs目录下的子目录,要删除libs中多余的子文件夹,只留下armeabi文件夹和以jar为后缀名的jar包。
10)右键点击工程TestNDK,Run As -& Android Application, 运行程序。当出现图所示信息时,NDK实例运行成功!
如果程序运行出错,显示&应用程序**(进程:com.example.***)意外停止,请重试&的错误提示
这个错误产生的原因极有可能是你的JNI文件的命名或包的声明写错了。查一下程序运行的日志logcat,显示错误为&java.lang.UnsatisfiedLinkError:stringFromJNI&,说的是Java层调用本地方法stringFromJNI时,却没有找到这个本地方法,所以除了错。
那么Android虚拟机怎么查找这个本地方法的呢?先说一下Android工程的环境:包名称是com.example.testndk,应用名称是HelloJni,如果Java层调用了本地方法String stringFromJNI(),那么Android虚拟机就会由这三个变量生成一个名字:jstring Java_com_example_testndk_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) ,并在本地方法中严格按照这个名字查找同名的本地方法。如果找不到,就会返回上图的错误。所以要确认一下JNI文件的命名或包的声明是否与Java文件中的一致。
在本地文件(即C或C++文件)改正此错误后,运行,正常显示。
阅读(...) 评论()1813人阅读
应部分同学要求,把之前的几篇文章合成这个一篇
正式开始这个话题之前,先简单介绍一下什么是NDK和JNI,部分内容来自网络
Android NDK是什么,为什么我们要用NDK?
Android NDK&是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C
/ C++之类的原生代码语言执行部分程序。NDK包括了:
从C / C++生成原生代码库所需要的工具和build files。将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files,即.apk文件)中。支持所有未来Android平台的一些列原生系统头文件和库
为何要用到NDK?概括来说主要分为以下几种情况:
代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。
Android JNI是什么?和NDK是什么关系?
Java Native Interface(JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI是本地编程接口,它使得在&Java&虚拟机(VM)&内部运行的&Java&代码能够与用其它编程语言(如&C、C++和汇编语言)编写的应用程序和库进行交互操作。
简单来说,可以认为NDK就是能够方便快捷开发.so文件的工具。JNI的过程比较复杂,生成.so需要大量操作,而NDK就是简化了这个过程。
NDK的异常会不会导致程序Crash,NDK的常见的有哪些类型异常?
NDK编译生成的.so文件作为程序的一部分,在运行发生异常时同样会造成程序崩溃。不同于Java代码异常造成的程序崩溃,在NDK的异常发生时,程序在Android设备上都会立即退出,即通常所说的闪退,而不会弹出“程序xxx无响应,是否立即关闭”之类的提示框。
NDK是使用C/C++来进行开发的,熟悉C/C++的程序员都知道,指针和内存管理是最重要也是最容易出问题的地方,稍有不慎就会遇到诸如内存无效访问、无效对象、内存泄露、堆栈溢出等常见的问题,最后都是同一个结果:程序崩溃。例如我们常说的空指针错误,就是当一个内存指针被置为空(NULL)之后再次对其进行访问;另外一个经常出现的错误是,在程序的某个位置释放了某个内存空间,而后在程序的其他位置试图访问该内存地址,这就会产生一个无效地址错误。常见的错误类型如下:
初始化错误访问错误
数组索引访问越界指针对象访问越界访问空指针对象访问无效指针对象迭代器访问越界
内存泄露参数错误堆栈溢出类型转换错误数字除0错误
NDK错误发生时,我们能拿到什么信息?
利用Android NDK开发本地应用的时候,几乎所有的程序员都遇到过程序崩溃的问题,但它的崩溃会在logcat中打印一堆看起来类似天书的堆栈信息,让人举足无措。单靠添加一行行的打印信息来定位错误代码做在的行数,无疑是一件令人崩溃的事情。在网上搜索“Android NDK崩溃”,可以搜索到很多文章来介绍如何通过Android提供的工具来查找和定位NDK的错误,但大都晦涩难懂。下面以一个实际的例子来说明,首先生成一个错误,然后演示如何通过两种不同的方法,来定位错误的函数名和代码行。
首先,看我们在hello-jni程序的代码中做了什么(有关如何创建或导入工程,此处略),看下图:在JNI_OnLoad()的函数中,即so加载时,调用willCrash()函数,而在willCrash()函数中,&std::string的这种赋值方法会产生一个空指针错误。这样,在hello-jni程序加载时就会闪退。我们记一下这两个行数:在61行调用了willCrash()函数;在69行发生了崩溃。
下面来看看发生崩溃(闪退)时系统打印的logcat日志:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/:user/test-keys'
pid: 32607, tid: 32607, name: xample.hellojni
&&& com.example.hellojni &&&
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr
r1 beb123a8
r4 5d635f68
r5 5cdc3198
r6 41efcb18
r7 5d62df44
fp beb1238c
ip 5d635f7c
sp beb12380
lr 5d62ddec
pc 400e7438
backtrace:
/system/lib/libc.so
pc 00004de8
/data/app-lib/com.example.hellojni-2/libhello-jni.so
/data/app-lib/com.example.hellojni-2/libhello-jni.so
pc 00004fb4
/data/app-lib/com.example.hellojni-2/libhello-jni.so
pc 00004f58
/data/app-lib/com.example.hellojni-2/libhello-jni.so
/system/lib/libdvm.so
/system/lib/libdvm.so
/system/lib/libdvm.so
pc 0002b7fc
/system/lib/libdvm.so
pc 00060fe1
/system/lib/libdvm.so
pc 0006100b
/system/lib/libdvm.so
pc 0006c6eb
/system/lib/libdvm.so
pc 00067a1f
/system/lib/libdvm.so
/system/lib/libdvm.so
pc 0002b7fc
/system/lib/libdvm.so
/system/lib/libdvm.so
pc 0006912d
/system/lib/libdvm.so
/system/lib/libdvm.so
pc 0002b7fc
/system/lib/libdvm.so
pc 00060fe1
/system/lib/libdvm.so
pc 00049ff9
/system/lib/libdvm.so
/system/lib/libandroid_runtime.so
pc 0004e1bd
/system/lib/libandroid_runtime.so
pc 00001d37
/system/bin/app_process
pc 0001bd98
/system/lib/libc.so
/system/bin/app_process
如果你看过logcat打印的NDK错误时的日志就会知道,我省略了后面很多的内容,很多人看到这么多密密麻麻的日志就已经头晕脑胀了,即使是很多资深的Android开发者,在面对NDK日志时也大都默默的选择了无视。
“符号化”NDK错误信息的方法
其实,只要你细心的查看,再配合Google&提供的工具,完全可以快速的准确定位出错的代码位置,这个工作我们称之为“符号化”。需要注意的是,如果要对NDK错误进行符号化的工作,需要保留编译过程中产生的包含符号表的so文件,这些文件一般保存在$PROJECT_PATH/obj/local/目录下。
第一种方法:ndk-stack
这个命令行工具包含在NDK工具的安装目录,和ndk-build和其他一些常用的NDK命令放在一起,比如在我的电脑上,其位置是/android-ndk-r9d/ndk-stack。根据Google官方文档,NDK从r6版本开始提供ndk-stack命令,如果你用的之前的版本,建议还是尽快升级至最新的版本。使用ndk
–stack命令也有两种方式
使用ndk-stack实时分析日志
在运行程序的同时,使用adb获取logcat日志,并通过管道符输出给ndk-stack,同时需要指定包含符号表的so文件位置;如果你的程序包含了多种CPU架构,在这里需求根据错误发生时的手机CPU类型,选择不同的CPU架构目录,如:
adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
当崩溃发生时,会得到如下的信息:
********** Crash dump: **********
Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/:user/test-keys'
pid: 32607, tid: 32607, name: xample.hellojni
&&& com.example.hellojni &&&
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr
Stack frame #00
/system/lib/libc.so (strlen+72)
Stack frame #01
pc 00004de8
/data/app-lib/com.example.hellojni-2/libhello-jni.so (std::char_traits&char&::length(char const*)+20): Routine std::char_traits&char&::length(char const*) at /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/char_traits.h:229
Stack frame #02
/data/app-lib/com.example.hellojni-2/libhello-jni.so (std::basic_string&char, std::char_traits&char&, std::allocator&char& &::basic_string(char const*, std::allocator&char& const&)+44): Routine basic_string at /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/_string.c:639
Stack frame #03
pc 00004fb4
/data/app-lib/com.example.hellojni-2/libhello-jni.so (willCrash()+68): Routine willCrash() at /home/testin/hello-jni/jni/hello-jni.cpp:69
Stack frame #04
pc 00004f58
/data/app-lib/com.example.hellojni-2/libhello-jni.so (JNI_OnLoad+20): Routine JNI_OnLoad at /home/testin/hello-jni/jni/hello-jni.cpp:61
Stack frame #05
/system/lib/libdvm.so (dvmLoadNativeCode(char const*, Object*, char**)+516)
Stack frame #06
/system/lib/libdvm.so
Stack frame #07
/system/lib/libdvm.so
Stack frame #08
pc 0002b7fc
/system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+180)
Stack frame #09
pc 00060fe1
/system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+272)
……(后面略)
我们重点看一下#03和#04,这两行都是在我们自己生成的libhello-jni.so中的报错信息,那么会发现如下关键信息:
#03 (willCrash()+68): Routine willCrash() at /home/testin/hello-jni/jni/hello-jni.cpp:69
#04 (JNI_OnLoad+20): Routine JNI_OnLoad at /home/testin/hello-jni/jni/hello-jni.cpp:61
回想一下我们的代码,在JNI_OnLoad()函数中(第61行),我们调用了willCrash()函数;在willCrash()函数中(第69行),我们制造了一个错误。这些信息都被准确无误的提取了出来!是不是非常简单?
先获取日志,再使用ndk-stack分析
这种方法其实和上面的方法没有什么大的区别,仅仅是logcat日志获取的方式不同。可以在程序运行的过程中将logcat日志保存到一个文件,甚至可以在崩溃发生时,快速的将logcat日志保存起来,然后再进行分析,比上面的方法稍微灵活一点,而且日志可以留待以后继续分析。
adb shell logcat & 1.log
ndk-stack -sym $PROJECT_PATH/obj/local/armeabi –dump 1.log
第二种方法:使用addr2line和objdump命令
这个方法适用于那些,不满足于上述ndk-stack的简单用法,而喜欢刨根问底的程序员们,这两个方法可以揭示ndk-stack命令的工作原理是什么,尽管用起来稍微麻烦一点,但是可以满足一下程序员的好奇心。
先简单说一下这两个命令,在绝大部分的linux发行版本中都能找到他们,如果你的操作系统是linux,而你测试手机使用的是Intel x86系列,那么你使用系统中自带的命令就可以了。然而,如果仅仅是这样,那么绝大多数人要绝望了,因为恰恰大部分开发者使用的是Windows,而手机很有可能是armeabi系列。
别急,在NDK中自带了适用于各个操作系统和CPU架构的工具链,其中就包含了这两个命令,只不过名字稍有变化,你可以在NDK目录的toolchains目录下找到他们。以我的Mac电脑为例,如果我要找的是适用于armeabi架构的工具,那么他们分别为arm-linux-androideabi-addr2line和arm-linux-androideabi-objdump;位置在下面目录中,后续介绍中将省略此位置:
/Developer/android_sdk/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/
假设你的电脑是windows,&CPU架构为mips,那么你要的工具可能包含在这个目录中:
D:\ android-ndk-r9d\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64\bin\
好了言归正传,如何使用这两个工具,下面具体介绍:
1. 找到日志中的关键函数指针
其实很简单,就是找到backtrace信息中,属于我们自己的so文件报错的行。
首先要找到backtrace信息,有的手机会明确打印一行backtrace(比如我们这次使用的手机),那么这一行下面的一系列以“#两位数字&pc”开头的行就是backtrace信息了。有时可能有的手机并不会打印一行backtrace,那么只要找到一段以“#两位数字&pc&”开头的行,就可以了。
其次要找到属于自己的so文件报错的行,这就比较简单了。找到这些行之后,记下这些行中的函数地址
2. 使用addr2line查找代码位置
执行如下的命令,多个指针地址可以在一个命令中带入,以空格隔开即可
arm-linux-androideabi-addr2line –e obj/local/armeabi/libhello-jni.so 0056c8 004f58
/android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/char_traits.h:229
/android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/_string.c:639
/WordSpaces/hello-jni/jni/hello-jni.cpp:69
/WordSpaces hello-jni/jni/hello-jni.cpp:6
从addr2line的结果就能看到,我们拿到了我们自己的错误代码的调用关系和行数,在hello-jni.cpp的69行和61行(另外两行因为使用的是标准函数,可以忽略掉),结果和ndk-stack是一致的,说明ndk-stack也是通过addr2line来获取代码位置的。
补充信息:在本文完成后,有同学提供了下列方法,在使用addr2line查找代码位置时增加 -f 参数,就可以可以获取函数信息,这样可以忽略下面一小节objdump的使用
arm-linux-androideabi-addr2line -f –e obj/local/armeabi/libhello-jni.so 00004fb4
得到的结果如下
/WordSpaces/hello-jni/jni/hello-jni.cpp:69
3. 使用objdump获取函数信息
通过addr2line命令,其实我们已经找到了我们代码中出错的位置,已经可以帮助程序员定位问题所在了。但是,这个方法只能获取代码行数,并没有显示函数信息,显得不那么“完美”,对于追求极致的程序员来说,这当然是不够的。下面我们就演示怎么来定位函数信息。
使用如下命令导出函数表:
arm-linux-androideabi-objdump –S obj/local/armeabi/libhello-jni.so & hello.asm
在生成的asm文件中查找刚刚我们定位的两个关键指针00004fb4和00004f58
从这两张图可以清楚的看到(要注意的是,在不同的NDK版本和不同的操作系统中,asm文件的格式不是完全相同,但都大同小异,请大家仔细比对),这两个指针分别属于willCrash()和JNI_OnLoad()函数,再结合刚才addr2line的结果,那么这两个地址分别对应的信息就是:
00004fb4: willCrash() /WordSpaces/hello-jni/jni/hello-jni.cpp:69
00004f58: JNI_OnLoad()/WordSpaces/hello-jni/jni/hello-jni.cpp:61
相当完美,和ndk-stack得到的信息完全一致!
使用Testin崩溃分析服务定位NDK错误
以上提到的方法,只适合在开发测试期间,如果你的应用或者游戏已经发布上线,而用户经常反馈说崩溃、闪退,指望用户帮你收集信息定位问题,几乎是不可能的。这个时候,我们就需要用其他的手段来捕获崩溃信息。
目前业界已经有一些公司推出了崩溃信息收集的服务,通过嵌入SDK,在程序发生崩溃时收集堆栈信息,发送到云服务平台,从而帮助开发者定位错误信息。在这方面,处于领先地位的是国内的Testin和国外的crittercism,其中crittercism需要付费,而且没有专门的中国开发者支持,我们更推荐Testin,其崩溃分析服务是完全免费的。
Testin从1.4版本开始支持NDK的崩溃分析,其最新版本已经升级到1.7。当程序发生NDK错误时,其内嵌的SDK会收集程序在用户手机上发生崩溃时的堆栈信息(主要就是上面我们通过logcat日志获取到的函数指针)、设备信息、线程信息等等,SDK将这些信息上报至Testin云服务平台,只要登陆到Testin平台,就可以看到所有用户上报的崩溃信息,包括NDK;并且这些崩溃做过归一化的处理,在不同系统和ROM的版本上打印的信息会略有不同,但是在Testin的网站上这些都做了很好的处理,避免了我们一些重复劳动。
上图的红框部分,就是从用户手机上报的,我们自己的so中报错的函数指针地址堆栈信息,就和我们开发时从logcat读到的日志一样,是一些晦涩难懂的指针地址,Testin为NDK崩溃提供了符号化的功能,只要将我们编译过程中产生的包含符号表的so文件上传(上文我们提到过的obj/local/目录下的适用于各个CPU架构的so),就可以自动将函数指针地址定位到函数名称和代码行数。符号化之后,看起来就和我们前面在本地测试的结果是一样的了,一目了然。
而且使用这个功能还有一个好处:这些包含符号表的so文件,在每次我们自己编译之后都会改变,很有可能我们刚刚发布一个新版本,这些目录下的so就已经变了,因为开发者会程序的修改程序;在这样的情况下,即使我们拿到了崩溃时的堆栈信息,那也无法再进行符号化了。所以我们在编译打包完成后记得备份我们的so文件。这时我们可以将这些文件上传到Testin进行符号化的工作,Testin会为我们保存和管理不同版本的so文件,确保信息不会丢失。来看一下符号化之后的显示:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1877次
排名:千里之外使用 Android NDK 重用现有的 C 代码
开始之前首先,了解 Android 原生开发工具包(NDK)的动机之一是得以利用开源项目,大多数项目都是用 C 语言编写的。完成本教程后,您将了解到如何创建 Java 本地接口(JNI)库,它使用 C 语言编写,使用原生开发工具包(NDK)进行编译,并将该库包含到了使用 Java 语言编写的 Android 应用程序中。应用程序演示了如何根据原始图像数据执行基本的图像处理操作。您还将学习如何扩展 Eclipse 构建环境以将 NDK 项目集成到 Android SDK 项目文件中。以此为基础,您可以更好地将现有开源代码移植到 Android 平台。关于本教程本教程介绍了 Eclipse 环境中的 Android NDK。NDK 过去常常使用 C 编程语言为 Android 应用程序添加功能。本教程以 NDK 及其常用场景的概述开始。然后,介绍了图像处理,以及本教程的应用程序 IBM Photo Phun 的简介和演示。本应用程序混合使用了基于 SDK 的 Java 代码和 NDK 编译的 C 代码。随后,本教程介绍了 Java 本地接口(JNI),这是使用 NDK 时您会感兴趣的一种技术。对完整项目源文件的预先了解,可以为本文所构建的应用程序提供一个路线图。然后您将逐步构建此应用程序。本文为您阐述了所有涉及的 Java 类和 C 源文件。最后,自定义 Eclipse 构建环境来将 NDK 工具链直接集成到易用的 Eclipse 构建流程中。先决条件要学习本教程,您应该熟悉使用 Android SDK 构建 Android 应用程序,并对 C 编程语言有基本的了解。此外,您需要了解下列内容:Eclipse 和 Android Developer Tools (ADT) — Primary code editor、Java Compiler 和 Android Development Tools 插件Android 软件开发工具包(SDK)Android 原生开发工具包(NDK)PNG 图像 — 用于测试图像处理操作的图像我在 MacBook Pro 上使用 Eclipse V3.4.2 和 Android SDK V8(支持名为 Android 2.2 (Froyo) 版本)创建了本教程的代码示例。本教程使用的 NDK 版本是 r4b。代码要求使用版本 r4b 或更高版本,因为在之前的 NDK 版本,Android NDK 没有图像处理功能。参见
获得这些工具的链接。Android NDK我们首先介绍 Android NDK,以及如何使用它来改进 Android 平台。尽管 Android SDK 提供了非常丰富的编程环境,但 Android NDK 扩大了其范围,并且通过引入现有源代码(一些是专有代码,另一些是开源代码)加速了所需功能的交付。NDK可从 Android 网站免费下载 NDK 版本。NDK 包括将以 C 语言编写的功能纳入 Android 应用程序所需的所有组件。NDK 的初始版本仅提供多数基本功能且有很多限制。随后发布的每个 NDK 版本,都扩展了其功能。对于 r5 或 NDK 应用程序,编写者可直接以 C 语言编写应用程序的大多数内容,包括用户接口和事件处理功能。此处演示的图像处理功能包含在 NDK 的 r4b 版本中。NDK 的两种常用方式是提高应用程序性能和将其移植到 Android 来利用现有 C 代码。首先介绍性能改进。以 C 语言编写代码并不保证可大幅提升性能。事实上,与编写良好的 Java 应用程序相比,糟糕的本机代码会降低应用程序性能。当以 C 语言精心编写功能来执行基于内存或计算密集型操作(像本教程中演示的那样)时,可提升应用程序性能。具体地讲,利用指针的算法特别适用于 NDK。NDK 的第二种常用用例是移植到为另一个平台(比如 Linux®)编写的现有 C 代码部分。本教程演示了 NDK,强调了性能和重用。NDK 包含一个编译器和一些构建脚本,支持您关注 C 源文件并保留 NDK 安装的构建。可轻松将 NDK 构建流程包含到 Eclipse 开发环境中,在
部分进行了演示。在开始应用程序之前,先简单介绍一些数字图像处理的基础知识。数字图像处理的基础知识现代计算机技术的一个很好的方面是数码摄影的出现和普及。数码摄影不仅仅是捕捉孩子正在玩耍的数码影像。可在随手拍摄的手机照片、高端的婚纱照相簿、宇宙深空图像和许多其他应用场景中找到数字图像。修改数字图像是我们的兴趣,并且是本教程的示例应用程序的核心功能。数字图像操作有很多方式,包括但不限于以下操作:裁剪— 选择图像的一部分缩放— 更改图像的大小旋转— 更改图像的方向转换— 从一种格式转换为另一种格式取样— 更改图像的密度混合/变形— 更改图像的外观筛选— 提取图像的元素,比如颜色或频率边缘检测— 用于机器视觉应用程序来标识图像中的对象压缩— 降低图像的存储大小通过像素操作增强图像:
直方图均衡化对比度亮度其中一些操作是逐个像素进行的,而其他一些操作包含每次对图像的一小部分进行操作的矩阵数学模型。无论是什么操作,所有图像处理算法都包含处理原始图像数据。此教程演示了像素和矩阵操作的使用,以 C 编程语言编写,在 Android 设备上运行。应用程序架构本部分介绍此教程的示例应用程序的架构,从概述完成的项目开始,然后介绍其构建的每个主要步骤。您可以自己逐步重建应用程序,或者是从
部分下载完整的项目。完整项目本教程演示了简单的图像处理应用程序 IBM Photo Phun 的构建。 显示了 Eclipse IDE 的一个屏幕截图,通过展开项目来查看源文件和输出文件。图 1. Eclipse 项目视图应用程序的 UI 是使用传统 Android 开发技术构建的,使用了一个布局文件(main.xml)和一个 Activity,在 IBMPhotoPhun.java 中实现。位于项目主文件夹下方名为 jni 文件夹的一个 C 源文件中,包含图像处理例程。NDK 工具链将 C 源文件编译到一个名为 libibmphotophun.so 的共享库文件中。编译的库文件都存储在 libs 文件夹中。每个目标硬件平台或处理器架构都会创建一个库文件。表 1 枚举了应用程序的源文件。表 1. 所需的应用程序源文件文件注释IBMPhotoPhun.java扩展 UI 的 Android Activity 类和应用逻辑Ibmphotophun.c实现图像处理例程main.xml应用程序 UI 的主页AndroidManifest.xmlAndroid 应用程序的部署描述符sampleimage.png用于演示目的的图像(可使用自己的图像替换此图像)Android.mkNDK 用于构建 JNI 库的 makefile 片段如果您没有一个正在运行的 Android 开发环境,现在是安装 Android 工具的好时机。有关如何设置 Android 开发环境的更多信息,请参见
获得所需工具的链接,以及其他一些关于开发 Android 应用程序的文章和教程。熟悉 Android 有助于了解本教程。既然您已经了解了架构和应用程序,现在可以查看它在 Android 设备上运行时的外观。演示应用程序有时在开始时牢记结尾很有帮助,因此在您了解创建此应用程序的逐步过程之前,可快速操作一下。以下屏幕截图来自运行 Android 2.2 (Froyo) 的 Nexus One。图像是使用 Dalvik Debug Monitor Service (DDMS) 工具捕获的,该工具作为 Android Developer Tools Eclipse 插件的一部分安装。 显示了装有示例图像的应用程序的主界面。快速查看以下图像,您会了解我如何得到一个程序设计器而不是其他一些电视节目,这归功于我这张“很上镜”的脸。在构建应用程序时可使用自己的图像替换此图像。图 2. IBM Photo Phun 应用程序的主界面屏幕顶部的按钮支持您更改图像。第一个按钮 Reset 将图像恢复为原始彩色图像。选择 Convert Image 按钮会将图像转换为灰度图像,如图 3 所示。图 3. 灰度图像Find Edges 按钮从原始彩色图像开始,将其转换为灰度图像,然后执行 Sobel Edge Detection 算法。图 4 显示了边缘检测算法的结果。图 4. 检测边缘边缘检测算法通常在机器视觉应用程序中使用,并且作为多步骤图像处理操作的第一个步骤。从这点来说,最后的两个按钮支持您通过更改每个像素的亮度来使图像变暗或变亮。图 5 显示了灰度图像的较亮版本。图 5. 增加了亮度图 6 显示的图像边缘较暗。图 6. 降低了亮度现在您可以开始处理图像了。创建应用程序在本部分中,我们将利用 Eclipse ADT 插件中提供的工具创建应用程序。即使不熟悉针对 Android 创建应用程序,您也能够轻松地从本部分中学到东西。 部分包含对创建 Android 应用程序很有用文章和教程。ADT 新项目向导在 Eclipse IDE 内部创建应用程序非常简单,这是因为有 ADT 新项目向导,如图 7 所示。图 7. 创建新 Android 项目填写新项目向导时,提供以下信息:有效的项目名称。目标版本。注意,对于此项目,您必须使用 Android V2.2 或 Android V2.3 作为目标 SDK 平台级别。有效的应用程序名称。软件包名称。活动名称。完成向导界面填写后,选择 Finish。单击 Next 按钮提示创建“Test”项目伴随此项目,这一步很有用,不过我们不进行介绍了。将项目导入到 Eclipse 后,您可以实现此应用程序所需的源文件。可从应用程序的 UI 元素开始。实现用户界面此应用程序的 UI 非常简单。它包括一个 Activity,并具有一些 Button 小部件和一个 ImageView 小部件来显示所选图像。与许多 Android 应用程序一样,在 main.xml 文件中定义 UI,如清单 1 所示。清单 1. UI 布局文件 main.xml&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffffff"
&LinearLayout xmlns:android="/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnReset"
android:text="Reset"
android:visibility="visible"
android:onClick="onResetImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnConvert"
android:text="Convert Image"
android:visibility="visible"
android:onClick="onConvertToGray"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnFindEdges"
android:text="Find Edges"
android:visibility="visible"
android:onClick="onFindEdges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnDimmer"
android:text="- "
android:visibility="visible"
android:onClick="onDimmer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnBrighter"
android:text=" +"
android:visibility="visible"
android:onClick="onBrighter"
&/LinearLayout&
&ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_gravity="center_vertical|center_horizontal"
android:id="@+id/ivDisplay"
&/LinearLayout&注意两个 LinearLayout 元素的使用。外部元素控制 UI 的垂直流动,而内部元素 LinearLayout 设置其子元素的水平管理。水平布局元素包括界面顶部的所有 Button 小部件。ImageView 设置为使包含的图像居中,并且有一个 id 属性,支持您在运行时处理其内容。每个 Button 小部件都有一个 onClick 属性。此属性的值必须对应于包含的 Activity 类中的公共无效方法,它具有一个 View 参数。此方法是设置单击处理函数的一种快速简单的方法,无需在运行时定义匿名处理函数或访问元素。参见
了解使用此方法处理点击 Button 的更多信息。在布局文件中定义了 UI 后,必须编写 Activity 代码以使用 UI。这在文件 IBMPhotoPhun.java 中实现,其中扩展了 Activity 类。您可以在清单 2 中查看代码。清单 2. IBM Photo Phun 导入和类说明/*
* IBMPhotoPhun.java
* Author: Frank Ableson
* Contact Info:
package com.msi.ibm.
import android.app.A
import android.os.B
import android.util.L
import android.graphics.BitmapF
import android.graphics.B
import android.graphics.Bitmap.C
import android.view.V
import android.widget.ImageV
public class IBMPhotoPhun extends Activity {
private String tag = "IBMPhotoPhun";
private Bitmap bitmapOrig =
private Bitmap bitmapGray =
private Bitmap bitmapWip =
private ImageView ivDisplay =
// NDK STUFF
System.loadLibrary("ibmphotophun");
public native void convertToGray(Bitmap bitmapIn,Bitmap bitmapOut);
public native void changeBrightness(int direction,Bitmap bitmap);
public native void findEdges(Bitmap bitmapIn,Bitmap bitmapOut);
// END NDK STUFF
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.i(tag,"before image stuff");
ivDisplay = (ImageView) findViewById(R.id.ivDisplay);
// load bitmap from resources
BitmapFactory.Options options = new BitmapFactory.Options();
// Make sure it is 24 bit color as our image processing algorithm
// expects this format
options.inPreferredConfig = Config.ARGB_8888;
bitmapOrig = BitmapFactory.decodeResource(this.getResources(),
R.drawable.sampleimage,options);
if (bitmapOrig != null)
ivDisplay.setImageBitmap(bitmapOrig);
public void onResetImage(View v) {
Log.i(tag,"onResetImage");
ivDisplay.setImageBitmap(bitmapOrig);
public void onFindEdges(View v) {
Log.i(tag,"onFindEdges");
// make sure our target bitmaps are happy
bitmapGray = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
bitmapWip = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
// before finding edges, we need to convert this image to gray
convertToGray(bitmapOrig,bitmapGray);
// find edges in the image
findEdges(bitmapGray,bitmapWip);
ivDisplay.setImageBitmap(bitmapWip);
public void onConvertToGray(View v) {
Log.i(tag,"onConvertToGray");
bitmapWip = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
convertToGray(bitmapOrig,bitmapWip);
ivDisplay.setImageBitmap(bitmapWip);
public void onDimmer(View v) {
Log.i(tag,"onDimmer");
changeBrightness(2,bitmapWip);
ivDisplay.setImageBitmap(bitmapWip);
public void onBrighter(View v) {
Log.i(tag,"onBrighter");
changeBrightness(1,bitmapWip);
ivDisplay.setImageBitmap(bitmapWip);
}可将此分为一些有名的注释:有一些成员变量:
tag— 这在所有日志记录语句中使用,以在调试期间筛选 LogCat。bitmapOrig— 此 Bitmap 保存原始彩色图像。bitmapGray— 此 Bitmap 保存图像的灰度副本,并且仅在 findEdges 例程中暂时使用。bitmapWip— 此 Bitmap 保存修改亮度值时的灰度图像。ivDisplay— 此 ImageView 是对 main.xml 布局文件中定义的 ImageView 的引用。“NDK Stuff”部分包括 4 行内容:
使用 System.loadLibrary 加载了包含本机代码的库。注意,此代码包含在名为 "static" 的代码块中。这将在应用程序启动时加载库。convertToGray 的原型声明 — 此函数有两个参数。第一个是颜色 Bitmap,第二个是使用第一个参数的灰度版本填充的 Bitmap。changeBrightness 的原型声明 — 此函数有两个参数。第一个是表示 up 或 down 的整数。第二个是逐个像素进行修改的 Bitmap。findEdges 的原型声明。它有两个参数。第一个是灰度 Bitmap,第二个是接收图像“仅显示边缘”版本的 Bitmap。onCreate 方法扩大了 R.layout.main 标识的布局,获得了对 ImageView (ivDisplay) 小部件的引用,然后加载了资源中的彩色图像。
BitmapFactory 方法有一个 options 参数,支持您加载 ARGB 格式的图像。“A”表示 alpha 通道,“RGB”表示红色、绿色、蓝色。许多开源图像处理库需要 24 位彩色图像,红色、绿色和蓝色各 8 位,并且每个像素由 RGB 三元组成。每个值的范围为 0 至 255。Android 平台上的图像保存为 32 位整数(alpha、红色、绿色和蓝色)。加载了图像之后,会在 ImageView 中显示。此类中的方法均衡对应于 Button 小部件的“click handlers”:
onResetImage 将原始彩色图像加载到 ImageView。onConvertToGray 将目标 Bitmap 创建为 8 位图像,并调用 convertToGray 本机函数。生成的图像(bitmapWip)在 ImageView 中显示。onFindEdges 创建两个中间 Bitmap 对象,将彩色图像转换为灰度图像并调用 findEdges 本机函数。生成的图像(bitmapWip)在 ImageView 中显示。onDimmer 和 onBrighter 都调用 changeBrightness 方法来修改图像。生成的图像(bitmapWip)在 ImageView 中显示。结束了 UI 代码。现在是您实现图像处理例程的时候了,但是首先我们需要创建库。创建 NDK 文件现在已经具备了 Android 应用程序的 UI 和应用逻辑,您需要实现图像处理函数。为此,您需要使用 NDK 创建 Java 本地库。在本例中,您将使用一些公共域 C 代码来实现图像处理函数并将它们打包到 Android 应用程序可用的库中。构建本地库NDK 创建了共享库并依赖于 makefile 系统。要为本项目构建本地库,您需要执行以下步骤:在项目文件下创建一个名为 jni 的新文件夹。在 jni 文件夹内,创建名为 Android.mk 的文件,该文件包含正确构建和命名库的 makefile 说明。在 jni 文件夹内,创建源文件,会在 Android.mk 文件中引用该源文件。在本教程中,C 源文件的名称为 ibmphotophun.c。清单 3 包含 Android.mk 文件的内容。清单 3. Android.mk 文件LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE
:= ibmphotophun
LOCAL_SRC_FILES := ibmphotophun.c
LOCAL_LDLIBS
:= -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)其中,该 makefile(片段)指示 NDK:将 ibmphotophun.c 源文件编译到共享库中。命名共享库。默认情况下,共享库命名约定为 lib&modulename&.so。因此,在这里将生成文件命名为 libibmphotophun.so。指定所需的 "input" 库。共享库依赖于两个用于日志记录(liblog.so)和 jni 图形(libjnigraphics.so)的内置库文件,日志记录库允许您为 LogCat 添加条目,这在项目的开发阶段很有用。图形库为使用 Android 位图及其图像数据提供例程。ibmphotophun.c 源文件包含一些 C 包含语句和 argb 类型(对应于 Android SDK 中的彩色数据类型)的定义。清单 4 显示了没有图像例程的 ibmphotophun.c,图像例程将在下一个清单中展示。清单 4. Ibmphotophun.c 宏命令和包含命令/*
* ibmphotophun.c
* Author: Frank Ableson
* Contact Info:
#include &jni.h&
#include &android/log.h&
#include &android/bitmap.h&
"libibmphotophun"
__android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
typedef struct
}LOGI 和 LOGE 宏对 Logging 工具进行调用,并且在功能上分别相当于 Android SDK 中的 Log.i() 和 Log.e()。具有 typedef struct 关键字的 argb 数据类型支持 C 代码评估以 32 位整数存储的单个像素的 4 个数据元素。3 个包含语句为 C 编译器提供必要的声明,以进行 jni 粘合、日志记录和位图处理。现在可以实现一些图像处理例程了,但是在我们检查代码之前,您需要了解 JNI 函数的命名约定。当 Java 代码调用本机函数时,它将函数名称映射到一个展开或修饰函数,该函数由 JNI 共享库导出。下面是约定:Java_fully_qualified_classname_functionname。例如,convertToGray 函数在 C 代码中实现为 Java_com_msi_ibm_ndk_IBMPhotoPhun_convertToGray。JNI 函数的前两个参数包含一个 JNI 环境指针和调用类对象实例。有关 JNI 的更多信息,请参见
部分。构建库非常简单。打开终端(或 DOS)窗口并将目录更改到存储这些文件的 jni 文件夹。确保路径中有 NDK 并执行 ndk-build 脚本。此脚本包含构建库所需的所有粘合代码。生成的库放置在与 jni 文件夹级别相同的 libs 文件夹(例如,&project folder&/libs/)。Eclipse 的 ADT 插件打包了 Android 应用程序后,会自动包含和传输库文件。会为每个支持的硬件平台都生成一个库文件。在运行时加载正确的库。现在来看一下如何实现图像处理算法。实现图像处理算法此应用程序使用的图像处理例程根据各种公共域和学术例程以及我在图像处理方面的经验进行改编。两个函数使用像素操作,第三个函数使用最小矩阵方法。我们首先看一下清单 5 中的 convertToGray 函数。清单 5. convertToGray 函数/*
convertToGray
Pixel operation
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_convertToGray(JNIEnv
* env, jobject
obj, jobject bitmapcolor,jobject bitmapgray)
AndroidBitmapI
AndroidBitmapI
LOGI("convertToGray");
if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) & 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
if ((ret = AndroidBitmap_getInfo(env, bitmapgray, &infogray)) & 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
LOGI("color image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infocolor.width,infocolor.height,infocolor.stride,infocolor.format,infocolor.flags);
if (infocolor.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGE("Bitmap format is not RGBA_8888 !");
LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
LOGE("Bitmap format is not A_8 !");
if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) & 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
if ((ret = AndroidBitmap_lockPixels(env, bitmapgray, &pixelsgray)) & 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
// modify pixels with image processing algorithm
for (y=0;y&infocolor.y++) {
argb * line = (argb *)
uint8_t * grayline = (uint8_t *)
for (x=0;x&infocolor.x++) {
grayline[x] = 0.3 * line[x].red + 0.59 * line[x].green + 0.11*line[x].
pixelscolor = (char *)pixelscolor + infocolor.
pixelsgray = (char *) pixelsgray + infogray.
LOGI("unlocking pixels");
AndroidBitmap_unlockPixels(env, bitmapcolor);
AndroidBitmap_unlockPixels(env, bitmapgray);
}此函数有两个调用 Java 代码的参数:ARGB 格式中的彩色 Bitmap 和接收彩色图像的灰度版本的 8 位灰度 Bitmap。下面是代码的简单介绍:AndroidBitmapInfo 结构,在 bitmap.h 中定义,有助于了解 Bitmap 对象。AndroidBitmap_getInfo 函数,在 jnigraphics 库中,获取有关具体 Bitmap 对象的信息。下一步是确保传入到 convertToGray 函数的位图是想要的格式。AndroidBitmap_lockPixels 函数锁定图像数据,这样您就可以直接在数据上执行操作。AndroidBitmap_unlockPixels 函数解锁之前锁定的像素数据。这些函数应该被称为“锁定/解锁对”。在锁定和解锁函数之前,您会看到像素操作。指针 fun以 C 语言编写的图像处理应用程序通常涉及指针的使用。指针是“指向”存储地址的变量。变量的数据类型指定您使用的存储类型和大小。例如,char 表示带符号的 8 位值,因此 char 指针 (char *) 支持您引用 8 位值,并通过该指针执行操作。图像数据表示为 uint8_t,这表示未带符号的 8 位值,其中每个字节的值范围为 0 至 255。3 个未带符号的 8 位值集合表示 24 位图像的图像数据的一个像素。完成图像设计处理各行数据和在列之前移动。Bitmap 结构包含名为“跨距”的成员。跨距表示单个图像数据行的宽度(以字节为单位)。例如,带有 alpha 通道的 24 位彩色图像每个像素为 32 位或 4 字节。因此,宽度为 320 像素的图像,其跨距为 320*4 或 1,280 字节。8 位灰度图像的每个像素为 8 位或 1 字节。宽度为 320 像素的灰度图像,其跨距为 320*1 或 320 字节。记住此信息,我们看一下将彩色图像转换为灰度图像的图像处理算法:当“锁定”图像数据时,对于输入彩色图像,名为 pixelscolor 的指针引用图像数据的基准地址;对于输出灰度图像,名为 pixelsgray 的指针引用图像数据的基准地址。两个 for-next 循环支持您迭代整个图像。
首先,迭代图像的高,每次一“行”。使用 infocolor.height 值获得行的计数。每迭代一行,指针就设置对应于行的图像数据第一“列”的存储位置。当您迭代某一行的列时,就将彩色数据的每个像素转换为表示灰度值的单个值。当转换了完成行时,您需要将指针指向下一行。这通过跳过跨距值来完成。对于所有像素导向的图像处理操作,您需要按照上述格式进行。例如,考虑清单 6 所示的 changeBrightness 函数。清单 6. changeBrightness 函数/*
changeBrightness
Pixel Operation
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_changeBrightness(JNIEnv
* env, jobject
obj, int direction,jobject bitmap)
AndroidBitmapI
if ((ret = AndroidBitmap_getInfo(env, bitmap, &infogray)) & 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
LOGE("Bitmap format is not A_8 !");
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixelsgray)) & 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
// modify pixels with image processing algorithm
LOGI("time to modify pixels....");
for (y=0;y&infogray.y++) {
uint8_t * grayline = (uint8_t *)
for (x=0;x&infogray.x++) {
v = (int) grayline[x];
if (direction == 1)
if (v &= 255) {
grayline[x] = 255;
} else if (v &= 0) {
grayline[x] = 0;
grayline[x] = (uint8_t)
pixelsgray = (char *) pixelsgray + infogray.
AndroidBitmap_unlockPixels(env, bitmap);
}此函数的操作方式与 convertToGray 函数非常相似,但具有以下不同之处:此函数仅需要一个灰度位图。已修改了传入的图像。此函数每次对每个像素增加或减少 5。此常量可以更改。我使用 5 是因为每次进行时会明显更改图像,无需总是按加号或减号按钮。像素值被限制为 0 至 255。在直接使用未带符号的变量进行这些操作时要小心,因为很容易会“越界”。例如,当我使用 changeBrightness 函数为一个值(比如 252)增加了 5,而最终得到的实际有效值却是 2。得到的效果很有意思,但并不是我想要的。这就是我使用名为 v 的整数,并将像素数据映射到带符号整数,然后再将该值与 0 和 255 比较的原因。还有一个图像处理算法需要检查:findEdges 函数,它的工作方式与之前的两个像素导向的函数有些不同。清单 7 展示了 findEdges 函数。清单 7. findEdges 函数检测到图像中的轮廓/*
Matrix operation
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_findEdges(JNIEnv
* env, jobject
obj, jobject bitmapgray,jobject bitmapedges)
AndroidBitmapI
AndroidBitmapI
sumX,sumY,
LOGI("findEdges running");
Gx[0][0] = -1;Gx[0][1] = 0;Gx[0][2] = 1;
Gx[1][0] = -2;Gx[1][1] = 0;Gx[1][2] = 2;
Gx[2][0] = -1;Gx[2][1] = 0;Gx[2][2] = 1;
Gy[0][0] = 1;Gy[0][1] = 2;Gy[0][2] = 1;
Gy[1][0] = 0;Gy[1][1] = 0;Gy[1][2] = 0;
Gy[2][0] = -1;Gy[2][1] = -2;Gy[2][2] = -1;
if ((ret = AndroidBitmap_getInfo(env, bitmapgray, &infogray)) & 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
if ((ret = AndroidBitmap_getInfo(env, bitmapedges, &infoedges)) & 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
LOGE("Bitmap format is not A_8 !");
LOGI("color image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infoedges.width,infoedges.height,infoedges.stride,infoedges.format,infoedges.flags);
if (infoedges.format != ANDROID_BITMAP_FORMAT_A_8) {
LOGE("Bitmap format is not A_8 !");
if ((ret = AndroidBitmap_lockPixels(env, bitmapgray, &pixelsgray)) & 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
if ((ret = AndroidBitmap_lockPixels(env, bitmapedges, &pixelsedge)) & 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
// modify pixels with image processing algorithm
LOGI("time to modify pixels....");
graydata = (uint8_t *)
edgedata = (uint8_t *)
for (y=0;y&=infogray.height - 1;y++) {
for (x=0;x&infogray.width -1;x++) {
// check boundaries
if (y==0 || y == infogray.height-1) {
} else if (x == 0 || x == infogray.width -1) {
// calc X gradient
for (i=-1;i&=1;i++) {
for (j=-1;j&=1;j++) {
sumX += (int) ( (*(graydata + x + i + (y + j)
* infogray.stride)) * Gx[i+1][j+1]);
// calc Y gradient
for (i=-1;i&=1;i++) {
for (j=-1;j&=1;j++) {
sumY += (int) ( (*(graydata + x + i + (y + j)
* infogray.stride)) * Gy[i+1][j+1]);
sum = abs(sumX) + abs(sumY);
if (sum&255) sum = 255;
if (sum&0) sum = 0;
*(edgedata + x + y*infogray.width) = 255 - (uint8_t)
AndroidBitmap_unlockPixels(env, bitmapgray);
AndroidBitmap_unlockPixels(env, bitmapedges);
}findEdges 例程与之前的两个函数有很多相同之处:与 convertToGray 函数一样,此函数有两个位图参数,但是在本例中,两个位图参数都是灰度图像。询问了位图以保证它们为所需的格式。会相应地锁定和解锁位图像素。该算法迭代源图像的行和列。与之前的两个函数不同,此函数将每个像素与其周围的像素进行比较,而不是简单地对像素值本身进行数学操作。此函数中实现的算法是 Sobel Edge Detection 算法的变体。在本实现中,我将每个像素与其周围的一个像素进行了比较。此算法和其他算法的变体可使用更大的“边界”获得不同的结果。将每个像素与其周围的像素进行比较突出了像素之间的对比度,而且这样做强调了“边缘”。我不会深入介绍此算法涉及的数学原理,这有两个原因。第一,它超出了本教程的讨论范围。第二,本教程的确切目的 — 重用现有 C 源代码— 由使用现有图像处理算法进行演示。您能够获得所需的结果,无需重新开始或将此代码移到 Java 技术。由于指针算法,C 是处理图像数据的理想环境。有关图像处理算法的更多信息,请参见
部分。自定义 Eclipse使用 Eclipse IDE 的好处之一是很少需要编译。每次在 Eclipse IDE 中保存文件时,就会自动构建项目。这非常适用于 Android SDK(即 Java)文件和 Android XML 文件,但是对于 NDK 构建的库来说怎么样呢?我们来了解一下。扩展 Eclipse 环境如前所述,构建本地库和运行 ndk-build 命令一样简单。但是,除了简单的练习外,当处理其他任何项目时,像下面这样做会很麻烦,即跳出终端或命令窗口并执行 ndk-build 命令,返回到 Eclipse 环境,然后通过“单击”一个项目文件执行刷新,这会强制重新编译和重新打包完成的应用程序。解决方案是根据您的 Android 项目自定义构建设置以扩展 Eclipse 环境。要修改构建设置,首先查看 Android 项目的属性并选择列表中的 Builders。添加一个新 Builder 并将其移动到列表顶部,如图 8 所示。图 8. 修改构建设置每个生成器都有 4 个配置选项卡。为您的生成器命名,比如将其命名为 Build NDK Library,然后填充选项卡。第一个选项卡("Main")指定可执行工具的位置和工作目录。浏览到您 jni 文件夹中的 ndk-build 文件和工作目录,如图 9 所示。图 9. 设置 NDK 的 Builder 属性您只想使用 ndk-build 而不是 Eclipse 工作空间的其他内容操作此项目,因此设置 Refresh 选项卡,如图 10 所示。图 10. 设置 Refresh 选项卡只有在修改 Android.mk 文件或 ibmphotophun.c 文件时,才会想要重新构建库。为此,在 Build Options 选项卡中 Specify Resources 按钮的下方选择 jni 文件夹。此外,通过核对适当的时间指定何时想要构建工具,如图 11 所示。图 11. 设置 build options单击 OK 确认设置后,确保此 NDK 构建工具设置为列表中的第一个条目,方法是选择 Up 按钮,直到其位于 Builders 列表的顶部,如图 7 所示。要测试是否正确设置了您的 Builder,打开 Eclipse 中的 ibmphotophun.c 源文件,方法是右键单击源文件,然后使用 Text Editor 打开它。进行简单的更改,然后保存文件。您应在控制台窗口中看到 NDK 工具链输出,如图 12 所示。如果您的 C 代码有错误,则它们显示为红色。图 12. NDK 输出在 Eclipse IDE 的控制台中显示将 NDK 结合到您的构建流程中,您可以侧重于编写代码而不用了解构建环境太多。需要对应用逻辑进行更改?没问题,修改 Java 代码并保存文件。需要调整图像处理算法?不用担心,只需修改 C 例程并保存文件即可。Eclipse 和 ADT 插件会完成其他操作。结束语本教程展示了一个示例,解析如何使用 Android NDK 将由 C 编程语言编写的功能纳入到应用程序当中。本文所使用的这些功能示范了如何利用开源/公共领域图像处理算法。以相似的方式,可借助 Android NDK 使用任何与 Android 平台兼容的有效 C 代码。除了在 Eclipse 中使用 NDK 的机制的内容之外,您还学习了有关图像处理的一些基本概念。
下载资源 (os-androidndk-IBMPhotoPhun.source.zip | 995KB)相关主题查看本教程的 。在
上阅读由 Frank Ableson 编写的“”。阅读由 Bill Green 编写的
了解更多信息。,由 Dwayne Phillips 编写,由 R&D Publications (ISBN 0-13-) 出版,提供了有关图像处理的更多信息。查看关于
的 Wikipedia 页面。查看
上的 。阅读 Linux Magazine 上的“”,了解在 Android 中如何处理按钮。阅读“”了解 Android 的网络功能。
查看“ ”了解有关在 Android 上使用 XML 和如何使用它们构建自己的 Android 应用程序的不同观点。,由 Frank Ableson 编写,是一本全面介绍 Android 开发的书。,由 Brian Fling 编写,讨论构建移动产品的最佳指南、标准、技术和最佳实践。获取 。访问 Android 的发起方 。阅读“”开始开发 Android 应用程序。阅读 Frank Ableson 在 Linux Magazine 上发表的“”。从 Android 官方开发人员站点下载 ,访问 API 参考资料并获得有关 Android 的最新消息。V1.5 和更新的版本都可以。本教程使用的是 Android SDK V8,它支持版本 Android 2.2 (Froyo)。 下载 。本教程使用的 NDK 版本是 r4b。 Android 是开源的,这意味着您可以从
获得其源代码。获取最新的 。本教程使用的版本是 V3.4.2。访问 developerWorks ,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品以及结合使用。下载
或 ,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Open sourceArticleID=657962ArticleTitle=使用 Android NDK 重用现有的 C 代码publish-date=

我要回帖

更多关于 android开发者中心 的文章

 

随机推荐