- 浏览: 498182 次
- 性别:
- 来自: 深圳
文章分类
- 全部博客 (185)
- job (15)
- linux/windows/unix/bash/shell (31)
- JAVA/J2EE/spring/hibernate/struts (30)
- VC/C++ (48)
- mysql/postgresql (6)
- php/jsp/asp/pear (1)
- FMS/flex/openlaszlo/red5/openmeetings (34)
- apache/tomcat/ftp/svn (6)
- xen/vm/Hadoop/cloudcompute (6)
- visual studio/eclipse/zendstudi/ant (8)
- others (1)
- windows异常处理 __try __except (1)
- (1)
- matlab (4)
- android (0)
最新评论
-
hongzhounlfd:
很透彻,很详细
依赖注入和控制反转 -
jefferyqjy:
谢谢~言简意赅~很明了!
依赖注入和控制反转 -
elderbrother:
太好了,谢谢
依赖注入和控制反转 -
east_zyd_zhao:
终于搞明白了
依赖注入和控制反转 -
Dremeng:
完美,一看就懂理解透彻
依赖注入和控制反转
Android JNI调用
2011-02-24 13:39
来公司第二天,让我俩整问题。
JNI调用红外扫描
这个我们没学过啊,百度一下,整理点资料 留以后看 以后学习··
1.jni的基本工作原理
(1)java的本质
想搞明白jni的本质,还要从java的本质说起。从本质上来说,java这门语言就是一门脚本语言(这是偶的个人理解,希望java大侠们不要用板砖拍我),它的运行完全依赖于脚本引擎对java的代码进行解释和执行(当然了,现代的java已经先进许多,可以从源代码编译成.class之类的中间格式的二进制文件,这种处理会大大地加快java脚本的运行速度,但是基本的执行方式仍然不变,由脚本引擎(我们称之为JVM)来执行,与python、perl之类的纯脚本相比,它只是把脚本变成了二进制格式而已。另外就是java本身对面向对象的概念支持得很好,拥有完善的功能库可供调用,把这个脚本引擎移植到所有平台上,那么这个脚本自然就实现所谓的“跨平台”了)。绝大多数的脚本引擎都支持一个很显著的特性,就是可以通过c/c++编写模块,在脚本中调用这些模块,以此来类比java,也是一样的,java一定要提供一种在脚本中调用c/c++编写的模块的机制,才能称得上是一个相对完善的脚本引擎。
(2)android中的java
android平台从本质上是 由arm-linux操作系统 和一个叫做dalvik的java虚拟机组成的。所有在android模拟器上面看到的那些华丽的界面,都是用java语言编写的(参见android平台源代码的frameworks/base目录)。目前看来dalvik只是提供了一个标准的支持jni调用的java虚拟机环境。android平台中所有的硬件相关的操作均是采用jni技术进行了封装,由java去调用jni模块,而jni模块使用c/c++调用android本身的arm-linux底层驱动。
例如,frameworks/base/libs/ui目录下面有一个叫做“EGLDisplaySurface.cpp”的文件,里面的:
status_t EGLDisplaySurface::mapFrameBuffer()函数中,就有直接对android的arm-linux中的framebuffer的初始化代码。
这也更加印证了,android其实是依靠java+jni建立起来的王国。hoho,如此一来,就凸显出jni在android开发中的重要性(当然,一些简单的小程序是完全可以只用java就搞定的).
“jni”的子目录,这个目录将用来存放.c的文件。
(3)编写jni模块的java调用类
这是必然的了,jni嘛,一定要有调用者才能够工作在src的最内层目录里面添加一个叫做JniModule.java的原文件,看上去如下所示:
public class JniModule {
static {
System.loadLibrary("aaaa") ;
}
public native static int jni_add(int a, int b) ;
}
注意,偶们最终会生成一个叫做libaaaa.so的arm兼容的二进制动态库,但是在使用System.loadLibrary动态载入的时候,只需要填写lib和.so之间的名字aaaa即可,在此实验的功能仅仅是两个数字a和b的求和计算以及如何在jni的c语言模块中把log日志打印到logcat中。
在JniTest.java中,偶们可以如下调用这个类:
public void onClick(View v) {
String ss ;
int a = 3 ;
int b = 4 ;
ss = "" ;
switch(v.getId()) {
case R.id.button1:
ss = "a="+String.valueOf(a)+","+"b=" + String.valueOf(b) + "," + "a+b=" +
String.valueOf(JniModule.jni_add(a, b));
setTitle(ss) ;
break ;
case R.id.button2:
setTitle("button2 click") ;
break ;
case R.id.button3:
int pid = android.os.Process.myPid();
android.os.Process.killProcess(pid);
break ;
}
}
注意,这里的button3是很重要的,功能是得到当前程序的进程id,然后显示地杀掉它!
为什么要这么做呢?原因在于,android里面的常规退出函数并没有真正地关闭当前运行的进程,而是切换到后台去了。这对普通的java应用看上去很平常,而且可以加速再次启动该程序的速度,但是对于带有jni模块的java程序而言就是恶梦,因为程序没有真的关闭。所以那个libaaaa.so库,会一直停留在内存中,这时候如果你希望把旧的so库替换成新的库,那就要重启手机才行。。。很痛苦,所以想到了这种办法,直接杀掉自己,那么下一次启动的时候就会自动重新载入最新的so库。
(4)生成java程序与c程序的接口文件
谈到这里,自然就会联想到是c语言的.h文件了,现在的问题在于如何从.java文件生成我们需要的.h格式的c/c++文件。答案就是javah这个小工具基本上所有的jdk都会提供:
javah -classpath "java类的地址" <你的java模块位置>
利用javah就可以很容易地将JniModule.java代码的native标记的部分转换为c/c++的.h文件中定义的导出函数。
以下是偶用于测试的makefile,相信懂makefile语法的朋友可以很容易就看明白偶在做什么,
为了实验能够非常“精确”地进行,在这个makefile中的全部路径都采用了绝对路径,其实用相对路径也是可以的(省力多了,但在做实验的时候要求绝对正确无误。。。):
CC=arm-none-linux-gnueabi-gcc
LD=arm-none-linux-gnueabi-ld
MV=mv
JH=javah
JHFLAGS=-classpath "/home/wayne/works/workspace/JniTest/bin"
LDFLAGS=-T "/home/wayne/CodeSourcery/Sourcery_G++_Lite/arm-none-linux-gnueabi/lib/ldscripts/armelf_linux_eabi.xsc" -shared
CFLAGS=-I. -I/home/wayne/works/workspace/JniTest/jni/include -I/home/wayne/works/workspace/JniTest/jni/include/linux -I/home/wayne/works/workspace/JniTest/jni -fpic
all: libaaaa.so
com_hurraytimes_jnitest_JniModule.h:
$(JH) $(JHFLAGS) com.hurraytimes.jnitest.JniModule
aaaa.o: aaaa.c com_hurraytimes_jnitest_JniModule.h
$(CC) $(CFLAGS) -c -o aaaa.o aaaa.c
libaaaa.so: aaaa.o
$(LD) $(LDFLAGS) -o libaaaa.so aaaa.o libcutils.a
$(RM) ../libs/armeabi/libaaaa.so
$(MV) libaaaa.so ../libs/armeabi/
clean:
$(RM) *.o *.so *~
这里需要特别提一点的,就是关于arm-none-linux-gnueabi-gcc的使用问题,这个编译器自从到了2008版本就开始琢磨着实现更加方便地“cross compiler”的功能了。以往的版本是arm-xxx-linux-gcc,就是为了编译arm-linux平台的软件的,如果你的芯片从三星的变为菲利普的,那么整条工具链就要重新编译。现在的这个2008版的为了让广大开发者(尤其是多种不同芯片平台的嵌入式开发者)的计算机里面不要安装好多套for不同芯片组的gcc工具链,弄了一个-T的参数,这里就可以让开发者使用一个gcc工具链生成不同平台和格式的可执行代码以及链接的库。虽然如此,但是偶还是觉得不大习惯,总之谢谢CodeSourcery很贴心的功能,让偶花了半个多小时在琢磨和查资料,到底是什么原因导致生成的jni模块无法在android上工作。
(5)jni模块的打包问题
再次声明,在android 1.5 cupcake以后的版本才可以用偶下文提到的打包方法。
在查看了ndk的脚本以后,我才知道原来android 1.5版本在打包apk的时候,是完全可以支持直接将.so的jni库打包到apk安装包中去的,解决了偶们这种铁杆c/c++开发者开发自己的jni组件的发布问题,java脚本嘛,做个事件啥的中转就完成它的使命了。
其实具体操作起来非常简单,在当前项目的跟目录下创建如下目录:
/libs/armeabi
然后把自己生成好的so库拷贝到这个armeabi目录下面即可,运行ant生成apk发布包的时候,就会自动地将/libs/armeabi目录下的so库打包到apk文件中,然后就是直接安装就好了!非常简单方便。
(6)关于ant里面实现jni的makefile调用的方法
首先肯定一下,ant是个不错的东西。但是如果说它要取代makefile的地位,偶个人固执地认为很难。makefile语法简单,随手就可以敲一个,但回头看看ant的build.xml,第一眼看上去就头晕。
xml很不错,但是就是他大爷的乱七八糟,而且居然宣称说是给人看的东西。。。凡事真正有些实质性的用处,用xml存储的数据(用于演示hello world之类的xml就免了),让人看起来都会头晕。
ant采用xml作为基本输入,偶个人认为还不如仿效makefile弄一套相对简单的语法来得方便。
好了不再发牢骚了,开始看一下,如何为android的build.xml添加ant支持的xml实现自动调用jni的makefile文件。
以下是偶用ant来编译jni模块的xml,稍加修改就可以用于开发和实验中,把这些加到</project>之前就可以了:
<target name="mk" >
<exec dir="./jni" executable="make" os="Linux" failonerror="true">
</exec>
</target>
<target name="mkclr" >
<exec dir="./jni" executable="make" os="Linux" failonerror="true">
<arg line=" clean" />
</exec>
</target>
使用方法就是ant mk和ant mkclr一个是相当于调用make,另一个是相当于调用make clean。
其余的操作都放到makefile里面去了(尽管偶的一位java朋友告诉我,makefile能做的事情ant都能做,makefile不能做的事情ant也能做,偶还是倾向于用makefile。。。除了顽固不化以外,最重要的一个原因是──懒得敲那么多东西。)。
最后需要说的就是,在偶传上来的代码中,可能会发现有一个叫做libcutils.a的编译好的静态库,这个东西就“说来话长”了,主要原因是偶在做实验的时候,还没有ndk发布出来,android手机里面也没个gdbserver之类的工具,调试起来十分痛苦。偶认为再怎么弱,也要输出点东西到logcat吧?!因此,从android-platform的平台源代码中提取了cutils的头文件,直接把android平台编译出来的二进制.a文件拷贝出来,链接到偶自己的“土法”生成的so库里面,这样就可以调用libcutils.a中定义的log函数,就可以直接通过联机的logcat查看jni中的log日志输出,很爽!ndk的文档中承诺,在未来的android ndk开发包中会提供在线调试的功能(gdbserver吗?呵呵,有了gdb,我想他们想要完全控制android已经不太现实了,毕竟gdb太强大了。。。)
到此为止,“土法”编译和编写jni的方法已经基本记录和讲解完毕。相信能够耐着性子看完偶这篇文章的朋友,一定能够对ndk的本质有了新的认识。而不是那里面readme和howto文档中的几行字,修改android.mk之类云云。。。
当然有了上面的这些底层编译的探索,加上ndk里面提供的.h和若干运行时库,甚至android平台源代码里面编译出来的静态二进制包,jni几乎可以实现任何功能。
还是那句话,“潘多拉”的盒子一旦打开,能否控制得住,就不是google这样的公司能够左右的了。
等有时间再来写写关于使用google的ndk来编写和调试jni模块的方法。。。
这篇文章来说说ndk的使用方法,其实主要是关于ndk的一些编译选项的研究和翻译(其实人家google的文档已经说的很清楚了)。偶选用的测试环境是slackware 12.0 + android 1.5 r1 for linux + jdk 1.6.0_12,ndk选用的是android 1.5 ndk r1这个版本的(直接解压就行,免安装的)。
1、从ndk安装说起
ndk安装的时候需要运行一个~/android-ndk-1.5_r1/build/目录下面的一个叫做host-setup.sh的脚本。大略读了一下这个脚本,发现这个主要是用来生成out/host/host/config.mk文件的。主要用于指定用户操作系统的判断以及支持的编译器类型(设置makefile中的cc,ar,ld之类的变量)
ndk的目录介绍。
2、ndk的目录结构分析
进入android-ndk-1.5_r1目录,看到如下目录结构:
GNUmakefile: 标准的makefile格式的文件,用于引用build/core/main.mk的编译脚本。
README.TXT:基本的说明,没啥大用,真正有用的文档都在docs目录下面。
apps/:存放带有jni接口的android工程目录(工程里面有利用native关键字定义的java函数)
build/:存放着几乎所有的ndk编译相关的脚本以及必要的静态链接库。
docs/:存放这ndk的所有“官方”文档,每一篇文档对于jni编写者来说这里面的任何一点点资料都是无价的。
out/:存放一些中间的临时文件,例如jni的.c/.cpp文件编译过程中产生的.o文件等。
sources/:存放jni文件的.c/.cpp的源代码文件。
3、基本的使用方法
(1)创建一个android工程
进入apps目录,运行如下命令:
android create project --target 2 --package com.TWM --activity NDKTest --path ./NDKTest/project
通过命令行创建一个叫做NDKTest的activity,注意这里的--path需要设置为./XXXXX/project这个目录,这个XXXXX目录主要是为了ndk的make区分不同项目和工程使用的。编写Application.mk文件的时候,一定要把Application.mk写到这个XXXXX目录下面。
$NDK/apps/<myapp>/Application.mk
另外,编译jni库的时候使用的命令也是如此:
make APP=<your app name>
这里的<your app name>实际上也是这个XXXXX目录。
(2)为工程添加一个jni的java调用接口
进入app/NDKTest/project/src/com/TWM/NdkTest目录,建立一个新的java文件(例如:NDKJni.java),然后把代码写成类似下面这个样子:
package com.TWM.NdkTest ;
public class NDKJni {
public native int MyFunc(int a, int b) ;
static {
System.loadLibrary("NDKjni") ;
}
}
这里的MyFunc由于是使用native修饰,因此,这个MyFunc函数是一个调用jni的函数。
(3)为java工程编写Application.mk文件
该文件主要放在app/NDKTest目录下,用于告知ndk的编译脚本,当前的程序需要哪个jni模块。
看上去应该是这个样子的:
APP_PROJECT_PATH := $(call my-dir)/project ---> 当前目录下的project目录包含了jni模块的java接口
APP_MODULES := NDKTest --->当前模块的名字叫做NDKTest
(4)弄清楚java程序的包层次
以当前的这个project为例,就是上面代码中的package com.TWM.NdkTest,定义的类名为NDKJni。因此,根据这个包的层次,可以根据jni文件的函数命名规则定义函数:
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b) ;
当然,手工根据包层次定义jni函数还是很痛苦的,可以借助于javah工具:
mkdir -p apps/NDKTest/project/jni
cd apps/NDKTest/project/jni
javah -classpath "../bin/classes" com.TWM.NdkTest.NDKJni
然后就会自动生成一个叫做com_TWM_NdkTest_NDKJni.h的文件,里面的内容基本上跟手工生成的差不多:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_TWM_NdkTest_NDKJni */
#ifndef _Included_com_TWM_NdkTest_NDKJni
#define _Included_com_TWM_NdkTest_NDKJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_TWM_NdkTest_NDKJni
* Method: MyFunc
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
好了,有了这个函数的定义,就可以准备去编写jni了。
(5)进入source目录
建立目录ndktest,然后在里面放置两个文件,一个是随便命名例如a.c,另外一个是一个叫做Android.mk的编译脚本文件。
里面的内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NDKTest --->这里是指定jni模块的名字,生成的so库应该叫做libNDKTest.so,这个名字一定要与Application.mk文件中的APP_MODULES相同。
LOCAL_SRC_FILES := a.c
include $(BUILD_SHARED_LIBRARY) --->这里是告诉编译脚本生成的库是共享库(本身NDK是可以生成动态库和静态库的)。
然后在a.c里面写入的内容如下:
JNIEXPORT jint JNICALL
Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b)
{
return a+b ;
}
(6)开始编译jni模块
首先进入android-ndk-1.5_r1目录,然后运行如下命令:
make APP=NDKTest [回车]
这个时候就会看到它开始编译并且在apps/NDKTest/projects/目录下建立了libs/armeabi/目录,并且把生成的libNDKTest.so拷贝到该目录下。
看到这里或许有人会问,它的编译参数怎么没有,我怎么调试阿?!其实很简单,只要多加一个编译参数即可。
make APP=NDKTest V=1 [回车]
你就会看到如下的输出(偶的测试程序里面把上面说的a.c改成NDKTest.c了,所以看到的内容略有不同):
wayne@wayne:~/android-ndk-1.5_r1$ make APP=NDKTest V=1
Android NDK: Building for application 'NDKTest'
Compile thumb : NDKTest <= sources/ndktest/NDKTest.c
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -Ibuild/platforms/android-1.5/arch-arm/usr/include -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -Isources/ndktest -DANDROID -O2 -DNDEBUG -g -c -MMD -MP -MF out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp sources/ndktest/NDKTest.c -o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o
build/core/mkdeps.sh out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d
SharedLibrary : libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -nostdlib -Wl,-soname,libNDKTest.so -Wl,-shared,-Bsymbolic out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o -Wl,--whole-archive -Wl,--no-whole-archive build/platforms/android-1.5/arch-arm/usr/lib/libc.so build/platforms/android-1.5/arch-arm/usr/lib/libstdc++.so build/platforms/android-1.5/arch-arm/usr/lib/libm.so -Wl,--no-undefined -Wl,-rpath-link=build/platforms/android-1.5/arch-arm/usr/lib /home/wayne/android-ndk-1.5_r1/build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a -o out/apps/NDKTest/android-1.5-arm/libNDKTest.so
Install : libNDKTest.so => apps/NDKTest/project/libs/armeabi
mkdir -p apps/NDKTest/project/libs/armeabi
install -p out/apps/NDKTest/android-1.5-arm/libNDKTest.so apps/NDKTest/project/libs/armeabi/libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-strip --strip-debug apps/NDKTest/project/libs/armeabi/libNDKTest.so
(7)开始编译android本地java程序
进入apps/NDKTest/project目录,然后运行ant debug来生成调试版本的apk包,注意,此时,apk包里面会自动把刚刚生成的libNDKTest.so打包进去的。这一点可以通过把apk文件用unzip命令解包来验证,在此不再赘述。
这就是android-ndk编译jni程序的全过程,确实要比偶在上一篇文章中描述的方法来得简单许多,总结一下:
(a)在apps目录里面创建带有native关键字声明的java项目。(注意,目录需要多打一层,用来放Application.mk文件)
(b)在sources目录里面创建真正的jni模块目录,里面一定要包含一个叫做Android.mk的文件。
(c)在apps里面的Application.mk与sources目录里面的Android.mk在MODULE的名字上一定要“遥相呼应”。
(d)编译的方法是,进入android-ndk-1.5_r1目录,运行make APP=<your app name> V=1生成jni库;其实不止如此,make APP=<your app name> clean也可以清除掉。
2011-02-24 13:39
来公司第二天,让我俩整问题。
JNI调用红外扫描
这个我们没学过啊,百度一下,整理点资料 留以后看 以后学习··
1.jni的基本工作原理
(1)java的本质
想搞明白jni的本质,还要从java的本质说起。从本质上来说,java这门语言就是一门脚本语言(这是偶的个人理解,希望java大侠们不要用板砖拍我),它的运行完全依赖于脚本引擎对java的代码进行解释和执行(当然了,现代的java已经先进许多,可以从源代码编译成.class之类的中间格式的二进制文件,这种处理会大大地加快java脚本的运行速度,但是基本的执行方式仍然不变,由脚本引擎(我们称之为JVM)来执行,与python、perl之类的纯脚本相比,它只是把脚本变成了二进制格式而已。另外就是java本身对面向对象的概念支持得很好,拥有完善的功能库可供调用,把这个脚本引擎移植到所有平台上,那么这个脚本自然就实现所谓的“跨平台”了)。绝大多数的脚本引擎都支持一个很显著的特性,就是可以通过c/c++编写模块,在脚本中调用这些模块,以此来类比java,也是一样的,java一定要提供一种在脚本中调用c/c++编写的模块的机制,才能称得上是一个相对完善的脚本引擎。
(2)android中的java
android平台从本质上是 由arm-linux操作系统 和一个叫做dalvik的java虚拟机组成的。所有在android模拟器上面看到的那些华丽的界面,都是用java语言编写的(参见android平台源代码的frameworks/base目录)。目前看来dalvik只是提供了一个标准的支持jni调用的java虚拟机环境。android平台中所有的硬件相关的操作均是采用jni技术进行了封装,由java去调用jni模块,而jni模块使用c/c++调用android本身的arm-linux底层驱动。
例如,frameworks/base/libs/ui目录下面有一个叫做“EGLDisplaySurface.cpp”的文件,里面的:
status_t EGLDisplaySurface::mapFrameBuffer()函数中,就有直接对android的arm-linux中的framebuffer的初始化代码。
这也更加印证了,android其实是依靠java+jni建立起来的王国。hoho,如此一来,就凸显出jni在android开发中的重要性(当然,一些简单的小程序是完全可以只用java就搞定的).
“jni”的子目录,这个目录将用来存放.c的文件。
(3)编写jni模块的java调用类
这是必然的了,jni嘛,一定要有调用者才能够工作在src的最内层目录里面添加一个叫做JniModule.java的原文件,看上去如下所示:
public class JniModule {
static {
System.loadLibrary("aaaa") ;
}
public native static int jni_add(int a, int b) ;
}
注意,偶们最终会生成一个叫做libaaaa.so的arm兼容的二进制动态库,但是在使用System.loadLibrary动态载入的时候,只需要填写lib和.so之间的名字aaaa即可,在此实验的功能仅仅是两个数字a和b的求和计算以及如何在jni的c语言模块中把log日志打印到logcat中。
在JniTest.java中,偶们可以如下调用这个类:
public void onClick(View v) {
String ss ;
int a = 3 ;
int b = 4 ;
ss = "" ;
switch(v.getId()) {
case R.id.button1:
ss = "a="+String.valueOf(a)+","+"b=" + String.valueOf(b) + "," + "a+b=" +
String.valueOf(JniModule.jni_add(a, b));
setTitle(ss) ;
break ;
case R.id.button2:
setTitle("button2 click") ;
break ;
case R.id.button3:
int pid = android.os.Process.myPid();
android.os.Process.killProcess(pid);
break ;
}
}
注意,这里的button3是很重要的,功能是得到当前程序的进程id,然后显示地杀掉它!
为什么要这么做呢?原因在于,android里面的常规退出函数并没有真正地关闭当前运行的进程,而是切换到后台去了。这对普通的java应用看上去很平常,而且可以加速再次启动该程序的速度,但是对于带有jni模块的java程序而言就是恶梦,因为程序没有真的关闭。所以那个libaaaa.so库,会一直停留在内存中,这时候如果你希望把旧的so库替换成新的库,那就要重启手机才行。。。很痛苦,所以想到了这种办法,直接杀掉自己,那么下一次启动的时候就会自动重新载入最新的so库。
(4)生成java程序与c程序的接口文件
谈到这里,自然就会联想到是c语言的.h文件了,现在的问题在于如何从.java文件生成我们需要的.h格式的c/c++文件。答案就是javah这个小工具基本上所有的jdk都会提供:
javah -classpath "java类的地址" <你的java模块位置>
利用javah就可以很容易地将JniModule.java代码的native标记的部分转换为c/c++的.h文件中定义的导出函数。
以下是偶用于测试的makefile,相信懂makefile语法的朋友可以很容易就看明白偶在做什么,
为了实验能够非常“精确”地进行,在这个makefile中的全部路径都采用了绝对路径,其实用相对路径也是可以的(省力多了,但在做实验的时候要求绝对正确无误。。。):
CC=arm-none-linux-gnueabi-gcc
LD=arm-none-linux-gnueabi-ld
MV=mv
JH=javah
JHFLAGS=-classpath "/home/wayne/works/workspace/JniTest/bin"
LDFLAGS=-T "/home/wayne/CodeSourcery/Sourcery_G++_Lite/arm-none-linux-gnueabi/lib/ldscripts/armelf_linux_eabi.xsc" -shared
CFLAGS=-I. -I/home/wayne/works/workspace/JniTest/jni/include -I/home/wayne/works/workspace/JniTest/jni/include/linux -I/home/wayne/works/workspace/JniTest/jni -fpic
all: libaaaa.so
com_hurraytimes_jnitest_JniModule.h:
$(JH) $(JHFLAGS) com.hurraytimes.jnitest.JniModule
aaaa.o: aaaa.c com_hurraytimes_jnitest_JniModule.h
$(CC) $(CFLAGS) -c -o aaaa.o aaaa.c
libaaaa.so: aaaa.o
$(LD) $(LDFLAGS) -o libaaaa.so aaaa.o libcutils.a
$(RM) ../libs/armeabi/libaaaa.so
$(MV) libaaaa.so ../libs/armeabi/
clean:
$(RM) *.o *.so *~
这里需要特别提一点的,就是关于arm-none-linux-gnueabi-gcc的使用问题,这个编译器自从到了2008版本就开始琢磨着实现更加方便地“cross compiler”的功能了。以往的版本是arm-xxx-linux-gcc,就是为了编译arm-linux平台的软件的,如果你的芯片从三星的变为菲利普的,那么整条工具链就要重新编译。现在的这个2008版的为了让广大开发者(尤其是多种不同芯片平台的嵌入式开发者)的计算机里面不要安装好多套for不同芯片组的gcc工具链,弄了一个-T的参数,这里就可以让开发者使用一个gcc工具链生成不同平台和格式的可执行代码以及链接的库。虽然如此,但是偶还是觉得不大习惯,总之谢谢CodeSourcery很贴心的功能,让偶花了半个多小时在琢磨和查资料,到底是什么原因导致生成的jni模块无法在android上工作。
(5)jni模块的打包问题
再次声明,在android 1.5 cupcake以后的版本才可以用偶下文提到的打包方法。
在查看了ndk的脚本以后,我才知道原来android 1.5版本在打包apk的时候,是完全可以支持直接将.so的jni库打包到apk安装包中去的,解决了偶们这种铁杆c/c++开发者开发自己的jni组件的发布问题,java脚本嘛,做个事件啥的中转就完成它的使命了。
其实具体操作起来非常简单,在当前项目的跟目录下创建如下目录:
/libs/armeabi
然后把自己生成好的so库拷贝到这个armeabi目录下面即可,运行ant生成apk发布包的时候,就会自动地将/libs/armeabi目录下的so库打包到apk文件中,然后就是直接安装就好了!非常简单方便。
(6)关于ant里面实现jni的makefile调用的方法
首先肯定一下,ant是个不错的东西。但是如果说它要取代makefile的地位,偶个人固执地认为很难。makefile语法简单,随手就可以敲一个,但回头看看ant的build.xml,第一眼看上去就头晕。
xml很不错,但是就是他大爷的乱七八糟,而且居然宣称说是给人看的东西。。。凡事真正有些实质性的用处,用xml存储的数据(用于演示hello world之类的xml就免了),让人看起来都会头晕。
ant采用xml作为基本输入,偶个人认为还不如仿效makefile弄一套相对简单的语法来得方便。
好了不再发牢骚了,开始看一下,如何为android的build.xml添加ant支持的xml实现自动调用jni的makefile文件。
以下是偶用ant来编译jni模块的xml,稍加修改就可以用于开发和实验中,把这些加到</project>之前就可以了:
<target name="mk" >
<exec dir="./jni" executable="make" os="Linux" failonerror="true">
</exec>
</target>
<target name="mkclr" >
<exec dir="./jni" executable="make" os="Linux" failonerror="true">
<arg line=" clean" />
</exec>
</target>
使用方法就是ant mk和ant mkclr一个是相当于调用make,另一个是相当于调用make clean。
其余的操作都放到makefile里面去了(尽管偶的一位java朋友告诉我,makefile能做的事情ant都能做,makefile不能做的事情ant也能做,偶还是倾向于用makefile。。。除了顽固不化以外,最重要的一个原因是──懒得敲那么多东西。)。
最后需要说的就是,在偶传上来的代码中,可能会发现有一个叫做libcutils.a的编译好的静态库,这个东西就“说来话长”了,主要原因是偶在做实验的时候,还没有ndk发布出来,android手机里面也没个gdbserver之类的工具,调试起来十分痛苦。偶认为再怎么弱,也要输出点东西到logcat吧?!因此,从android-platform的平台源代码中提取了cutils的头文件,直接把android平台编译出来的二进制.a文件拷贝出来,链接到偶自己的“土法”生成的so库里面,这样就可以调用libcutils.a中定义的log函数,就可以直接通过联机的logcat查看jni中的log日志输出,很爽!ndk的文档中承诺,在未来的android ndk开发包中会提供在线调试的功能(gdbserver吗?呵呵,有了gdb,我想他们想要完全控制android已经不太现实了,毕竟gdb太强大了。。。)
到此为止,“土法”编译和编写jni的方法已经基本记录和讲解完毕。相信能够耐着性子看完偶这篇文章的朋友,一定能够对ndk的本质有了新的认识。而不是那里面readme和howto文档中的几行字,修改android.mk之类云云。。。
当然有了上面的这些底层编译的探索,加上ndk里面提供的.h和若干运行时库,甚至android平台源代码里面编译出来的静态二进制包,jni几乎可以实现任何功能。
还是那句话,“潘多拉”的盒子一旦打开,能否控制得住,就不是google这样的公司能够左右的了。
等有时间再来写写关于使用google的ndk来编写和调试jni模块的方法。。。
这篇文章来说说ndk的使用方法,其实主要是关于ndk的一些编译选项的研究和翻译(其实人家google的文档已经说的很清楚了)。偶选用的测试环境是slackware 12.0 + android 1.5 r1 for linux + jdk 1.6.0_12,ndk选用的是android 1.5 ndk r1这个版本的(直接解压就行,免安装的)。
1、从ndk安装说起
ndk安装的时候需要运行一个~/android-ndk-1.5_r1/build/目录下面的一个叫做host-setup.sh的脚本。大略读了一下这个脚本,发现这个主要是用来生成out/host/host/config.mk文件的。主要用于指定用户操作系统的判断以及支持的编译器类型(设置makefile中的cc,ar,ld之类的变量)
ndk的目录介绍。
2、ndk的目录结构分析
进入android-ndk-1.5_r1目录,看到如下目录结构:
GNUmakefile: 标准的makefile格式的文件,用于引用build/core/main.mk的编译脚本。
README.TXT:基本的说明,没啥大用,真正有用的文档都在docs目录下面。
apps/:存放带有jni接口的android工程目录(工程里面有利用native关键字定义的java函数)
build/:存放着几乎所有的ndk编译相关的脚本以及必要的静态链接库。
docs/:存放这ndk的所有“官方”文档,每一篇文档对于jni编写者来说这里面的任何一点点资料都是无价的。
out/:存放一些中间的临时文件,例如jni的.c/.cpp文件编译过程中产生的.o文件等。
sources/:存放jni文件的.c/.cpp的源代码文件。
3、基本的使用方法
(1)创建一个android工程
进入apps目录,运行如下命令:
android create project --target 2 --package com.TWM --activity NDKTest --path ./NDKTest/project
通过命令行创建一个叫做NDKTest的activity,注意这里的--path需要设置为./XXXXX/project这个目录,这个XXXXX目录主要是为了ndk的make区分不同项目和工程使用的。编写Application.mk文件的时候,一定要把Application.mk写到这个XXXXX目录下面。
$NDK/apps/<myapp>/Application.mk
另外,编译jni库的时候使用的命令也是如此:
make APP=<your app name>
这里的<your app name>实际上也是这个XXXXX目录。
(2)为工程添加一个jni的java调用接口
进入app/NDKTest/project/src/com/TWM/NdkTest目录,建立一个新的java文件(例如:NDKJni.java),然后把代码写成类似下面这个样子:
package com.TWM.NdkTest ;
public class NDKJni {
public native int MyFunc(int a, int b) ;
static {
System.loadLibrary("NDKjni") ;
}
}
这里的MyFunc由于是使用native修饰,因此,这个MyFunc函数是一个调用jni的函数。
(3)为java工程编写Application.mk文件
该文件主要放在app/NDKTest目录下,用于告知ndk的编译脚本,当前的程序需要哪个jni模块。
看上去应该是这个样子的:
APP_PROJECT_PATH := $(call my-dir)/project ---> 当前目录下的project目录包含了jni模块的java接口
APP_MODULES := NDKTest --->当前模块的名字叫做NDKTest
(4)弄清楚java程序的包层次
以当前的这个project为例,就是上面代码中的package com.TWM.NdkTest,定义的类名为NDKJni。因此,根据这个包的层次,可以根据jni文件的函数命名规则定义函数:
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b) ;
当然,手工根据包层次定义jni函数还是很痛苦的,可以借助于javah工具:
mkdir -p apps/NDKTest/project/jni
cd apps/NDKTest/project/jni
javah -classpath "../bin/classes" com.TWM.NdkTest.NDKJni
然后就会自动生成一个叫做com_TWM_NdkTest_NDKJni.h的文件,里面的内容基本上跟手工生成的差不多:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_TWM_NdkTest_NDKJni */
#ifndef _Included_com_TWM_NdkTest_NDKJni
#define _Included_com_TWM_NdkTest_NDKJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_TWM_NdkTest_NDKJni
* Method: MyFunc
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_TWM_NdkTest_NDKJni_MyFunc
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
好了,有了这个函数的定义,就可以准备去编写jni了。
(5)进入source目录
建立目录ndktest,然后在里面放置两个文件,一个是随便命名例如a.c,另外一个是一个叫做Android.mk的编译脚本文件。
里面的内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NDKTest --->这里是指定jni模块的名字,生成的so库应该叫做libNDKTest.so,这个名字一定要与Application.mk文件中的APP_MODULES相同。
LOCAL_SRC_FILES := a.c
include $(BUILD_SHARED_LIBRARY) --->这里是告诉编译脚本生成的库是共享库(本身NDK是可以生成动态库和静态库的)。
然后在a.c里面写入的内容如下:
JNIEXPORT jint JNICALL
Java_com_TWM_NdkTest_NDKJni_MyFunc(JNIEnv * env, jobject thiz, jint a, jint b)
{
return a+b ;
}
(6)开始编译jni模块
首先进入android-ndk-1.5_r1目录,然后运行如下命令:
make APP=NDKTest [回车]
这个时候就会看到它开始编译并且在apps/NDKTest/projects/目录下建立了libs/armeabi/目录,并且把生成的libNDKTest.so拷贝到该目录下。
看到这里或许有人会问,它的编译参数怎么没有,我怎么调试阿?!其实很简单,只要多加一个编译参数即可。
make APP=NDKTest V=1 [回车]
你就会看到如下的输出(偶的测试程序里面把上面说的a.c改成NDKTest.c了,所以看到的内容略有不同):
wayne@wayne:~/android-ndk-1.5_r1$ make APP=NDKTest V=1
Android NDK: Building for application 'NDKTest'
Compile thumb : NDKTest <= sources/ndktest/NDKTest.c
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -Ibuild/platforms/android-1.5/arch-arm/usr/include -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -Isources/ndktest -DANDROID -O2 -DNDEBUG -g -c -MMD -MP -MF out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp sources/ndktest/NDKTest.c -o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o
build/core/mkdeps.sh out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d.tmp out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o.d
SharedLibrary : libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-gcc -nostdlib -Wl,-soname,libNDKTest.so -Wl,-shared,-Bsymbolic out/apps/NDKTest/android-1.5-arm/objs/NDKTest/NDKTest.o -Wl,--whole-archive -Wl,--no-whole-archive build/platforms/android-1.5/arch-arm/usr/lib/libc.so build/platforms/android-1.5/arch-arm/usr/lib/libstdc++.so build/platforms/android-1.5/arch-arm/usr/lib/libm.so -Wl,--no-undefined -Wl,-rpath-link=build/platforms/android-1.5/arch-arm/usr/lib /home/wayne/android-ndk-1.5_r1/build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a -o out/apps/NDKTest/android-1.5-arm/libNDKTest.so
Install : libNDKTest.so => apps/NDKTest/project/libs/armeabi
mkdir -p apps/NDKTest/project/libs/armeabi
install -p out/apps/NDKTest/android-1.5-arm/libNDKTest.so apps/NDKTest/project/libs/armeabi/libNDKTest.so
build/prebuilt/linux-x86/arm-eabi-4.2.1/bin/arm-eabi-strip --strip-debug apps/NDKTest/project/libs/armeabi/libNDKTest.so
(7)开始编译android本地java程序
进入apps/NDKTest/project目录,然后运行ant debug来生成调试版本的apk包,注意,此时,apk包里面会自动把刚刚生成的libNDKTest.so打包进去的。这一点可以通过把apk文件用unzip命令解包来验证,在此不再赘述。
这就是android-ndk编译jni程序的全过程,确实要比偶在上一篇文章中描述的方法来得简单许多,总结一下:
(a)在apps目录里面创建带有native关键字声明的java项目。(注意,目录需要多打一层,用来放Application.mk文件)
(b)在sources目录里面创建真正的jni模块目录,里面一定要包含一个叫做Android.mk的文件。
(c)在apps里面的Application.mk与sources目录里面的Android.mk在MODULE的名字上一定要“遥相呼应”。
(d)编译的方法是,进入android-ndk-1.5_r1目录,运行make APP=<your app name> V=1生成jni库;其实不止如此,make APP=<your app name> clean也可以清除掉。
发表评论
-
剖析Android消息机制
2011-10-26 15:56 1041剖析Android消息机制 在Android中,线程内部或者 ... -
C++STL轻松导学(2)
2011-09-27 17:02 13222.2.2 第二版:工业时代- ... -
C++ STL轻松导学
2011-09-27 16:59 1179作为C++标准不可缺少的 ... -
Chapter 6 Exceptions(JAVA EXCEPTION IN NATIVE CODE)
2011-09-26 09:53 1500Contents | Prev | Next | Index ... -
JNI编程中如何传递参数和返回值。
2011-09-14 17:51 1793首先要强调的是,native方法不但可以传递Java的基本类型 ... -
Windows Mobile与Android应用开发对比
2011-09-06 11:44 1297Windows Mobile在经历过最初的Wince系列,po ... -
定义VC 消息映射函数小结
2011-08-21 22:15 1322定义VC 消息映射函数小 ... -
多线程中的事件对象
2011-08-21 14:23 1448Using Event Objects 使用事件对象 Appl ... -
VC++多线程调用webservice实例
2011-08-21 12:04 1590一、开始多线程 1.开始 ... -
多线程同步机制(Vc++)
2011-08-21 09:46 1743Synchronizing Execution of Mult ... -
如何结束线程VC++
2011-08-21 09:20 2800Terminating a Thread Terminati ... -
VS2005使用多字节字符集问题
2011-08-03 13:27 20851>------ 已启动生成: 项目: psgdatat ... -
java中的jar关联SRC调试
2011-07-31 21:28 1114我现在的方法是: 打开后看到的是.class文件,然后点ch ... -
matlab的作图函数(二维) 星号,点号 颜色
2011-07-27 14:57 10042zz matlab的作图函数(二维 ... -
android 调用C++的so
2011-07-08 18:36 4399第一步:开发环境的安 ... -
JAVA环境变量配置和详解
2011-07-08 13:46 1215你知道Java环境变量如何配置吗,这里和大家分享一下,主要包括 ... -
windows异常处理__try__except
2011-07-07 14:24 1976try-except用法 try except是win ... -
Java中的一个byte
2011-06-30 14:34 1016Java中的一个byte,其范围是-128~127的,而Int ... -
NDK中char*如何转换成jstring
2011-06-30 13:05 1878JNIEXPORT jstring JNICALLJava_T ... -
CFileDialog多选文件时的最大数量
2011-06-25 20:29 2289system("explorer d:\我的 ...
相关推荐
6. 最后,确保你的 Android 项目配置支持原生库的编译和链接。如果你使用的是 Android Studio,可以在 `build.gradle` 文件中添加 `externalNativeBuild` 部分,如下所示: ```groovy android { ... default...
但是,由于Android系统版本和设备之间的差异,开发者在使用JNI时需要特别注意兼容性问题,尤其是在使用特定库或API时。 总结来说,JNI是Java平台提供的一种机制,允许Java代码调用本地(非Java)代码,以充分利用...
5. **Android JNI Device.doc内容推测**:这份文档很可能详细介绍了如何创建和使用JNI接口来与特定的Android设备驱动进行通信。可能包括了创建本地方法、处理设备I/O、错误处理、同步机制等内容。 6. **性能优化**...
Android jni基础知识, # This is a test examlpe foe android jni 1、实现一个android工程,在其Android.mk文件中不要定义jni相关的东西,代码中可以尽情的使用 2、使用mmm 命令编译该工程,能够编译成功的。...
### Android JNI 学习笔记详解 #### 一、JNI简介 JNI(Java Native Interface)...通过上述步骤,我们可以在Android应用中成功地实现和调用JNI功能。这不仅有助于提升应用性能,还可以充分利用现有的C/C++代码资源。
Android JNI(Java Native Interface)是Java平台的一部分,自Java 1.1开始引入,它提供了一种方式让Java代码能够与本地(非Java)代码进行交互,主要针对C和C++,但理论上支持任何调用约定兼容的语言。使用JNI虽然会...
JNI(Java Native Interface)是Java平台的标准组成部分,它允许Java代码和其他语言写的代码进行交互。在Android开发中,JNI尤其重要,因为有时我们需要利用C、C++等原生代码来实现一些性能敏感或者无法在Java中完成...
Android C、Java、JNI效率测试结果分析 Android 操作系统中,效率...本文档对 Android G1 环境中 C、Java、JNI 调用效率测试结果进行了详细的分析和讨论,结果表明,JNI 调用效率非常高,适合在 Android 环境中使用。
Android JNI(Java Native Interface)是Android系统中一个重要的技术,它允许Java代码和其他语言写的代码进行交互。JNI在Android开发中扮演着不可或缺的角色,尤其是在进行性能优化、利用硬件加速、调用C/C++库或者...
android jni详细介绍,包括如下文档: android_jni_javah_...Android技术之JNI和HAL.pdf Android通过JNI调用驱动程序(完全解析实例).doc 由浅入深,让你了解JNI如何在android里使用,也可以顺便了解一下android hal机制.
- **JNI编程指南.doc**:这份文档可能详细介绍了JNI的原理、使用方法、最佳实践以及常见问题的解决,是学习和理解JNI的关键资料。 5. **安卓毕业设计和课程设计**: - JNI技术可以应用于各种项目,例如开发一个...
23.JNI与Android VM之关系.doc 24.Observer样式应用于Android框架设计.doc 25.使用SQLite的Blob储存.mp3档案.doc 26.活用Android的Message Queue1.doc 27.活用Android的Message Queue(2).doc 28.Anrdroid的Message ...
JNI,全称Java Native Interface,是Java平台标准的一部分,它为Java程序员提供了一种方法,可以在Java应用程序中调用本地(非Java)...通过深入理解和熟练运用JNI,开发者能够创建更高效、功能更丰富的Android应用。
5本JNI资料,非常详细; 1.android_jni操作指南.pdf 2.JNI设计实践之路.pdf 3.jni详解.pdf 4.Java_JNI_编程进阶.pdf 5.Android_JNI总结.doc
本文的研究工作主要集中在基于 Android 平台的绘图软件开发,通过移植开源绘图软件 MyPaint 的笔刷引擎和开源视频编码库 FFMPEG,实现了 Android 绘图软件的开发,并探讨了一种利用 JNI 机制进行 Android 底层 C/C++...
通过理解JNI的实现方式,以及如何编译和使用JNI库,开发者可以充分利用Android的多语言特性,提升应用性能,或者实现特定的硬件功能。在实际项目中,JNI常用于图像处理、游戏引擎集成、加密算法等高性能需求的场景。
3. JNI层:android_os_Power.cpp作为Java层和C++层之间的桥梁。 4. HAL层:实现与具体硬件的交互,如电源管理芯片的控制。 5. Kernel层:接收HAL层的指令,控制设备进入或退出休眠状态。 通过跟踪这个框架,我们...
13. android JNI 简介与实现,讲解Java Native Interface,如何在Android中使用C/C++代码。 14. Android 开发指南中文版,对于初学者来说是非常实用的参考资源。 15. android ADT-0.9.6,可能是早期版本的Android...
3. 分析了 Android 底层开发的原理和方法,探讨了一种利用 JNI 机制进行 Android 底层 c/c++ 开发技术。这个过程中涉及到 Android 底层开发技术、JNI 机制、c/c++ 语言编程等。 知识点: * Android 底层开发技术:...